From e822299d2f05bd70fd2ee4ec0213f083fb6bbdbb Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 18 Jul 2022 17:40:18 +0200 Subject: [PATCH] Contributed modules --- .../web/modules/contrib/facets/composer.json | 2 +- .../config/schema/facets.facet.schema.yml | 6 + .../modules/contrib/facets/facets.info.yml | 8 +- .../web/modules/contrib/facets/facets.install | 36 + .../web/modules/contrib/facets/facets.module | 16 +- .../facets_range_widget.info.yml | 6 +- .../modules/facets_rest/facets_rest.info.yml | 6 +- .../tests/rest_view/rest_view.info.yml | 6 +- .../facets_searchbox_widget.info.yml | 6 +- .../facets_summary/facets_summary.info.yml | 6 +- .../src/Form/FacetsSummaryForm.php | 4 +- .../src/Form/FacetsSummarySettingsForm.php | 2 +- .../Block/FacetsSummaryBlockDeriver.php | 2 +- .../processor/ResetFacetsProcessor.php | 4 +- .../src/Processor/ProcessorPluginBase.php | 2 +- .../tests/src/Functional/IntegrationTest.php | 1 + .../Controller/FacetBlockAjaxController.php | 11 +- .../contrib/facets/src/Entity/Facet.php | 150 +++- .../contrib/facets/src/Event/FacetsEvents.php | 45 ++ .../src/Event/GetFacetCacheContexts.php | 72 ++ .../facets/src/Event/GetFacetCacheMaxAge.php | 72 ++ .../facets/src/Event/GetFacetCacheTags.php | 72 ++ .../facets/src/Event/PostBuildFacet.php | 56 ++ .../contrib/facets/src/Event/UrlCreated.php | 100 +++ .../contrib/facets/src/FacetInterface.php | 32 + .../contrib/facets/src/FacetListBuilder.php | 5 +- .../src/FacetManager/DefaultFacetManager.php | 33 +- .../FacetSource/FacetSourceDeriverBase.php | 2 +- .../src/FacetSource/FacetSourcePluginBase.php | 13 + .../FacetSourcePluginInterface.php | 15 +- .../contrib/facets/src/Form/FacetForm.php | 94 ++- .../facets/src/Form/FacetSettingsForm.php | 9 +- .../facets/src/Form/FacetSourceEditForm.php | 6 +- .../src/Hierarchy/HierarchyPluginBase.php | 14 +- .../facets/src/Plugin/Block/FacetBlock.php | 97 ++- .../src/Plugin/Block/FacetBlockDeriver.php | 2 +- .../facets/facet_source/SearchApiDisplay.php | 116 ++- .../src/Plugin/facets/hierarchy/DateItems.php | 3 + .../src/Plugin/facets/hierarchy/Taxonomy.php | 22 +- .../processor/ActiveWidgetOrderProcessor.php | 3 + .../facets/processor/BooleanItemProcessor.php | 3 + .../processor/CombineFacetProcessor.php | 6 +- .../facets/processor/CountLimitProcessor.php | 3 + .../processor/CountWidgetOrderProcessor.php | 3 + .../facets/processor/DateItemProcessor.php | 3 + .../processor/DependentFacetProcessor.php | 8 +- .../DisplayValueWidgetOrderProcessor.php | 3 + .../ExcludeSpecifiedItemsProcessor.php | 3 + .../processor/GranularItemProcessor.php | 3 + .../processor/HideActiveItemsProcessor.php | 3 + .../HideInactiveSiblingsProcessor.php | 4 + .../HideNonNarrowingResultProcessor.php | 3 + .../processor/HideOnlyOneItemProcessor.php | 3 + .../facets/processor/HierarchyProcessor.php | 7 +- .../facets/processor/ListItemProcessor.php | 8 +- .../RawValueWidgetOrderProcessor.php | 3 + .../ShowOnlyDeepestLevelItemsProcessor.php | 3 + .../processor/ShowSiblingsProcessor.php | 7 +- .../TermWeightWidgetOrderProcessor.php | 11 + ...ranslateEntityAggregatedFieldProcessor.php | 21 +- .../processor/TranslateEntityProcessor.php | 27 +- .../UidToUserNameCallbackProcessor.php | 16 + .../facets/processor/UrlProcessorHandler.php | 24 +- .../facets/query_type/SearchApiGranular.php | 2 +- .../facets/query_type/SearchApiRange.php | 7 +- .../facets/query_type/SearchApiString.php | 26 +- .../facets/url_processor/QueryString.php | 85 +- .../src/Plugin/facets/widget/LinksWidget.php | 8 +- .../src/Processor/ProcessorPluginBase.php | 11 +- .../src/Processor/SortProcessorPluginBase.php | 5 +- .../src/QueryType/QueryTypePluginBase.php | 2 +- .../src/QueryType/QueryTypeRangeBase.php | 7 +- .../contrib/facets/src/Result/Result.php | 44 ++ .../facets/src/Result/ResultInterface.php | 32 + .../UrlProcessor/UrlProcessorInterface.php | 8 + .../UrlProcessor/UrlProcessorPluginBase.php | 31 +- .../facets/src/Utility/FacetsDateHandler.php | 8 +- .../facets/src/Utility/FacetsUrlGenerator.php | 68 ++ .../facets/src/Widget/WidgetPluginBase.php | 6 - .../templates/facets-item-list.html.twig | 14 + .../facets_custom_widget.info.yml | 6 +- .../facets_events_test.info.yml | 6 +- .../facets_processors_collection.info.yml | 13 + .../facets_processors_collection.module | 19 + .../facets_processors_collection.services.yml | 28 + .../src/Cache/FpcCacheContext.php | 86 ++ .../facets/processor/FpcBuildProcessor.php | 59 ++ .../processor/FpcPostQueryProcessor.php | 52 ++ .../facets/processor/FpcSortProcessor.php | 60 ++ .../processor/FpcSortRandomProcessor.php | 36 + .../query_type/CacheableQueryTypePlugin.php | 27 + .../facets_query_processor.info.yml | 6 +- .../facets_query_processor.services.yml | 14 + .../facets/url_processor/DummyQuery.php | 30 +- .../views.view.search_api_test_view.yml | 349 ++++++--- .../facets_search_api_dependency.info.yml | 6 +- .../tests/src/Functional/BlockTestTrait.php | 4 +- .../Functional/BreadcrumbIntegrationTest.php | 2 +- .../HierarchicalFacetIntegrationTest.php | 2 +- .../src/Functional/IntegrationCacheTest.php | 741 ++++++++++++++++++ .../tests/src/Functional/IntegrationTest.php | 61 +- .../Functional/Rest/FacetResourceTestBase.php | 15 + .../src/Functional/UrlIntegrationTest.php | 4 +- .../Kernel/Entity/FacetFacetSourceTest.php | 3 +- .../FacetManager/DefaultFacetManagerTest.php | 324 +++++++- .../processor/DependentFacetProcessorTest.php | 34 + .../processor/ListItemProcessorTest.php | 89 ++- .../Plugin/url_processor/QueryStringTest.php | 45 +- .../Unit/Plugin/widget/LinksWidgetTest.php | 6 + .../web/modules/contrib/metatag/CHANGELOG.txt | 66 ++ .../web/modules/contrib/metatag/README.md | 4 +- .../schema/metatag.metatag_tag.schema.yml | 5 +- .../config/schema/metatag.settings.schema.yml | 3 + .../contrib/metatag/css/firehose_widget.css | 9 + .../modules/contrib/metatag/metatag.info.yml | 6 +- .../modules/contrib/metatag/metatag.install | 45 +- .../contrib/metatag/metatag.libraries.yml | 6 + .../modules/contrib/metatag/metatag.module | 67 +- .../contrib/metatag/metatag.post_update.php | 157 ++++ .../contrib/metatag/metatag.services.yml | 2 +- .../contrib/metatag/metatag.tokens.inc | 4 +- .../metatag_app_links.info.yml | 6 +- .../schema/metatag_dc.metatag_tag.schema.yml | 2 +- .../metatag/metatag_dc/metatag_dc.info.yml | 6 +- .../metatag_dc_advanced.info.yml | 6 +- .../metatag_extended_perms.info.yml | 8 +- .../tests/src/Functional/PermissionsTest.php | 4 +- .../metatag_facebook.info.yml | 6 +- .../metatag_favicons.info.yml | 6 +- .../metatag_favicons/metatag_favicons.module | 5 +- .../src/Plugin/metatag/Tag/MaskIcon.php | 14 +- .../metatag_google_cse.info.yml | 6 +- .../metatag_google_plus.info.yml | 6 +- .../src/Plugin/metatag/Tag/Author.php | 6 +- .../metatag_hreflang.info.yml | 6 +- .../metatag_hreflang/metatag_hreflang.module | 2 +- .../metatag_mobile.metatag_tag.schema.yml | 2 +- .../metatag_mobile/metatag_mobile.info.yml | 6 +- .../metatag_open_graph.metatag_tag.schema.yml | 2 +- .../metatag_open_graph.info.yml | 6 +- .../metatag_open_graph.install | 8 +- .../metatag_open_graph_products.info.yml | 6 +- .../metatag/Tag/ProductAvailability.php | 2 - .../Plugin/metatag/Tag/ProductCondition.php | 2 - .../metatag/Tag/ProductRetailerItemId.php | 1 - .../metatag_page_manager.info.yml | 6 +- ...gerTest.php => MetatagPageManagerTest.txt} | 2 +- .../metatag_pinterest.info.yml | 6 +- .../Plugin/metatag/Tag/PinterestNohover.php | 2 +- .../src/Plugin/metatag/Tag/PinterestNopin.php | 2 +- .../Plugin/metatag/Tag/PinterestNosearch.php | 2 +- .../contrib/metatag/metatag_routes/README.md | 25 + .../metatag_routes/metatag_routes.info.yml | 11 + .../metatag_routes.links.action.yml | 6 + .../metatag_routes/metatag_routes.module | 46 ++ .../metatag_routes/metatag_routes.routing.yml | 9 + .../src/Form/MetatagCustomCreateForm.php | 194 +++++ ...tatag_twitter_cards.metatag_tag.schema.yml | 2 +- .../metatag_twitter_cards.info.yml | 6 +- .../Plugin/metatag/Tag/TwitterCardsType.php | 2 +- ...etatag_verification.metatag_tag.schema.yml | 5 +- .../metatag_verification.info.yml | 6 +- .../src/Plugin/metatag/Tag/Google.php | 4 +- .../src/Plugin/metatag/Tag/Siwecos.php | 23 + .../MetatagVerificationTagsTest.php | 8 +- .../schema/metatag_views.views.schema.yml | 3 + .../metatag_views/metatag_views.info.yml | 6 +- .../metatag_views/metatag_views.module | 1 - .../src/Controller/MetatagViewsController.php | 4 +- .../MetatagViewsTranslationController.php | 6 +- .../src/Form/MetatagViewsAddForm.php | 2 +- .../src/Form/MetatagViewsEditForm.php | 9 +- .../src/MetatagViewsCachePluginManager.php | 10 + .../src/MetatagViewsCacheWrapper.php | 30 +- .../MetatagDisplayExtender.php | 13 +- .../src/Functional/MetatagViewsTokenTest.php | 21 +- .../src/Controller/MetatagController.php | 14 +- .../metatag/src/Form/MetatagDefaultsForm.php | 23 +- .../metatag/src/Form/MetatagSettingsForm.php | 32 +- .../src/MetatagDefaultsListBuilder.php | 2 +- .../contrib/metatag/src/MetatagManager.php | 196 +++-- .../contrib/metatag/src/MetatagTrimmer.php | 26 +- .../Field/FieldType/MetatagFieldItem.php | 5 +- .../Field/FieldWidget/MetatagFirehose.php | 18 +- .../Field/MetatagEntityFieldItemList.php | 1 - .../src/Plugin/metatag/Group/GroupBase.php | 2 +- .../metatag/src/Plugin/metatag/Tag/Author.php | 23 + .../Plugin/metatag/Tag/ContentLanguage.php | 6 +- .../src/Plugin/metatag/Tag/MetaNameBase.php | 4 +- .../src/Plugin/metatag/Tag/Referrer.php | 2 +- .../metatag/src/Plugin/metatag/Tag/Robots.php | 2 +- .../metatag/src/Plugin/metatag/Tag/Title.php | 25 +- .../migrate/process/d6/NodewordsEntities.php | 2 +- .../migrate/process/d7/MetatagEntities.php | 9 +- .../migrate/source/d6/NodewordsField.php | 6 + .../source/d6/NodewordsFieldInstance.php | 12 +- .../migrate/source/d7/MetatagFieldDeriver.php | 4 +- .../source/d7/MetatagFieldInstance.php | 4 +- .../source/d7/MetatagFieldInstanceDeriver.php | 6 +- .../metatag_test_custom_route.info.yml | 6 +- .../metatag_test_integration.info.yml | 6 +- .../metatag_test_tag.info.yml | 6 +- .../tests/src/Functional/DefaultTags.php | 2 +- .../src/Functional/LanguageHandlingTest.php | 116 +++ .../tests/src/Functional/MetatagAdminTest.php | 2 +- .../src/Functional/MetatagFieldTestBase.php | 6 +- .../src/Functional/MetatagFieldUserTest.php | 4 +- .../tests/src/Functional/MetatagForumTest.php | 5 +- .../src/Functional/MetatagFrontpageTest.php | 9 +- .../src/Functional/MetatagStringTest.php | 5 +- .../src/Functional/MetatagTagTypesTest.php | 7 +- .../tests/src/Functional/MetatagTagsTest.php | 15 +- .../src/Functional/MetatagTagsTestBase.php | 16 +- .../src/Functional/MetatagTokenStatus.php | 9 +- .../tests/src/Functional/MetatagTokenTest.php | 4 + .../tests/src/Functional/MetatagXssTest.php | 9 +- .../MetatagAvailableTokensTest.php | 2 +- .../tests/src/Kernel/MetatagManagerTest.php | 9 +- .../Kernel/Migrate/d7/MetatagEntitiesTest.php | 8 +- .../source/d6/NodewordsFieldInstanceTest.php | 5 +- .../source/d7/MetatagFieldInstanceTest.php | 9 +- .../tests/src/Unit/MetatagTrimmerTest.php | 4 + 222 files changed, 4660 insertions(+), 821 deletions(-) create mode 100644 frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheContexts.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheMaxAge.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheTags.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/src/Event/PostBuildFacet.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/src/Event/UrlCreated.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.info.yml create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.module create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.services.yml create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Cache/FpcCacheContext.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortRandomProcessor.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.services.yml create mode 100644 frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationCacheTest.php create mode 100644 frontend/drupal9/web/modules/contrib/metatag/css/firehose_widget.css create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag.libraries.yml rename frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/{MetatagPageManagerTest.php => MetatagPageManagerTest.txt} (99%) create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_routes/README.md create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.info.yml create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.links.action.yml create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.module create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.routing.yml create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php create mode 100644 frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Siwecos.php create mode 100644 frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Author.php create mode 100644 frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/LanguageHandlingTest.php diff --git a/frontend/drupal9/web/modules/contrib/facets/composer.json b/frontend/drupal9/web/modules/contrib/facets/composer.json index 377ab48c6..fd3469879 100644 --- a/frontend/drupal9/web/modules/contrib/facets/composer.json +++ b/frontend/drupal9/web/modules/contrib/facets/composer.json @@ -16,7 +16,7 @@ }, "license": "GPL-2.0+", "require-dev": { - "drupal/search_api": "~1.21", + "drupal/search_api": "^1.24||1.x-dev", "drupal/jquery_ui_slider": "~1.1", "drupal/jquery_ui_touch_punch": "~1.0" }, diff --git a/frontend/drupal9/web/modules/contrib/facets/config/schema/facets.facet.schema.yml b/frontend/drupal9/web/modules/contrib/facets/config/schema/facets.facet.schema.yml index 34d34667b..a3e63079e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/config/schema/facets.facet.schema.yml +++ b/frontend/drupal9/web/modules/contrib/facets/config/schema/facets.facet.schema.yml @@ -14,6 +14,12 @@ facets.facet.*: min_count: type: integer label: 'Minimum count' + missing: + type: boolean + label: 'Missing' + missing_label: + type: label + label: 'Missing label' url_alias: type: label label: 'Name of facet as used in the URL' diff --git a/frontend/drupal9/web/modules/contrib/facets/facets.info.yml b/frontend/drupal9/web/modules/contrib/facets/facets.info.yml index aa986e40f..9143b9993 100644 --- a/frontend/drupal9/web/modules/contrib/facets/facets.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/facets.info.yml @@ -1,14 +1,14 @@ name: 'Facets' type: module description: 'Faceted search interfaces that can be used on Search API searchers.' -core_version_requirement: ^9.2 || ^10.0 +core_version_requirement: ^9.3 || ^10.0 package: Search configure: entity.facets_facet.collection test_dependencies: - search_api:search_api - drupal:views -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/facets.install b/frontend/drupal9/web/modules/contrib/facets/facets.install index 8a5b4e620..a547869ea 100644 --- a/frontend/drupal9/web/modules/contrib/facets/facets.install +++ b/frontend/drupal9/web/modules/contrib/facets/facets.install @@ -8,6 +8,7 @@ use Drupal\facets\Entity\Facet; use Drupal\facets\Entity\FacetSource; use Drupal\block\Entity\Block; +use Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay; /** * Implements hook_update_dependencies(). @@ -211,6 +212,41 @@ function facets_update_8008() { * Resave facets for consistent configuration export. */ function facets_update_8009() { + // Moved to facets_update_8011(). +} + +/** + * Enable facet block caching for the views with "Search API tag or time" cache. + */ +function facets_update_8010() { + $facet_storage = \Drupal::entityTypeManager()->getStorage('facets_facet'); + $processed_views = []; + /** @var \Drupal\facets\FacetInterface $facet */ + foreach ($facet_storage->loadMultiple() as $facet) { + if ( + ($source = $facet->getFacetSource()) + && $source instanceof SearchApiDisplay + && ($view_executable = $source->getViewsDisplay()) + && !in_array($view_executable->id(), $processed_views) + && ($cache_plugin = $view_executable->getDisplay()->getPlugin('cache')) + && in_array( + $cache_plugin->getPluginId(), + ['search_api_tag', 'search_api_time'] + ) + ) { + $view_executable->save(); + $processed_views[] = $view_executable->id(); + } + } + return !empty($processed_views) + ? sprintf('Facet caching was enabled for the following views: %s.', implode(', ', $processed_views)) + : 'There are no views with search API cache plugins and facets in the same time, so nothing has been updated.'; +} + +/** + * Resave facets for consistent configuration export. + */ +function facets_update_8011() { $facets = Facet::loadMultiple(); foreach ($facets as $facet) { $facet->save(); diff --git a/frontend/drupal9/web/modules/contrib/facets/facets.module b/frontend/drupal9/web/modules/contrib/facets/facets.module index bc7d27e65..7c9ae08aa 100644 --- a/frontend/drupal9/web/modules/contrib/facets/facets.module +++ b/frontend/drupal9/web/modules/contrib/facets/facets.module @@ -10,10 +10,12 @@ use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Site\Settings; use Drupal\Core\Url; use Drupal\facets\Entity\Facet; use Drupal\facets\Entity\FacetSource; use Drupal\facets\FacetInterface; +use Drupal\facets\FacetSource\SearchApiFacetSourceInterface; use Drupal\views\Entity\View; use Drupal\Core\Entity\EntityInterface; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; @@ -202,8 +204,18 @@ function facets_entity_predelete(EntityInterface $entity) { * @see https://www.drupal.org/node/1842756 */ function facets_preprocess_facets_item_list(array &$variables) { - if ($variables['facet'] !== NULL && $variables['facet']->get('show_title') === TRUE) { - $variables['title'] = $variables['facet']->label(); + /** @var FacetInterface $facet */ + $facet = $variables['facet']; + if ($facet !== NULL) { + if ($facet->get('show_title') === TRUE) { + $variables['title'] = $facet->label(); + } + if (Settings::get('facets_debug_cacheable_metadata', FALSE) && $facet->getFacetSource() instanceof SearchApiFacetSourceInterface) { + $variables['cache_hash'] = $hash = substr(base_convert(hash('sha256', uniqid(time())), 16, 36), 0, 6); + $variables['cache_contexts'] = implode(', ', $facet->getCacheContexts()); + $variables['cache_tags'] = implode(', ', $facet->getCacheTags()); + $variables['cache_max_age'] = $facet->getCacheMaxAge(); + } } template_preprocess_item_list($variables); } diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_range_widget/facets_range_widget.info.yml b/frontend/drupal9/web/modules/contrib/facets/modules/facets_range_widget/facets_range_widget.info.yml index 9bb61f9d1..6d5624031 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_range_widget/facets_range_widget.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_range_widget/facets_range_widget.info.yml @@ -12,7 +12,7 @@ test_dependencies: - facets:facets - drupal:views -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/facets_rest.info.yml b/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/facets_rest.info.yml index 338700aa3..08d9dea4e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/facets_rest.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/facets_rest.info.yml @@ -12,7 +12,7 @@ test_dependencies: - drupal:views - drupal:rest -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/tests/rest_view/rest_view.info.yml b/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/tests/rest_view/rest_view.info.yml index 203761820..fa68af573 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/tests/rest_view/rest_view.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_rest/tests/rest_view/rest_view.info.yml @@ -12,7 +12,7 @@ dependencies: - search_api:search_api_test_db - facets:facets_rest -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_searchbox_widget/facets_searchbox_widget.info.yml b/frontend/drupal9/web/modules/contrib/facets/modules/facets_searchbox_widget/facets_searchbox_widget.info.yml index 1967ffdcd..21252ebac 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_searchbox_widget/facets_searchbox_widget.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_searchbox_widget/facets_searchbox_widget.info.yml @@ -10,7 +10,7 @@ test_dependencies: - facets:facets - drupal:views -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/facets_summary.info.yml b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/facets_summary.info.yml index 448cbde6d..c7801dd65 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/facets_summary.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/facets_summary.info.yml @@ -10,7 +10,7 @@ test_dependencies: - search_api:search_api - drupal:views -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummaryForm.php b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummaryForm.php index d244a00c3..0c2e7b432 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummaryForm.php +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummaryForm.php @@ -292,9 +292,7 @@ class FacetsSummaryForm extends EntityForm { foreach ($processors_by_stage as $stage => $processors) { /** @var \Drupal\facets\Processor\ProcessorInterface $processor */ foreach ($processors as $processor_id => $processor) { - $weight = isset($processor_settings[$processor_id]['weights'][$stage]) - ? $processor_settings[$processor_id]['weights'][$stage] - : $processor->getDefaultWeight($stage); + $weight = $processor_settings[$processor_id]['weights'][$stage] ?? $processor->getDefaultWeight($stage); if ($processor->isHidden()) { $form['processors'][$processor_id]['weights'][$stage] = [ '#type' => 'value', diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php index 2b19def23..6d71ae4ab 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Form/FacetsSummarySettingsForm.php @@ -234,7 +234,7 @@ class FacetsSummarySettingsForm extends EntityForm { // Clear Drupal cache for blocks to reflect recent changes. $this->blockManager->clearCachedDefinitions(); $facet_source_id = $form_state->getValue('facet_source_id'); - list($type,) = explode(':', $facet_source_id); + [$type] = explode(':', $facet_source_id); if ($type !== 'search_api') { return $facets_summary; } diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlockDeriver.php b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlockDeriver.php index 3848c48ae..8b0db30dc 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlockDeriver.php +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlockDeriver.php @@ -44,7 +44,7 @@ class FacetsSummaryBlockDeriver implements ContainerDeriverInterface { */ public function getDerivativeDefinition($derivative_id, $base_plugin_definition) { $derivatives = $this->getDerivativeDefinitions($base_plugin_definition); - return isset($derivatives[$derivative_id]) ? $derivatives[$derivative_id] : NULL; + return $derivatives[$derivative_id] ?? NULL; } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/facets_summary/processor/ResetFacetsProcessor.php b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/facets_summary/processor/ResetFacetsProcessor.php index 636abecc4..250eb5ff3 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/facets_summary/processor/ResetFacetsProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Plugin/facets_summary/processor/ResetFacetsProcessor.php @@ -51,9 +51,7 @@ class ResetFacetsProcessor extends ProcessorPluginBase implements BuildProcessor } $request_stack = \Drupal::requestStack(); - // Support 9.3+. - // @todo remove switch after 9.3 or greater is required. - $request = version_compare(\Drupal::VERSION, '9.3', '>=') ? $request_stack->getMainRequest() : $request_stack->getMasterRequest(); + $request = $request_stack->getMainRequest(); $query_params = $request->query->all(); // Bypass all active facets and remove them from the query parameters array. diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Processor/ProcessorPluginBase.php b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Processor/ProcessorPluginBase.php index 08175efcc..2798df33e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Processor/ProcessorPluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/src/Processor/ProcessorPluginBase.php @@ -66,7 +66,7 @@ class ProcessorPluginBase extends PluginBase implements ProcessorInterface { */ public function getDescription() { $plugin_definition = $this->getPluginDefinition(); - return isset($plugin_definition['description']) ? $plugin_definition['description'] : ''; + return $plugin_definition['description'] ?? ''; } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/tests/src/Functional/IntegrationTest.php b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/tests/src/Functional/IntegrationTest.php index ef7849b43..a716469d8 100644 --- a/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/tests/src/Functional/IntegrationTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/modules/facets_summary/tests/src/Functional/IntegrationTest.php @@ -249,6 +249,7 @@ class IntegrationTest extends FacetsTestBase { 'id' => $id, 'facet_source_id' => 'search_api:views_page__search_api_test_view__page_1', ]; + $this->drupalGet('admin/config/search/facets/add-facet-summary'); $this->submitForm($values, 'Save'); $this->assertSession()->pageTextContains('Caching of view Search API Test Fulltext search view has been disabled.'); diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Controller/FacetBlockAjaxController.php b/frontend/drupal9/web/modules/contrib/facets/src/Controller/FacetBlockAjaxController.php index daca4f7e1..264303550 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Controller/FacetBlockAjaxController.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Controller/FacetBlockAjaxController.php @@ -127,15 +127,8 @@ class FacetBlockAjaxController extends ControllerBase { $facets_blocks = array_unique($facets_blocks); $new_request = Request::create($path); - // Support 9.3+. - // @todo remove after 9.3 or greater is required. - if (class_exists(DrupalRequestStack::class)) { - $request_stack = new DrupalRequestStack(); - } - // Legacy request stack. - else { - $request_stack = new SymfonyRequestStack(); - } + $request_stack = new DrupalRequestStack(); + $processed = $this->pathProcessor->processInbound($path, $new_request); $processed_request = Request::create($processed); diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Entity/Facet.php b/frontend/drupal9/web/modules/contrib/facets/src/Entity/Facet.php index f615f35a9..f957174ef 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Entity/Facet.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Entity/Facet.php @@ -4,6 +4,9 @@ namespace Drupal\facets\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\facets\Event\GetFacetCacheContexts; +use Drupal\facets\Event\GetFacetCacheMaxAge; +use Drupal\facets\Event\GetFacetCacheTags; use Drupal\facets\Exception\Exception; use Drupal\facets\Exception\InvalidProcessorException; use Drupal\facets\Exception\InvalidQueryTypeException; @@ -41,6 +44,8 @@ use Drupal\facets\FacetInterface; * "url_alias", * "weight", * "min_count", + * "missing", + * "missing_label", * "show_only_one_result", * "field_identifier", * "facet_source_id", @@ -56,7 +61,7 @@ use Drupal\facets\FacetInterface; * "only_visible_when_facet_source_is_visible", * "processor_configs", * "empty_behavior", - * "show_title" + * "show_title", * }, * links = { * "collection" = "/admin/config/search/facets", @@ -317,6 +322,24 @@ class Facet extends ConfigEntityBase implements FacetInterface { */ protected $min_count = 1; + protected $cache_dependencies_calculated = FALSE; + + /** + * The missing parameter. + * + * @var bool + * The missing parameter. + */ + protected $missing = FALSE; + + /** + * The missing parameter label. + * + * @var string + * The missing parameter label. + */ + protected $missing_label = 'others'; + /** * Returns the widget plugin manager. * @@ -423,7 +446,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { * The loaded processors, keyed by processor ID. */ protected function loadProcessors() { - if (is_array($this->processors)) { + if (isset($this->processors) && is_array($this->processors)) { return $this->processors; } @@ -721,7 +744,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { * {@inheritdoc} */ public function getFacetSource() { - if (is_null($this->facet_source_instance) && $this->facet_source_id) { + if (!isset($this->facet_source_instance) && $this->facet_source_id) { /** @var \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_plugin_manager */ $facet_source_plugin_manager = \Drupal::service('plugin.manager.facets.facet_source'); if (!$facet_source_plugin_manager->hasDefinition($this->facet_source_id)) { @@ -758,13 +781,13 @@ class Facet extends ConfigEntityBase implements FacetInterface { } $storage = \Drupal::entityTypeManager()->getStorage('facets_facet_source'); - $source_id = str_replace(':', '__', $this->facet_source_id); - - // Load and return the facet source config object from the storage. - $facet_source = $storage->load($source_id); - if ($facet_source instanceof FacetSource) { - $this->facetSourceConfig = $facet_source; - return $this->facetSourceConfig; + if ($source_id = str_replace(':', '__', $this->facet_source_id ?? '')) { + // Load and return the facet source config object from the storage. + $facet_source = $storage->load($source_id); + if ($facet_source instanceof FacetSource) { + $this->facetSourceConfig = $facet_source; + return $this->facetSourceConfig; + } } // We didn't have a facet source config entity yet for this facet source @@ -821,6 +844,14 @@ class Facet extends ConfigEntityBase implements FacetInterface { if (in_array($result->getRawValue(), $this->active_values)) { $result->setActiveState(TRUE); } + elseif ($result->isMissing()) { + foreach ($this->active_values as $active_value) { + if (str_starts_with($active_value, '!(')) { + $result->setActiveState(TRUE); + break; + } + } + } } } } @@ -849,7 +880,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { foreach ($facet_source_plugin_manager->getDefinitions() as $name => $facet_source_definition) { if (class_exists($facet_source_definition['class']) && empty($this->facetSourcePlugins[$name])) { // Create our settings for this facet source.. - $config = isset($this->facetSourcePlugins[$name]) ? $this->facetSourcePlugins[$name] : []; + $config = $this->facetSourcePlugins[$name] ?? []; /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ $facet_source = $facet_source_plugin_manager->createInstance($name, $config); @@ -881,7 +912,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { // Filter processors by status if required. Enabled processors are those // which have settings in the processor_configs. - if ($only_enabled) { + if ($processors && $only_enabled) { $processors_settings = $this->getProcessorConfigs(); $processors = array_intersect_key($processors, $processors_settings); } @@ -973,8 +1004,10 @@ class Facet extends ConfigEntityBase implements FacetInterface { 'weights' => $processor['weights'], 'settings' => $processor['settings'], ]; - // Sort the processors so we won't have unnecessary changes. + // Sort the processors, so we won't have unnecessary changes. ksort($this->processor_configs); + + $this->cache_dependencies_calculated = FALSE; } /** @@ -983,6 +1016,8 @@ class Facet extends ConfigEntityBase implements FacetInterface { public function removeProcessor($processor_id) { unset($this->processor_configs[$processor_id]); unset($this->processors[$processor_id]); + + $this->cache_dependencies_calculated = FALSE; } /** @@ -1027,6 +1062,34 @@ class Facet extends ConfigEntityBase implements FacetInterface { return $this->min_count; } + /** + * {@inheritdoc} + */ + public function setMissing(bool $missing) { + $this->missing = $missing; + } + + /** + * {@inheritdoc} + */ + public function isMissing(): bool { + return $this->missing; + } + + /** + * {@inheritdoc} + */ + public function setMissingLabel(string $label) { + $this->missing_label = $label; + } + + /** + * {@inheritdoc} + */ + public function getMissingLabel(): string { + return $this->missing_label; + } + /** * {@inheritdoc} */ @@ -1062,6 +1125,10 @@ class Facet extends ConfigEntityBase implements FacetInterface { parent::postSave($storage, $update); if (!$update) { self::clearBlockCache(); + // Register newly created facet within its source, for the caching. + if (($source = $this->getFacetSource()) && $source->getCacheMaxAge() !== 0) { + $source->registerFacet($this); + } } } @@ -1091,12 +1158,69 @@ class Facet extends ConfigEntityBase implements FacetInterface { $container->get('plugin.manager.block')->clearCachedDefinitions(); } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + $this->calculateCacheDependencies(); + + $eventDispatcher = \Drupal::service('event_dispatcher'); + $event = new GetFacetCacheTags(parent::getCacheTags(), $this); + $eventDispatcher->dispatch($event); + $this->cacheTags = $event->getCacheTags() ?? $this->cacheTags; + + return array_values($this->cacheTags); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $this->calculateCacheDependencies(); + + $eventDispatcher = \Drupal::service('event_dispatcher'); + $event = new GetFacetCacheContexts(parent::getCacheContexts(), $this); + $eventDispatcher->dispatch($event); + $this->cacheContexts = $event->getCacheContexts() ?? $this->cacheContexts; + + return array_values($this->cacheContexts); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + $this->calculateCacheDependencies(); + + $eventDispatcher = \Drupal::service('event_dispatcher'); + $event = new GetFacetCacheMaxAge(parent::getCacheMaxAge(), $this); + $eventDispatcher->dispatch($event); + $this->cacheMaxAge = $event->getCacheMaxAge() ?? $this->cacheMaxAge; + + return $this->cacheMaxAge; + } + + protected function calculateCacheDependencies(): void { + if (!$this->cache_dependencies_calculated) { + if ($facet_source = $this->getFacetSource()) { + $this->addCacheableDependency($facet_source); + } + + foreach ($this->getProcessors() ?? [] as $processor) { + $this->addCacheableDependency($processor); + } + + $this->cache_dependencies_calculated = TRUE; + } + } + /** * Remove the facet lazy built data when the facet is serialized. */ public function __sleep() { unset($this->facet_source_instance); unset($this->processors); + return parent::__sleep(); } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Event/FacetsEvents.php b/frontend/drupal9/web/modules/contrib/facets/src/Event/FacetsEvents.php index e05b2fefc..8adbe2449 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Event/FacetsEvents.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Event/FacetsEvents.php @@ -25,4 +25,49 @@ final class FacetsEvents { */ public const ACTIVE_FILTERS_PARSED = ActiveFiltersParsed::class; + /** + * This event allows modules to change the facet links' URL if needed. + * + * @Event + * + * @see \Drupal\facets\Event\UrlCreated + */ + public const URL_CREATED = UrlCreated::class; + + /** + * This event allows modules to modify a facet after it is built. + * + * @Event + * + * @see \Drupal\facets\Event\PostBuildFacet + */ + public const POST_BUILD_FACET = PostBuildFacet::class; + + /** + * This event allows modules to change the cache contexts of a facet. + * + * @Event + * + * @see \Drupal\facets\Event\GetFacetCacheContexts + */ + public const GET_FACET_CACHE_CONTEXTS = GetFacetCacheContexts::class; + + /** + * This event allows modules to change the cache max age of a facet. + * + * @Event + * + * @see \Drupal\facets\Event\GetFacetCacheMaxAge + */ + public const GET_FACET_CACHE_MAX_AGE = GetFacetCacheMaxAge::class; + + /** + * This event allows modules to change the cache tags of a facet. + * + * @Event + * + * @see \Drupal\facets\Event\GetFacetCacheTags + */ + public const GET_FACET_CACHE_TAGS = GetFacetCacheTags::class; + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheContexts.php b/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheContexts.php new file mode 100644 index 000000000..954083355 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheContexts.php @@ -0,0 +1,72 @@ +cacheContexts = $cacheContexts; + $this->facet = $facet; + } + + /** + * Get the cache contexts. + * + * @return string[] + * The cache contexts. + */ + public function getCacheContexts(): array { + return $this->cacheContexts ?? []; + } + + /** + * Get the cache contexts. + * + * @param string[] $cacheContexts + * The cache contexts. + */ + public function setCacheContexts($cacheContexts): void { + $this->cacheContexts = $cacheContexts; + } + + /** + * Get the facet. + * + * @return \Drupal\facets\FacetInterface + * The facet. + */ + public function getFacet() { + return $this->facet; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheMaxAge.php b/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheMaxAge.php new file mode 100644 index 000000000..74d7429fe --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheMaxAge.php @@ -0,0 +1,72 @@ +cacheMaxAge = $cacheMaxAge; + $this->facet = $facet; + } + + /** + * Get the cache max age. + * + * @return int + * The cache max age. + */ + public function getCacheMaxAge(): int { + return $this->cacheMaxAge ?? 0; + } + + /** + * Get the cache max age. + * + * @param int $cacheMaxAge + * The cache max age. + */ + public function setCacheMaxAge($cacheMaxAge): void { + $this->cacheMaxAge = $cacheMaxAge; + } + + /** + * Get the facet. + * + * @return \Drupal\facets\FacetInterface + * The facet. + */ + public function getFacet() { + return $this->facet; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheTags.php b/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheTags.php new file mode 100644 index 000000000..adc75c4e0 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/src/Event/GetFacetCacheTags.php @@ -0,0 +1,72 @@ +cacheTags = $cacheTags; + $this->facet = $facet; + } + + /** + * Get the cache tags. + * + * @return string[] + * The cache tags. + */ + public function getCacheTags(): array { + return $this->cacheTags ?? []; + } + + /** + * Get the cache tags. + * + * @param string[] $cacheTags + * The cache tags. + */ + public function setCacheTags($cacheTags): void { + $this->cacheTags = $cacheTags; + } + + /** + * Get the facet. + * + * @return \Drupal\facets\FacetInterface + * The facet. + */ + public function getFacet() { + return $this->facet; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Event/PostBuildFacet.php b/frontend/drupal9/web/modules/contrib/facets/src/Event/PostBuildFacet.php new file mode 100644 index 000000000..56610ac35 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/src/Event/PostBuildFacet.php @@ -0,0 +1,56 @@ +facet = $facet; + } + + /** + * Get the facet. + * + * @return \Drupal\facets\FacetInterface + * The facet. + */ + public function getFacet(): FacetInterface { + return $this->facet; + } + + /** + * Set the facet. + * + * @param \Drupal\facets\FacetInterface $facet + * The facet. + * + * @return void + */ + public function setFacet(FacetInterface $facet): void { + $this->facet = $facet; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Event/UrlCreated.php b/frontend/drupal9/web/modules/contrib/facets/src/Event/UrlCreated.php new file mode 100644 index 000000000..b93d32aa1 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/src/Event/UrlCreated.php @@ -0,0 +1,100 @@ +url = $url; + $this->facetResult = $facetResult; + $this->facet = $facet; + } + + /** + * Get the URL. + * + * @return \Drupal\Core\Url + * The URL. + */ + public function getUrl(): Url { + return $this->url; + } + + /** + * Set the URL. + * + * @param \Drupal\Core\Url $url + * The URL to set. + */ + public function setUrl(Url $url): void { + $this->url = $url; + } + + /** + * Get the facet result. + * + * Only to be used as context, because changing this will not result in any + * changes to the final url. + * + * @return \Drupal\facets\Result\ResultInterface + * The facet result. + */ + public function getFacetResult() { + return $this->facetResult; + } + + /** + * Get the facet. + * + * Only to be used as context, because changing this will not result in any + * changes to the final url. + * + * @return \Drupal\facets\FacetInterface + * The facet. + */ + public function getFacet() { + return $this->facet; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/src/FacetInterface.php b/frontend/drupal9/web/modules/contrib/facets/src/FacetInterface.php index fb0ce155d..b92b5cad3 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/FacetInterface.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/FacetInterface.php @@ -504,4 +504,36 @@ interface FacetInterface extends ConfigEntityInterface { */ public function getMinCount(); + /** + * Sets the missing parameter. + * + * @param bool $missing + * Whether to show a missing item or not. + */ + public function setMissing(bool $missing); + + /** + * Returns the missing parameter. + * + * @return bool + * Minimum count. + */ + public function isMissing(): bool; + + /** + * Sets the missing parameter label. + * + * @param string $label + * The label. + */ + public function setMissingLabel(string $label); + + /** + * Returns the missing parameter label. + * + * @return string + * The label. + */ + public function getMissingLabel(): string; + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/FacetListBuilder.php b/frontend/drupal9/web/modules/contrib/facets/src/FacetListBuilder.php index 40e2f9c39..dc9d19158 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/FacetListBuilder.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/FacetListBuilder.php @@ -191,6 +191,7 @@ class FacetListBuilder extends DraggableListBuilder { '#type' => 'markup', '#markup' => 'Facet source', ], + 'machine_name' => ['#markup' => $facet_source['id']], 'title' => [ '#theme_wrappers' => [ 'container' => [ @@ -198,9 +199,9 @@ class FacetListBuilder extends DraggableListBuilder { ], ], '#type' => 'markup', - '#markup' => $facet_source['id'], + '#markup' => $facet_source['label'], '#wrapper_attributes' => [ - 'colspan' => 3, + 'colspan' => 2, ], ], 'operations' => [ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/FacetManager/DefaultFacetManager.php b/frontend/drupal9/web/modules/contrib/facets/src/FacetManager/DefaultFacetManager.php index b3e93dfe1..66f348482 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/FacetManager/DefaultFacetManager.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/FacetManager/DefaultFacetManager.php @@ -2,8 +2,10 @@ namespace Drupal\facets\FacetManager; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\facets\Event\PostBuildFacet; use Drupal\facets\Exception\InvalidProcessorException; use Drupal\facets\FacetInterface; use Drupal\facets\FacetSource\FacetSourcePluginManager; @@ -109,11 +111,13 @@ class DefaultFacetManager { * The facet source ID to process. */ public function alterQuery(&$query, $facetsource_id) { + $query_is_cacheable = $query instanceof RefinableCacheableDependencyInterface; /** @var \Drupal\facets\FacetInterface[] $facets */ $facets = $this->getFacetsByFacetSourceId($facetsource_id); - foreach ($facets as $facet) { + foreach ($facets as $facet) { $processors = $facet->getProcessors(); + if (isset($processors['dependent_processor'])) { $conditions = $processors['dependent_processor']->getConfiguration(); @@ -126,7 +130,6 @@ class DefaultFacetManager { } foreach ($enabled_conditions as $facet_id => $condition_settings) { - if (!isset($facets[$facet_id]) || !$processors['dependent_processor']->isConditionMet($condition_settings, $facets[$facet_id])) { // The conditions are not met anymore, remove the active items. $facet->setActiveItems([]); @@ -139,7 +142,12 @@ class DefaultFacetManager { unset($active_filters[$facet->id()]); $urlProcessor->setActiveFilters($active_filters); } - + // Add "dependend facet" cacheabillity to make sure that whenever + // its preferences will change, for instance to "negate", query + // results cache will take it to consideration. + if ($query_is_cacheable) { + $query->addCacheableDependency($facet); + } // Don't convert this facet's active items into query conditions. // Continue with the next facet. continue(2); @@ -156,6 +164,10 @@ class DefaultFacetManager { ] ); $query_type_plugin->execute(); + // Merge cache medata that gathered from facet and its processors. + if ($query_is_cacheable) { + $query->addCacheableDependency($facet); + } } } @@ -236,6 +248,7 @@ class DefaultFacetManager { } $post_query_processor->postQuery($facet); } + $this->processedFacets[$facetsource_id][$facet->id()] = $facet; } } @@ -326,7 +339,11 @@ class DefaultFacetManager { $facet->setResults($results); - $this->builtFacets[$facet->getFacetSourceId()][$facet->id()] = $facet; + $eventDispatcher = \Drupal::service('event_dispatcher'); + $event = new PostBuildFacet($facet); + $eventDispatcher->dispatch($event); + + $this->builtFacets[$facet->getFacetSourceId()][$facet->id()] = $event->getFacet(); } return $this->builtFacets[$facet->getFacetSourceId()][$facet->id()]; @@ -338,7 +355,8 @@ class DefaultFacetManager { * This method delegates to the relevant plugins to render a facet, it calls * out to a widget plugin to do the actual rendering when results are found. * When no results are found it calls out to the correct empty result plugin - * to build a render array. + * to build a render array. Renderable array will include all facet plugins + * cache metadata that were used to build this facet. * * Before doing any rendering, the processors that implement the * BuildProcessorInterface enabled on this facet will run. @@ -369,7 +387,6 @@ class DefaultFacetManager { /** @var \Drupal\facets\Widget\WidgetPluginInterface $widget */ $widget = $facet->getWidgetInstance(); $build = $widget->build($facet); - // No results behavior handling. Return a custom text or false depending on // settings. if (empty($facet->getResults())) { @@ -439,7 +456,7 @@ class DefaultFacetManager { * call returnBuiltFacet() instead. * * @param \Drupal\facets\FacetInterface $facet - * The facet to process. + * The facet to process with a collected plugins cache metadata. * * @return \Drupal\facets\FacetInterface|null * The updated facet if it exists, NULL otherwise. @@ -456,7 +473,7 @@ class DefaultFacetManager { * The facet to process. * * @return \Drupal\facets\FacetInterface - * The built facet. + * The built Facet object with a collected plugins cache metadata. */ public function returnBuiltFacet(FacetInterface $facet) { return $this->processBuild($facet); diff --git a/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourceDeriverBase.php b/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourceDeriverBase.php index 5bd3a2b73..96be2c792 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourceDeriverBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourceDeriverBase.php @@ -87,7 +87,7 @@ abstract class FacetSourceDeriverBase implements ContainerDeriverInterface { */ public function getDerivativeDefinition($derivative_id, $base_plugin_definition) { $derivatives = $this->getDerivativeDefinitions($base_plugin_definition); - return isset($derivatives[$derivative_id]) ? $derivatives[$derivative_id] : NULL; + return $derivatives[$derivative_id] ?? NULL; } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginBase.php index 9a470f8fd..20c41678a 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginBase.php @@ -2,6 +2,7 @@ namespace Drupal\facets\FacetSource; +use Drupal\Core\Cache\UncacheableDependencyTrait; use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -12,6 +13,11 @@ use Drupal\facets\QueryType\QueryTypePluginManager; /** * Defines a base class from which other facet sources may extend. * + * By default all plugins that will extend this class will disable facets + * caching mechanism. It is strongly recommended to turn it on by implementing + * own methods for the CacheableDependencyInterface interface and + * ::registerFacet() method. + * * Plugins extending this class need to define a plugin definition array through * annotation. The definition includes the following keys: * - id: The unique, system-wide identifier of the facet source. @@ -24,6 +30,7 @@ use Drupal\facets\QueryType\QueryTypePluginManager; * @see plugin_api */ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePluginInterface, ContainerFactoryPluginInterface { + use UncacheableDependencyTrait; /** * The plugin manager. @@ -153,4 +160,10 @@ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePl $this->facet->setFieldIdentifier($field_identifier); } + /** + * {@inheritdoc} + */ + public function registerFacet(FacetInterface $facet) { + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginInterface.php b/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginInterface.php index 1b9710126..af4a8cadc 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginInterface.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/FacetSource/FacetSourcePluginInterface.php @@ -3,6 +3,7 @@ namespace Drupal\facets\FacetSource; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\facets\FacetInterface; @@ -15,7 +16,7 @@ use Drupal\facets\FacetInterface; * * @see plugin_api */ -interface FacetSourcePluginInterface extends PluginFormInterface, DependentPluginInterface { +interface FacetSourcePluginInterface extends PluginFormInterface, DependentPluginInterface, CacheableDependencyInterface { /** * Fills the facet entities with results from the facet source. @@ -115,4 +116,16 @@ interface FacetSourcePluginInterface extends PluginFormInterface, DependentPlugi */ public function buildFacet(); + /** + * Register newly added facet within its source. + * + * Add facet cache tags and contexts into the facet source, to make sure that + * search results will change whenever facets will be updated. Usually can be + * achieved by adding facet entity as a cache dependency to a search results. + * + * @param \Drupal\facets\FacetInterface $facet + * Facet entity that being inserted. + */ + public function registerFacet(FacetInterface $facet); + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetForm.php b/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetForm.php index 98db6d649..ea9a24cfc 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetForm.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetForm.php @@ -426,7 +426,7 @@ class FacetForm extends EntityForm { $form['facet_settings']['empty_behavior_container']['empty_behavior_text'] = [ '#type' => 'text_format', '#title' => $this->t('Empty text'), - '#format' => isset($empty_behavior_config['text_format']) ? $empty_behavior_config['text_format'] : 'plain_text', + '#format' => $empty_behavior_config['text_format'] ?? 'plain_text', '#editor' => TRUE, '#default_value' => isset($empty_behavior_config['text_format']) ? $empty_behavior_config['text'] : '', ]; @@ -465,35 +465,34 @@ class FacetForm extends EntityForm { '#title' => $this->t('Use hierarchy'), '#default_value' => $facet->getUseHierarchy(), ]; - if (!$facet->getFacetSource() instanceof SearchApiDisplay) { - $form['facet_settings']['use_hierarchy']['#disabled'] = TRUE; - $form['facet_settings']['use_hierarchy']['#description'] = $this->t('This setting only works with Search API based facets.'); - } - else { + if ($facet->getFacetSource() instanceof SearchApiDisplay) { $processor_url = Url::fromRoute('entity.search_api_index.processors', [ 'search_api_index' => $facet->getFacetSource()->getIndex()->id(), ]); - $description = $this->t('Renders the items using hierarchy. Make sure to enable the hierarchy processor on the Search api index processor configuration for this to work as expected. If disabled all items will be flattened.', [ + $description = $this->t('Renders the items using hierarchy. Depending on the selected plugin below, make sure to enable the hierarchy processor on the Search api index processor configuration for this to work as expected. If disabled all items might be flattened.', [ ':processor-url' => $processor_url->toString(), ]); - $form['facet_settings']['use_hierarchy']['#description'] = $description; - - $hierarchy = $facet->getHierarchy(); - $options = array_map(function (HierarchyPluginBase $plugin) { - return $plugin->getPluginDefinition()['label']; - }, $facet->getHierarchies()); - $form['facet_settings']['hierarchy'] = [ - '#type' => 'select', - '#title' => $this->t('Hierarchy type'), - '#options' => $options, - '#default_value' => $hierarchy ? $hierarchy['type'] : '', - '#states' => [ - 'visible' => [ - ':input[name="facet_settings[use_hierarchy]"]' => ['checked' => TRUE], - ], - ], - ]; } + else { + $description = $this->t('Renders the items using hierarchy. Note that some of the selectable plugins below will not supports all search backends. The taxonomy plugin will only work with Search API.'); + } + $form['facet_settings']['use_hierarchy']['#description'] = $description; + + $hierarchy = $facet->getHierarchy(); + $options = array_map(function (HierarchyPluginBase $plugin) { + return $plugin->getPluginDefinition()['label']; + }, $facet->getHierarchies()); + $form['facet_settings']['hierarchy'] = [ + '#type' => 'select', + '#title' => $this->t('Hierarchy type'), + '#options' => $options, + '#default_value' => $hierarchy ? $hierarchy['type'] : '', + '#states' => [ + 'visible' => [ + ':input[name="facet_settings[use_hierarchy]"]' => ['checked' => TRUE], + ], + ], + ]; $form['facet_settings']['keep_hierarchy_parents_active'] = [ '#type' => 'checkbox', @@ -535,8 +534,9 @@ class FacetForm extends EntityForm { '#type' => 'number', '#title' => $this->t('Minimum count'), '#default_value' => $facet->getMinCount(), - '#description' => $this->t('Only display the results if there is this minimum amount of results.'), + '#description' => $this->t('Only display the results if there is this minimum amount of results. The default is "1". A setting "0" might result in a list of all possible facet items, regardless of the actual search query. But the result of a minimum count of "0" is not reliable and may very on the type of the field, the Search API backend and even between different releases or runtime configurations of the backend (for example Solr). Therefore it is highly recommended to avoid any feature that depends on a minimum count of "0".'), '#maxlength' => 4, + '#min' => 0, '#required' => TRUE, ]; if (!$facet->getFacetSource() instanceof SearchApiDisplay) { @@ -545,6 +545,30 @@ class FacetForm extends EntityForm { $form['facet_settings']['min_count']['#description'] .= $this->t('This setting only works with Search API based facets.'); } + $form['facet_settings']['missing'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show missing'), + '#default_value' => $facet->isMissing(), + '#description' => $this->t('Add a facet item that counts and selects all search results which match the current query but do not belong to any of the facet items.'), + ]; + if (!$facet->getFacetSource() instanceof SearchApiDisplay) { + $form['facet_settings']['missing']['#disabled'] = TRUE; + $form['facet_settings']['missing']['#description'] .= '
'; + $form['facet_settings']['missing']['#description'] .= $this->t('This setting only works with Search API based facets.'); + } + + $form['facet_settings']['missing_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label of missing items'), + '#description' => $this->t('Label of the facet item for which do not belong to any of the regular items.'), + '#default_value' => $facet->getMissingLabel(), + '#states' => [ + 'visible' => [ + ':input[name="facet_settings[missing]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['facet_settings']['weight'] = [ '#type' => 'number', '#title' => $this->t('Weight'), @@ -597,9 +621,7 @@ class FacetForm extends EntityForm { foreach ($processors_by_stage as $stage => $processors) { /** @var \Drupal\facets\Processor\ProcessorInterface $processor */ foreach ($processors as $processor_id => $processor) { - $weight = isset($processor_settings[$processor_id]['weights'][$stage]) - ? $processor_settings[$processor_id]['weights'][$stage] - : $processor->getDefaultWeight($stage); + $weight = $processor_settings[$processor_id]['weights'][$stage] ?? $processor->getDefaultWeight($stage); if ($processor->isHidden()) { $form['processors'][$processor_id]['weights'][$stage] = [ '#type' => 'value', @@ -682,10 +704,10 @@ class FacetForm extends EntityForm { // Validate url alias. $url_alias = $form_state->getValue(['facet_settings', 'url_alias']); - if ($url_alias == 'page') { + if ($url_alias === 'page') { $form_state->setErrorByName('url_alias', $this->t('This URL alias is not allowed.')); } - elseif (preg_match('/[^a-zA-Z0-9_~\.\-]/', $url_alias)) { + elseif (preg_match('/[^a-zA-Z0-9_~.\-]/', $url_alias)) { $form_state->setErrorByName('url_alias', $this->t('The URL alias contains characters that are not allowed.')); } } @@ -733,6 +755,18 @@ class FacetForm extends EntityForm { 'min_count', ] )); + $facet->setMissing((bool) $form_state->getValue( + [ + 'facet_settings', + 'missing', + ] + )); + $facet->setMissingLabel($form_state->getValue( + [ + 'facet_settings', + 'missing_label', + ] + )); $facet->setOnlyVisibleWhenFacetSourceIsVisible($form_state->getValue( [ 'facet_settings', diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSettingsForm.php b/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSettingsForm.php index 300014737..247ad9196 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSettingsForm.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSettingsForm.php @@ -292,7 +292,7 @@ class FacetSettingsForm extends EntityForm { $this->messenger()->addMessage($this->t('Facet %name has been updated.', ['%name' => $facet->getName()])); } - list($type,) = explode(':', $facet_source_id); + [$type] = explode(':', $facet_source_id); if ($type !== 'search_api') { return $facet; } @@ -305,9 +305,10 @@ class FacetSettingsForm extends EntityForm { if ($view->display_handler instanceof Block) { $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE); } - $view->display_handler->overrideOption('cache', ['type' => 'none']); - $view->save(); - $this->messenger()->addMessage($this->t('Caching of view %view has been disabled.', ['%view' => $view->storage->label()])); + $views_cache_type = $view->display_handler->getOption('cache')['type']; + if ($views_cache_type !== 'none') { + $this->messenger()->addMessage($this->t('You may experience issues, because %view use cache. In case you will try to turn set cache plugin to none.', ['%view' => $view->storage->label()])); + } } } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSourceEditForm.php b/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSourceEditForm.php index 93fa4cda5..ced510d85 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSourceEditForm.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Form/FacetSourceEditForm.php @@ -102,12 +102,12 @@ class FacetSourceEditForm extends EntityForm { $form['breadcrumb']['active'] = [ '#type' => 'checkbox', '#title' => $this->t('Append active facets to breadcrumb'), - '#default_value' => isset($breadcrumb_settings['active']) ? $breadcrumb_settings['active'] : FALSE, + '#default_value' => $breadcrumb_settings['active'] ?? FALSE, ]; $form['breadcrumb']['before'] = [ '#type' => 'checkbox', '#title' => $this->t('Show facet label before active facet'), - '#default_value' => isset($breadcrumb_settings['before']) ? $breadcrumb_settings['before'] : TRUE, + '#default_value' => $breadcrumb_settings['before'] ?? TRUE, '#states' => [ 'visible' => [ ':input[name="breadcrumb[active]"]' => ['checked' => TRUE], @@ -117,7 +117,7 @@ class FacetSourceEditForm extends EntityForm { $form['breadcrumb']['group'] = [ '#type' => 'checkbox', '#title' => $this->t('Group active items under same crumb (not implemented yet - now always grouping)'), - '#default_value' => isset($breadcrumb_settings['group']) ? $breadcrumb_settings['group'] : FALSE, + '#default_value' => $breadcrumb_settings['group'] ?? FALSE, '#states' => [ 'visible' => [ ':input[name="breadcrumb[active]"]' => ['checked' => TRUE], diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Hierarchy/HierarchyPluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/Hierarchy/HierarchyPluginBase.php index 6277ea1d4..5b1958e7a 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Hierarchy/HierarchyPluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Hierarchy/HierarchyPluginBase.php @@ -2,6 +2,8 @@ namespace Drupal\facets\Hierarchy; +use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\UncacheableDependencyTrait; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -9,17 +11,21 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** * A base class for plugins that implements most of the boilerplate. + * + * By default all plugins that will extend this class will disable facets + * caching mechanism. It is strongly recommended to turn it on by implementing + * own methods for the CacheableDependencyInterface interface. */ -abstract class HierarchyPluginBase extends ProcessorPluginBase implements HierarchyInterface, ContainerFactoryPluginInterface { +abstract class HierarchyPluginBase extends ProcessorPluginBase implements HierarchyInterface, ContainerFactoryPluginInterface, CacheableDependencyInterface { + + use UncacheableDependencyTrait; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $request_stack = $container->get('request_stack'); - // Support 9.3+. - // @todo remove switch after 9.3 or greater is required. - $request = version_compare(\Drupal::VERSION, '9.3', '>=') ? $request_stack->getMainRequest() : $request_stack->getMasterRequest(); + $request = $request_stack->getMainRequest(); return new static($configuration, $plugin_id, $plugin_definition, $request); } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlock.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlock.php index ac3b07655..0bda38a70 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlock.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlock.php @@ -2,10 +2,14 @@ namespace Drupal\facets\Plugin\Block; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Block\BlockBase; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\facets\FacetInterface; use Drupal\facets\FacetManager\DefaultFacetManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -33,6 +37,11 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { */ protected $facetStorage; + /** + * @var \Drupal\facets\FacetInterface + */ + protected $facet; + /** * Construct a FacetBlock instance. * @@ -70,24 +79,19 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { * {@inheritdoc} */ public function build() { - /** @var \Drupal\facets\FacetInterface $facet */ - $facet = $this->facetStorage->load($this->getDerivativeId()); - - // No need to build the facet if it does not need to be visible. - if ($facet->getOnlyVisibleWhenFacetSourceIsVisible() && - (!$facet->getFacetSource() || !$facet->getFacetSource()->isRenderedInCurrentRequest())) { - return []; - } - // Do not build the facet if the block is being previewed. if ($this->getContextValue('in_preview')) { return []; } + $facet = $this->getFacet(); + // Let the facet_manager build the facets. $build = $this->facetManager->build($facet); if (!empty($build)) { + CacheableMetadata::createFromObject($facet)->applyTo($build); + // Add extra elements from facet source, for example, ajax scripts. // @see Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ @@ -122,47 +126,45 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { return $build; } + /** + * Get facet block entity. + * + * @return \Drupal\facets\FacetInterface + * The facet entity. + */ + protected function getFacet(): FacetInterface { + if (!$this->facet) { + $this->facet = $this->facetStorage->load($this->getDerivativeId()); + } + return $this->facet; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return $this->getFacet()->getCacheTags(); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return $this->getFacet()->getCacheContexts(); + } + /** * {@inheritdoc} */ public function getCacheMaxAge() { - // A facet block cannot be cached, because it must always match the current - // search results, and Search API gets those search results from a data - // source that can be external to Drupal. Therefore it is impossible to - // guarantee that the search results are in sync with the data managed by - // Drupal. Consequently, it is not possible to cache the search results at - // all. If the search results cannot be cached, then neither can the facets, - // because they must always match. - // Fortunately, facet blocks are rendered using a lazy builder (like all - // blocks in Drupal), which means their rendering can be deferred (unlike - // the search results, which are the main content of the page, and deferring - // their rendering would mean sending an empty page to the user). This means - // that facet blocks can be rendered and sent *after* the initial page was - // loaded, by installing the BigPipe (big_pipe) module. - // - // When BigPipe is enabled, the search results will appear first, and then - // each facet block will appear one-by-one, in DOM order. - // See https://www.drupal.org/project/big_pipe. - // - // In a future version of Facet API, this could be refined, but due to the - // reliance on external data sources, it will be very difficult if not - // impossible to improve this significantly. - // - // Note: when using Drupal core's Search module instead of the contributed - // Search API module, the above limitations do not apply, but for now it is - // not considered worth the effort to optimize this just for Drupal core's - // Search. - return 0; + return $this->getFacet()->getCacheMaxAge(); } /** * {@inheritdoc} */ public function calculateDependencies() { - /** @var \Drupal\facets\FacetInterface $facet */ - $facet = $this->facetStorage->load($this->getDerivativeId()); - - return ['config' => [$facet->getConfigDependencyName()]]; + return ['config' => [$this->getFacet()->getConfigDependencyName()]]; } /** @@ -185,4 +187,19 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { return $this->t('Placeholder for the "@facet" facet', ['@facet' => $this->getDerivativeId()]); } + /** + * {@inheritDoc} + * + * Allow to render facet block if one of the following conditions are met: + * - facet is allowed to be displayed regardless of the source visibility + * - facet source is rendered in the same request as facet. + */ + public function blockAccess(AccountInterface $account) { + $facet = $this->getFacet(); + return AccessResult::allowedIf( + !$facet->getOnlyVisibleWhenFacetSourceIsVisible() + || ($facet->getFacetSource() && $facet->getFacetSource()->isRenderedInCurrentRequest()) + )->addCacheableDependency($facet); + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlockDeriver.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlockDeriver.php index 5c5205444..9444db7a8 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlockDeriver.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/Block/FacetBlockDeriver.php @@ -44,7 +44,7 @@ class FacetBlockDeriver implements ContainerDeriverInterface { */ public function getDerivativeDefinition($derivative_id, $base_plugin_definition) { $derivatives = $this->getDerivativeDefinitions($base_plugin_definition); - return isset($derivatives[$derivative_id]) ? $derivatives[$derivative_id] : NULL; + return $derivatives[$derivative_id] ?? NULL; } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/facet_source/SearchApiDisplay.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/facet_source/SearchApiDisplay.php index 57911d525..3553a1586 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/facet_source/SearchApiDisplay.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/facet_source/SearchApiDisplay.php @@ -3,6 +3,8 @@ namespace Drupal\facets\Plugin\facets\facet_source; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Extension\ModuleHandler; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -24,6 +26,13 @@ use Symfony\Component\HttpFoundation\Request; /** * Provides a facet source based on a Search API display. * + * @todo The support for non views displays might be removed from facets 3.x and + * moved into a sub or contributed module. So this class needs to become + * something like "SearchApiViewsDisplay" and a "SearchApiCustomDisplay" + * plugin needs to be provided by the sub or contributed module. At the + * moment we have switches within this class for example to get the cache + * metadata. Those need to be removed. + * * @FacetsFacetSource( * id = "search_api", * deriver = "Drupal\facets\Plugin\facets\facet_source\SearchApiDisplayDeriver" @@ -31,6 +40,14 @@ use Symfony\Component\HttpFoundation\Request; */ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSourceInterface { + /** + * List of Search API cache plugins that works with Facets cache system. + */ + const CACHEABLE_PLUGINS = [ + 'search_api_tag', + 'search_api_time', + ]; + /** * The search index the query should is executed on. * @@ -115,9 +132,7 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo $container->get('plugin.manager.facets.query_type'), $container->get('search_api.query_helper'), $container->get('plugin.manager.search_api.display'), - // Support 9.3+. - // @todo remove switch after 9.3 or greater is required. - version_compare(\Drupal::VERSION, '9.3', '>=') ? $request_stack->getMainRequest() : $request_stack->getMasterRequest(), + $request_stack->getMainRequest(), $container->get('module_handler') ); } @@ -159,11 +174,12 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo // If there are no results, we can check the Search API Display plugin has // configuration for views. If that configuration exists, we can execute - // that view and try to use it's results. + // that view and try to use its results. $display_definition = $this->getDisplay()->getPluginDefinition(); if ($results === NULL && isset($display_definition['view_id'])) { $view = Views::getView($display_definition['view_id']); $view->setDisplay($display_definition['view_display']); + $view->preExecute(); $view->execute(); $results = $this->searchApiQueryHelper->getResults($search_id); } @@ -187,7 +203,7 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo $configuration = [ 'query' => $results->getQuery(), 'facet' => $facet, - 'results' => isset($facet_results[$facet->getFieldIdentifier()]) ? $facet_results[$facet->getFieldIdentifier()] : [], + 'results' => $facet_results[$facet->getFieldIdentifier()] ?? [], ]; // Get the Facet Specific Query Type so we can process the results @@ -418,4 +434,94 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo } } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + if ($views_display = $this->getViewsDisplay()) { + return $views_display + ->getDisplay() + ->getCacheMetadata() + ->getCacheContexts(); + } + + // Custom display implementations should provide their own cache metadata. + $display = $this->getDisplay(); + if ($display instanceof CacheableDependencyInterface) { + return $display->getCacheContexts(); + } + + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + if ($views_display = $this->getViewsDisplay()) { + return Cache::mergeTags( + $views_display->getDisplay()->getCacheMetadata()->getCacheTags(), + $views_display->getCacheTags() + ); + } + + // Custom display implementations should provide their own cache metadata. + $display = $this->getDisplay(); + if ($display instanceof CacheableDependencyInterface) { + return $display->getCacheTags(); + } + + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + if ($views_display = $this->getViewsDisplay()) { + $cache_plugin = $views_display->getDisplay()->getPlugin('cache'); + return Cache::mergeMaxAges( + $views_display->getDisplay()->getCacheMetadata()->getCacheMaxAge(), + $cache_plugin ? $cache_plugin->getCacheMaxAge() : 0 + ); + } + + // Custom display implementations should provide their own cache metadata. + $display = $this->getDisplay(); + if ($display instanceof CacheableDependencyInterface) { + return $display->getCacheMaxAge(); + } + + // Caching is not supported. + return 0; + } + + /** + * {@inheritDoc} + * + * Alter views view cache metadata: + * - When view being re-saved it will collect all cache metadata from its + * plugins, including cache plugin. + * - Search API cache plugin will pre-execute the query and collect cacheable + * metadata from all facets and will pass it to the view. + * + * View will use collected cache tags to invalidate search results. And cache + * context provided by the facet to vary results. + * + * @see \Drupal\views\Plugin\views\display\DisplayPluginBase::calculateCacheMetadata() + * @see \Drupal\search_api\Plugin\views\cache\SearchApiCachePluginTrait::alterCacheMetadata() + * @see \Drupal\facets\FacetManager\DefaultFacetManager::alterQuery() + */ + public function registerFacet(FacetInterface $facet) { + if ( + // On the config-sync or site install view will already have all required + // cache tags, so don't react if it's already there. + !in_array('config:' . $facet->getConfigDependencyName(), $this->getCacheTags()) + // Re-save it only if we know that views cache plugin works with facets. + && in_array($this->getViewsDisplay()->getDisplay()->getOption('cache')['type'], static::CACHEABLE_PLUGINS) + ) { + $this->getViewsDisplay()->save(); + } + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/DateItems.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/DateItems.php index 7af541065..30b229bf5 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/DateItems.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/DateItems.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\hierarchy; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\Hierarchy\HierarchyPluginBase; /** @@ -15,6 +16,8 @@ use Drupal\facets\Hierarchy\HierarchyPluginBase; */ class DateItems extends HierarchyPluginBase { + use UnchangingCacheableDependencyTrait; + /** * Static cache for the parents. * diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/Taxonomy.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/Taxonomy.php index 129c0e0ff..68266210e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/Taxonomy.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/hierarchy/Taxonomy.php @@ -2,8 +2,10 @@ namespace Drupal\facets\Plugin\facets\hierarchy; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\facets\Hierarchy\HierarchyPluginBase; +use Drupal\taxonomy\TermInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -96,7 +98,7 @@ class Taxonomy extends HierarchyPluginBase { $current_tid = $parent; $parents[$id][] = $parent; } - return isset($parents[$id]) ? $parents[$id] : []; + return $parents[$id] ?? []; } /** @@ -152,6 +154,17 @@ class Taxonomy extends HierarchyPluginBase { if (!$topLevelTerms) { /** @var \Drupal\taxonomy\Entity\Term $term */ $term = $this->getTermStorage()->load($id); + + // Issue #3260603: + // Due to a bug in core + // https://www.drupal.org/project/drupal/issues/2723323 + // it may happen that a taxonomy term is still referenced in a field, + // even though the term has been deleted. + // Not checking the term is empty produces a fatal error. + if (!$term instanceof TermInterface) { + continue; + } + $topLevelTerms = array_map(function ($term) { return $term->tid; }, $this->getTermStorage()->loadTree($term->bundle(), 0, 1)); @@ -197,4 +210,11 @@ class Taxonomy extends HierarchyPluginBase { return $this->termParents[$tid] = reset($parents)->id(); } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return Cache::mergeTags(parent::getCacheTags(), ['taxonomy_term:list']); + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ActiveWidgetOrderProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ActiveWidgetOrderProcessor.php index 149e18bea..fc3d32cc5 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ActiveWidgetOrderProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ActiveWidgetOrderProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\Processor\SortProcessorPluginBase; use Drupal\facets\Processor\SortProcessorInterface; use Drupal\facets\Result\Result; @@ -21,6 +22,8 @@ use Drupal\facets\Result\Result; */ class ActiveWidgetOrderProcessor extends SortProcessorPluginBase implements SortProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/BooleanItemProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/BooleanItemProcessor.php index 998fa4e3c..070d39f58 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/BooleanItemProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/BooleanItemProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\TypedData\ComplexDataDefinitionInterface; use Drupal\facets\FacetInterface; use Drupal\Core\Form\FormStateInterface; @@ -23,6 +24,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class BooleanItemProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CombineFacetProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CombineFacetProcessor.php index 40603ebf4..e1dde3a5e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CombineFacetProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CombineFacetProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -25,6 +26,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class CombineFacetProcessor extends ProcessorPluginBase implements BuildProcessorInterface, ContainerFactoryPluginInterface { + use UnchangingCacheableDependencyTrait; + /** * The language manager. * @@ -148,7 +151,6 @@ class CombineFacetProcessor extends ProcessorPluginBase implements BuildProcesso /** @var \Drupal\facets\Entity\Facet $current_facet */ $current_facet = $this->facetStorage->load($facet_id); $current_facet = $this->facetsManager->returnBuiltFacet($current_facet); - switch ($settings['mode']) { case 'union': $results = $keyed_results + $current_facet->getResultsKeyedByRawValue(); @@ -162,6 +164,8 @@ class CombineFacetProcessor extends ProcessorPluginBase implements BuildProcesso $results = array_intersect_key($keyed_results, $current_facet->getResultsKeyedByRawValue()); break; } + // Pass build processor information into current facet. + $facet->addCacheableDependency($current_facet); } return $results; diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountLimitProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountLimitProcessor.php index 2290c8076..059a07d34 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountLimitProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountLimitProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -21,6 +22,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class CountLimitProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountWidgetOrderProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountWidgetOrderProcessor.php index 85257ae2c..36a8c10c2 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountWidgetOrderProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/CountWidgetOrderProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\Processor\SortProcessorPluginBase; use Drupal\facets\Processor\SortProcessorInterface; use Drupal\facets\Result\Result; @@ -21,6 +22,8 @@ use Drupal\facets\Result\Result; */ class CountWidgetOrderProcessor extends SortProcessorPluginBase implements SortProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DateItemProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DateItemProcessor.php index 0922275ee..fca90e9da 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DateItemProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DateItemProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -22,6 +23,8 @@ use Drupal\facets\Plugin\facets\query_type\SearchApiDate; */ class DateItemProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DependentFacetProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DependentFacetProcessor.php index 7fea4a74a..4027b8a93 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DependentFacetProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DependentFacetProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -25,6 +26,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class DependentFacetProcessor extends ProcessorPluginBase implements BuildProcessorInterface, ContainerFactoryPluginInterface { + use UnchangingCacheableDependencyTrait; + /** * The language manager. * @@ -125,6 +128,7 @@ class DependentFacetProcessor extends ProcessorPluginBase implements BuildProces '#title' => $this->t('Values'), '#type' => 'textfield', '#default_value' => empty($config[$facet->id()]['values']) ? '' : $config[$facet->id()]['values'], + '#description' => $this->t('Enter a comma-separated list of values. Example: value1, value2, value3'), '#states' => [ 'visible' => [ ':input[name="facet_settings[' . $this->getPluginId() . '][settings][' . $facet->id() . '][enable]"]' => ['checked' => TRUE], @@ -168,10 +172,10 @@ class DependentFacetProcessor extends ProcessorPluginBase implements BuildProces } foreach ($enabled_conditions as $facet_id => $condition_settings) { - /** @var \Drupal\facets\Entity\Facet $current_facet */ $current_facet = $this->facetStorage->load($facet_id); $current_facet = $this->facetsManager->returnBuiltFacet($current_facet); + $facet->addCacheableDependency($current_facet); if (!$this->isConditionMet($condition_settings, $current_facet)) { return []; @@ -202,7 +206,7 @@ class DependentFacetProcessor extends ProcessorPluginBase implements BuildProces if ($condition_settings['condition'] === 'values') { $return = FALSE; - $values = explode(',', $condition_settings['values']); + $values = array_map('trim', explode(',', $condition_settings['values'])); foreach ($facet->getActiveItems() as $value) { if (in_array($value, $values)) { $return = TRUE; diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DisplayValueWidgetOrderProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DisplayValueWidgetOrderProcessor.php index 81dee8e3f..c1485806d 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DisplayValueWidgetOrderProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/DisplayValueWidgetOrderProcessor.php @@ -3,6 +3,7 @@ namespace Drupal\facets\Plugin\facets\processor; use Drupal\Component\Transliteration\TransliterationInterface; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\facets\Processor\SortProcessorInterface; use Drupal\facets\Processor\SortProcessorPluginBase; @@ -24,6 +25,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class DisplayValueWidgetOrderProcessor extends SortProcessorPluginBase implements SortProcessorInterface, ContainerFactoryPluginInterface { + use UnchangingCacheableDependencyTrait; + /** * The transliteration service. * diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ExcludeSpecifiedItemsProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ExcludeSpecifiedItemsProcessor.php index ff88c5bd2..dc9322b14 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ExcludeSpecifiedItemsProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ExcludeSpecifiedItemsProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -21,6 +22,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class ExcludeSpecifiedItemsProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/GranularItemProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/GranularItemProcessor.php index c5589edf5..293847aab 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/GranularItemProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/GranularItemProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -21,6 +22,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class GranularItemProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideActiveItemsProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideActiveItemsProcessor.php index d26e94c33..3612e93d2 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideActiveItemsProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideActiveItemsProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -20,6 +21,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class HideActiveItemsProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideInactiveSiblingsProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideInactiveSiblingsProcessor.php index 644767ab9..2028cc2fd 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideInactiveSiblingsProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideInactiveSiblingsProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -20,6 +21,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class HideInactiveSiblingsProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ @@ -29,6 +32,7 @@ class HideInactiveSiblingsProcessor extends ProcessorPluginBase implements Build if ($facet->getUseHierarchy()) { $hierarchy = $facet->getHierarchyInstance(); + $facet->addCacheableDependency($hierarchy); if (!$facet->getKeepHierarchyParentsActive()) { $parents_of_active_items = []; diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideNonNarrowingResultProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideNonNarrowingResultProcessor.php index d8d817a95..010a73775 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideNonNarrowingResultProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideNonNarrowingResultProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -20,6 +21,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class HideNonNarrowingResultProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideOnlyOneItemProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideOnlyOneItemProcessor.php index 375ed46e8..5c087659a 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideOnlyOneItemProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HideOnlyOneItemProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -20,6 +21,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class HideOnlyOneItemProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HierarchyProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HierarchyProcessor.php index 6350d1188..879a4475f 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HierarchyProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/HierarchyProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -21,6 +22,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class HierarchyProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * An array of all entity ids in the active resultset which are a child. * @@ -38,8 +41,10 @@ class HierarchyProcessor extends ProcessorPluginBase implements BuildProcessorIn foreach ($results as $result) { $keyed_results[$result->getRawValue()] = $result; } + $hierarchy = $facet->getHierarchyInstance(); + $facet->addCacheableDependency($hierarchy); - $parent_groups = $facet->getHierarchyInstance()->getChildIds(array_keys($keyed_results)); + $parent_groups = $hierarchy->getChildIds(array_keys($keyed_results)); $keyed_results = $this->buildHierarchicalTree($keyed_results, $parent_groups); // Remove children from primary level. diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ListItemProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ListItemProcessor.php index 2afc99a45..ae0f9951b 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ListItemProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ListItemProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; @@ -30,6 +31,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class ListItemProcessor extends ProcessorPluginBase implements BuildProcessorInterface, ContainerFactoryPluginInterface { + use UnchangingCacheableDependencyTrait; + /** * The config manager. * @@ -133,12 +136,13 @@ class ListItemProcessor extends ProcessorPluginBase implements BuildProcessorInt $fieldDefinition->getTargetEntityTypeId(), $fieldDefinition->getName() ); + if ($fieldDefinition instanceof BaseFieldDefinition) { if (isset($base_fields[$field->getPropertyPath()])) { $field = $base_fields[$field->getPropertyPath()]; } - } + // Diggs down to get the referenced field the entity reference is based // on. elseif ($this->configManager->loadConfigEntityByName($referenced_entity_name) !== NULL) { @@ -148,6 +152,7 @@ class ListItemProcessor extends ProcessorPluginBase implements BuildProcessorInt } if ($field instanceof FieldStorageDefinitionInterface) { if ($field->getName() !== 'type') { + $facet->addCacheableDependency($field); $allowed_values = options_allowed_values($field); if (!empty($allowed_values)) { return $this->overWriteDisplayValues($results, $allowed_values); @@ -158,6 +163,7 @@ class ListItemProcessor extends ProcessorPluginBase implements BuildProcessorInt // bundle field. $list_bundles = $this->entityTypeBundleInfo->getBundleInfo($entity); if (!empty($list_bundles)) { + $facet->addCacheTags(['entity_bundles']); foreach ($list_bundles as $key => $bundle) { $allowed_values[$key] = $bundle['label']; } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/RawValueWidgetOrderProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/RawValueWidgetOrderProcessor.php index c8e45e879..3067e03c8 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/RawValueWidgetOrderProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/RawValueWidgetOrderProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\Processor\SortProcessorPluginBase; use Drupal\facets\Processor\SortProcessorInterface; use Drupal\facets\Result\Result; @@ -20,6 +21,8 @@ use Drupal\facets\Result\Result; */ class RawValueWidgetOrderProcessor extends SortProcessorPluginBase implements SortProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowOnlyDeepestLevelItemsProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowOnlyDeepestLevelItemsProcessor.php index 1b51e28fa..c0467f9c5 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowOnlyDeepestLevelItemsProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowOnlyDeepestLevelItemsProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; use Drupal\facets\Processor\ProcessorPluginBase; @@ -20,6 +21,8 @@ use Drupal\facets\Processor\ProcessorPluginBase; */ class ShowOnlyDeepestLevelItemsProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowSiblingsProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowSiblingsProcessor.php index 49d0295bf..1bbd8ae99 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowSiblingsProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/ShowSiblingsProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -22,6 +23,8 @@ use Drupal\facets\Result\Result; */ class ShowSiblingsProcessor extends ProcessorPluginBase implements BuildProcessorInterface { + use UnchangingCacheableDependencyTrait; + /** * {@inheritdoc} */ @@ -31,7 +34,9 @@ class ShowSiblingsProcessor extends ProcessorPluginBase implements BuildProcesso $rawValues = array_map(function ($result) { return $result->getRawValue(); }, $results); - foreach ($facet->getHierarchyInstance()->getSiblingIds($rawValues, $facet->getActiveItems(), $this->getConfiguration()['show_parent_siblings']) as $siblingId) { + $hierarchy = $facet->getHierarchyInstance(); + $facet->addCacheableDependency($hierarchy); + foreach ($hierarchy->getSiblingIds($rawValues, $facet->getActiveItems(), $this->getConfiguration()['show_parent_siblings']) as $siblingId) { $results[] = new Result($facet, $siblingId, $siblingId, 0); } } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php index 29938931c..bedfb2ba2 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TermWeightWidgetOrderProcessor.php @@ -2,6 +2,8 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\TypedData\ComplexDataDefinitionInterface; @@ -25,6 +27,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class TermWeightWidgetOrderProcessor extends SortProcessorPluginBase implements ContainerFactoryPluginInterface { + use UnchangingCacheableDependencyTrait; + /** * The entity type manager. * @@ -126,4 +130,11 @@ class TermWeightWidgetOrderProcessor extends SortProcessorPluginBase implements return FALSE; } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return Cache::mergeTags(parent::getCacheTags(), ['taxonomy_term_list']); + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php index efc4c188c..b49c8d2b4 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityAggregatedFieldProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\Cache; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Config\ConfigManagerInterface; @@ -167,7 +168,7 @@ class TranslateEntityAggregatedFieldProcessor extends ProcessorPluginBase implem if ($entity instanceof TranslatableInterface && $entity->hasTranslation($language_interface->getId())) { $entity = $entity->getTranslation($language_interface->getId()); } - + $facet->addCacheableDependency($entity); // Overwrite the result's display value. $results[$i]->setDisplayValue($entity->label()); } @@ -179,6 +180,7 @@ class TranslateEntityAggregatedFieldProcessor extends ProcessorPluginBase implem // bundle field. foreach ($entity_type_ids as $entity) { $list_bundles = $this->entityTypeBundleInfo->getBundleInfo($entity); + $facet->addCacheTags(['entity_bundles']); if (!empty($list_bundles)) { foreach ($list_bundles as $key => $bundle) { $allowed_values[$key] = $bundle['label']; @@ -231,4 +233,21 @@ class TranslateEntityAggregatedFieldProcessor extends ProcessorPluginBase implem return FALSE; } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return Cache::mergeContexts(parent::getCacheContexts(), ['languages:language_interface']); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // This will work unless the Search API Query uses "wrong" caching. Ideally + // we would set a cache tag to invalidate the cache whenever a translatable + // entity is added or changed. But there's no tag in drupal yet. + return Cache::PERMANENT; + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityProcessor.php index dff609fa5..f1ea8d70e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/TranslateEntityProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -116,7 +117,12 @@ class TranslateEntityProcessor extends ProcessorPluginBase implements BuildProce // Loop over all results. foreach ($results as $i => $result) { if (!isset($entities[$ids[$i]])) { - unset($results[$i]); + if ($result->isMissing()) { + $results[$i]->setDisplayValue($facet->getMissingLabel()); + } + else { + unset($results[$i]); + } continue; } @@ -128,7 +134,7 @@ class TranslateEntityProcessor extends ProcessorPluginBase implements BuildProce if ($entity instanceof TranslatableInterface && $entity->hasTranslation($language_interface->getId())) { $entity = $entity->getTranslation($language_interface->getId()); } - + $facet->addCacheableDependency($entity); // Overwrite the result's display value. $results[$i]->setDisplayValue($entity->label()); } @@ -158,4 +164,21 @@ class TranslateEntityProcessor extends ProcessorPluginBase implements BuildProce return FALSE; } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return Cache::mergeContexts(parent::getCacheContexts(), ['languages:language_interface']); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // This will work unless the Search API Query uses "wrong" caching. Ideally + // we would set a cache tag to invalidate the cache whenever a translatable + // entity is added or changed. But there's no tag in drupal yet. + return Cache::PERMANENT; + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php index 254037dab..ecf6e9a12 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UidToUserNameCallbackProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\Cache; use Drupal\Core\TypedData\ComplexDataDefinitionInterface; use Drupal\Core\TypedData\DataReferenceDefinitionInterface; use Drupal\facets\FacetInterface; @@ -34,6 +35,7 @@ class UidToUserNameCallbackProcessor extends ProcessorPluginBase implements Buil /** @var \Drupal\user\Entity\User $user */ if (($user = User::load($result->getRawValue())) !== NULL) { $result->setDisplayValue($user->getDisplayName()); + $facet->addCacheableDependency($user); $usernames[] = $result; } } @@ -68,4 +70,18 @@ class UidToUserNameCallbackProcessor extends ProcessorPluginBase implements Buil return FALSE; } + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return Cache::mergeTags(parent::getCacheTags(), ['user_list']); + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UrlProcessorHandler.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UrlProcessorHandler.php index 92f865566..4c4ce1226 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UrlProcessorHandler.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/processor/UrlProcessorHandler.php @@ -2,6 +2,7 @@ namespace Drupal\facets\Plugin\facets\processor; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\facets\Exception\InvalidProcessorException; use Drupal\facets\FacetInterface; use Drupal\facets\Processor\BuildProcessorInterface; @@ -12,7 +13,7 @@ use Drupal\facets\Processor\ProcessorPluginBase; * The URL processor handler triggers the actual url processor. * * The URL processor handler allows managing the weight of the actual URL - * processor per Facet. This handler will trigger the actual. + * processor per Facet. This handler will trigger the actual. * * @FacetsUrlProcessor, which can be configured on the Facet source. * @@ -82,4 +83,25 @@ class UrlProcessorHandler extends ProcessorPluginBase implements BuildProcessorI $this->processor->setActiveItems($facet); } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return CacheableMetadata::createFromObject($this->processor)->getCacheTags(); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return CacheableMetadata::createFromObject($this->processor)->getCacheContexts(); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return CacheableMetadata::createFromObject($this->processor)->getCacheMaxAge(); + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiGranular.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiGranular.php index be77f898e..8f29a6f60 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiGranular.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiGranular.php @@ -48,7 +48,7 @@ class SearchApiGranular extends QueryTypeRangeBase { $query_operator = $this->facet->getQueryOperator(); $facet_results = []; foreach ($this->results as $result) { - if ($result['count'] || $query_operator == 'or') { + if ($result['count'] || $query_operator === 'or') { $result_filter = trim($result['filter'], '"'); $facet_results[] = new Result($this->facet, $result_filter, $result_filter, $result['count']); } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiRange.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiRange.php index e3863b6c8..da17bd0fe 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiRange.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiRange.php @@ -58,12 +58,17 @@ class SearchApiRange extends QueryTypePluginBase { if (!empty($this->results)) { $facet_results = []; foreach ($this->results as $result) { - if ($result['count'] || $query_operator == 'or') { + if ($result['count'] || $query_operator === 'or') { $count = $result['count']; while (is_array($result['filter'])) { $result['filter'] = current($result['filter']); } $result_filter = trim($result['filter'], '"'); + if ($result_filter === 'NULL' || $result_filter === '') { + // "Missing" facet items could not be handled in ranges. + continue; + } + $result = new Result($this->facet, $result_filter, $result_filter, $count); $facet_results[] = $result; } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiString.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiString.php index edbf66cf4..e9f693120 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiString.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/query_type/SearchApiString.php @@ -47,7 +47,17 @@ class SearchApiString extends QueryTypePluginBase { if (count($active_items)) { $filter = $query->createConditionGroup($operator, ['facet:' . $field_identifier]); foreach ($active_items as $value) { - $filter->addCondition($this->facet->getFieldIdentifier(), $value, $exclude ? '<>' : '='); + if (str_starts_with($value, '!(')) { + /** @var \Drupal\facets\UrlProcessor\UrlProcessorInterface $urlProcessor */ + $urlProcessor = $this->facet->getProcessors()['url_processor_handler']->getProcessor(); + foreach (explode($urlProcessor->getDelimiter(), substr($value, 2, -1)) as $missing_value) { + // Note that $exclude needs to be inverted for "missing". + $filter->addCondition($this->facet->getFieldIdentifier(), $missing_value, !$exclude ? '<>' : '='); + } + } + else { + $filter->addCondition($this->facet->getFieldIdentifier(), $value, $exclude ? '<>' : '='); + } } $query->addConditionGroup($filter); } @@ -63,8 +73,8 @@ class SearchApiString extends QueryTypePluginBase { if (!empty($this->results)) { $facet_results = []; foreach ($this->results as $result) { - if ($result['count'] || $query_operator == 'or') { - $result_filter = $result['filter']; + if ($result['count'] || $query_operator === 'or') { + $result_filter = $result['filter'] ?? ''; if ($result_filter[0] === '"') { $result_filter = substr($result_filter, 1); } @@ -73,10 +83,16 @@ class SearchApiString extends QueryTypePluginBase { } $count = $result['count']; $result = new Result($this->facet, $result_filter, $result_filter, $count); - $facet_results[] = $result; + $result->setMissing($this->facet->isMissing() && $result_filter === '!'); + $facet_results[$result_filter] = $result; } } - $this->facet->setResults($facet_results); + + if (isset($facet_results['!']) && $facet_results['!']->isMissing()) { + $facet_results['!']->setMissingFilters(array_keys($facet_results)); + } + + $this->facet->setResults(array_values($facet_results)); } return $this->facet; diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/url_processor/QueryString.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/url_processor/QueryString.php index bedb7445f..218d22e0a 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/url_processor/QueryString.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/url_processor/QueryString.php @@ -2,17 +2,18 @@ namespace Drupal\facets\Plugin\facets\url_processor; +use Drupal\Core\Cache\UnchangingCacheableDependencyTrait; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\EventSubscriber\MainContentViewSubscriber; -use Drupal\Core\Url; use Drupal\facets\Event\ActiveFiltersParsed; use Drupal\facets\Event\QueryStringCreated; +use Drupal\facets\Event\UrlCreated; use Drupal\facets\FacetInterface; use Drupal\facets\UrlProcessor\UrlProcessorPluginBase; +use Drupal\facets\Utility\FacetsUrlGenerator; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; /** * Query string URL processor. @@ -25,6 +26,8 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException; */ class QueryString extends UrlProcessorPluginBase { + use UnchangingCacheableDependencyTrait; + /** * A string of how to represent the facet in the url. * @@ -39,12 +42,20 @@ class QueryString extends UrlProcessorPluginBase { */ protected $eventDispatcher; + /** + * The URL generator. + * + * @var \Drupal\facets\Utility\FacetsUrlGenerator + */ + protected $urlGenerator; + /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $eventDispatcher) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $eventDispatcher, FacetsUrlGenerator $urlGenerator) { parent::__construct($configuration, $plugin_id, $plugin_definition, $request, $entity_type_manager); $this->eventDispatcher = $eventDispatcher; + $this->urlGenerator = $urlGenerator; $this->initializeActiveFilters(); } @@ -58,7 +69,8 @@ class QueryString extends UrlProcessorPluginBase { $plugin_definition, $container->get('request_stack')->getCurrentRequest(), $container->get('entity_type.manager'), - $container->get('event_dispatcher') + $container->get('event_dispatcher'), + $container->get('facets.utility.url_generator') ); } @@ -84,6 +96,8 @@ class QueryString extends UrlProcessorPluginBase { // Set the url alias from the facet object. $this->urlAlias = $facet->getUrlAlias(); + // In case of a view page display, the facet source has a path, If the + // source is a block, the path is null. $facet_source_path = $facet->getFacetSource()->getPath(); $request = $this->getRequestByFacetSourcePath($facet_source_path); $requestUrl = $this->getUrlForRequest($facet_source_path, $request); @@ -108,9 +122,14 @@ class QueryString extends UrlProcessorPluginBase { $this->buildUrls($facet, $children); } + $filter_missing = ''; if ($result->getRawValue() === NULL) { $filter_string = NULL; } + elseif ($result->isMissing()) { + $filter_missing = $this->urlAlias . $this->getSeparator() . '!('; + $filter_string = $filter_missing . implode($this->getDelimiter(), $result->getMissingFilters()) . ')'; + } else { $filter_string = $this->urlAlias . $this->getSeparator() . $result->getRawValue(); } @@ -121,11 +140,11 @@ class QueryString extends UrlProcessorPluginBase { // If the value is active, remove the filter string from the parameters. if ($result->isActive()) { foreach ($filter_params as $key => $filter_param) { - if ($filter_param == $filter_string) { + if ($filter_param === $filter_string || ($filter_missing && str_starts_with($filter_param, $filter_missing))) { unset($filter_params[$key]); } } - if ($facet->getUseHierarchy()) { + if ($facet->getUseHierarchy() && !$result->isMissing()) { $id = $result->getRawValue(); // Disable child filters. @@ -197,7 +216,7 @@ class QueryString extends UrlProcessorPluginBase { } } - // Allow other modules to alter the result url built. + // Allow other modules to alter the result url query string built. $event = new QueryStringCreated($result_get_params, $filter_params, $result, $this->activeFilters, $facet); $this->eventDispatcher->dispatch($event); $filter_params = $event->getFilterParameters(); @@ -216,14 +235,18 @@ class QueryString extends UrlProcessorPluginBase { // See https://www.drupal.org/node/2898189. unset($new_url_params['page']); - // Remove core wrapper format (e.g. render-as-ajax-response) paremeters. + // Remove core wrapper format (e.g. render-as-ajax-response) parameters. unset($new_url_params[MainContentViewSubscriber::WRAPPER_FORMAT]); // Set the new url parameters. $url->setOption('query', $new_url_params); } - $result->setUrl($url); + // Allow other modules to alter the result url built. + $event = new UrlCreated($url, $result, $facet); + $this->eventDispatcher->dispatch($event); + + $result->setUrl($event->getUrl()); } // Restore page parameter again. See https://www.drupal.org/node/2726455. @@ -266,9 +289,8 @@ class QueryString extends UrlProcessorPluginBase { /** * Gets the URL object for a request. * - * This method statically caches the URL object for a request based on the - * facet source path. This reduces subsequent calls to the processor from - * having to regenerate the URL object. + * This method delegates to the URL generator service. But we keep it for + * backward compatibility for custom implementations that extend this class. * * @param string $facet_source_path * The facet source path. @@ -279,37 +301,7 @@ class QueryString extends UrlProcessorPluginBase { * The URL. */ protected function getUrlForRequest($facet_source_path, Request $request) { - /** @var \Drupal\Core\Url[] $requestUrlsByPath */ - $requestUrlsByPath = &drupal_static(__CLASS__ . __FUNCTION__, []); - - if (array_key_exists($facet_source_path, $requestUrlsByPath)) { - return $requestUrlsByPath[$facet_source_path]; - } - - // Try to grab any route params from the original request. - // In case of request path not having a matching route, Url generator will - // fail with. - try { - $requestUrl = Url::createFromRequest($request); - } - catch (ResourceNotFoundException $e) { - // Bypass exception if no path available. - // Should be unreachable in default FacetSource implementations, - // but you never know. - if (!$facet_source_path) { - throw $e; - } - - $requestUrl = Url::fromUserInput($facet_source_path, [ - 'query' => [ - '_format' => $this->request->get('_format'), - ], - ]); - } - - $requestUrl->setOption('attributes', ['rel' => 'nofollow']); - $requestUrlsByPath[$facet_source_path] = $requestUrl; - return $requestUrl; + return $this->urlGenerator->getUrlForRequest($request, $facet_source_path); } /** @@ -417,4 +409,11 @@ class QueryString extends UrlProcessorPluginBase { return $mapping[$facet_source_id][$facet_id]; } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['url.query_args']; + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/widget/LinksWidget.php b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/widget/LinksWidget.php index 54fbfa9c6..d7662d3b4 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/widget/LinksWidget.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Plugin/facets/widget/LinksWidget.php @@ -67,14 +67,14 @@ class LinksWidget extends WidgetPluginBase { unset($active_filters[$facet->id()]); - // Only if there are still active filters, use url generator. + $urlGenerator = \Drupal::service('facets.utility.url_generator'); if ($active_filters) { - $url = \Drupal::service('facets.utility.url_generator') - ->getUrl($active_filters, FALSE); + $url = $urlGenerator->getUrl($active_filters, FALSE); } else { $request = \Drupal::request(); - $url = Url::createFromRequest($request); + $facet_source = $facet->getFacetSource(); + $url = $urlGenerator->getUrlForRequest($request, $facet_source ? $facet_source->getPath() : NULL); $params = $request->query->all(); unset($params[$url_processor->getFilterKey()]); if (\array_key_exists('page', $params)) { diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Processor/ProcessorPluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/Processor/ProcessorPluginBase.php index 5b3f3e5ac..93a36b4e1 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Processor/ProcessorPluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Processor/ProcessorPluginBase.php @@ -2,6 +2,8 @@ namespace Drupal\facets\Processor; +use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\UncacheableDependencyTrait; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Entity\DependencyTrait; use Drupal\Core\Plugin\PluginBase; @@ -9,9 +11,14 @@ use Drupal\facets\FacetInterface; /** * A base class for plugins that implements most of the boilerplate. + * + * By default all plugins that will extend this class will disable facets + * caching mechanism. It is strongly recommended to turn it on by implementing + * own methods for the CacheableDependencyInterface interface. */ -class ProcessorPluginBase extends PluginBase implements ProcessorInterface { +class ProcessorPluginBase extends PluginBase implements ProcessorInterface, CacheableDependencyInterface { + use UncacheableDependencyTrait; use DependencyTrait; /** @@ -69,7 +76,7 @@ class ProcessorPluginBase extends PluginBase implements ProcessorInterface { */ public function getDescription() { $plugin_definition = $this->getPluginDefinition(); - return isset($plugin_definition['description']) ? $plugin_definition['description'] : ''; + return $plugin_definition['description'] ?? ''; } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Processor/SortProcessorPluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/Processor/SortProcessorPluginBase.php index 94865f8df..4f5605846 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Processor/SortProcessorPluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Processor/SortProcessorPluginBase.php @@ -2,20 +2,21 @@ namespace Drupal\facets\Processor; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\facets\FacetInterface; /** * A base class for plugins that implements some boilerplate for a widget order. */ -abstract class SortProcessorPluginBase extends ProcessorPluginBase implements SortProcessorInterface { +abstract class SortProcessorPluginBase extends ProcessorPluginBase implements SortProcessorInterface, CacheableDependencyInterface { /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) { $processors = $facet->getProcessors(); - $config = isset($processors[$this->getPluginId()]) ? $processors[$this->getPluginId()] : NULL; + $config = $processors[$this->getPluginId()] ?? NULL; $build['sort'] = [ '#type' => 'radios', diff --git a/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypePluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypePluginBase.php index cf256f345..0f276878f 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypePluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypePluginBase.php @@ -93,7 +93,7 @@ abstract class QueryTypePluginBase extends PluginBase implements QueryTypeInterf 'limit' => $this->facet->getHardLimit(), 'operator' => $this->facet->getQueryOperator(), 'min_count' => $this->facet->getMinCount(), - 'missing' => FALSE, + 'missing' => $this->facet->isMissing(), 'query_type' => $this->getPluginId(), ]; } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypeRangeBase.php b/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypeRangeBase.php index dd5e3cf6d..68de4eeed 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypeRangeBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/QueryType/QueryTypeRangeBase.php @@ -71,9 +71,14 @@ abstract class QueryTypeRangeBase extends QueryTypePluginBase { foreach ($this->results as $result) { // Go through the results and add facet results grouped by filters // defined by self::calculateResultFilter(). - if ($result['count'] || $query_operator == 'or') { + if ($result['count'] || $query_operator === 'or') { $count = $result['count']; if ($result_filter = $this->calculateResultFilter(trim($result['filter'], '"'))) { + if ($result_filter === 'NULL' || $result_filter === '') { + // "Missing" facet items could not be handled in ranges. + continue; + } + if (isset($facet_results[$result_filter['raw']])) { $facet_results[$result_filter['raw']]->setCount( $facet_results[$result_filter['raw']]->getCount() + $count diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Result/Result.php b/frontend/drupal9/web/modules/contrib/facets/src/Result/Result.php index 434606ea7..334dc6855 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Result/Result.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Result/Result.php @@ -38,6 +38,20 @@ class Result implements ResultInterface { */ protected $count = 0; + /** + * Indicates if this is the additional result item for "missing". + * + * @var bool + */ + protected $missing = FALSE; + + /** + * Other filters that might become active if result item isn't "missing". + * + * @var array + */ + protected $missingFilters = []; + /** * The Url object. * @@ -106,6 +120,36 @@ class Result implements ResultInterface { $this->count = (int) $count; } + /** + * {@inheritdoc} + */ + public function isMissing(): bool { + return $this->missing; + } + + /** + * {@inheritdoc} + */ + public function setMissing(bool $missing) { + $this->missing = $missing; + } + + /** + * {@inheritdoc} + */ + public function getMissingFilters(): array { + return $this->missingFilters; + } + + /** + * {@inheritdoc} + */ + public function setMissingFilters(array $filters) { + $this->missingFilters = array_filter($filters, static function ($filter) { + return $filter !== '!'; + }); + } + /** * {@inheritdoc} */ diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Result/ResultInterface.php b/frontend/drupal9/web/modules/contrib/facets/src/Result/ResultInterface.php index a68fe979e..3e23b638e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Result/ResultInterface.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Result/ResultInterface.php @@ -49,6 +49,38 @@ interface ResultInterface { */ public function setCount($count); + /** + * Set if this result represents the "missing" facet item. + * + * @return bool + * True if this result represents the missing facet item. + */ + public function isMissing(): bool; + + /** + * Returns true if this result represents the "missing" facet item. + * + * @param bool $missing + * True if this result represents the missing facet item. + */ + public function setMissing(bool $missing); + + /** + * Get the filter values of the non-missing values to be inverted. + * + * @return array + * The filter values of the non-missing values to be inverted. + */ + public function getMissingFilters(): array; + + /** + * Set the filter values of the non-missing values to be inverted. + * + * @param array $filters + * The filter values of the non-missing values to be inverted. + */ + public function setMissingFilters(array $filters); + /** * Returns the url. * diff --git a/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorInterface.php b/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorInterface.php index c2c4a29ec..9f2e320c6 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorInterface.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorInterface.php @@ -59,6 +59,14 @@ interface UrlProcessorInterface { */ public function getSeparator(); + /** + * Returns the multi-value delimiter. + * + * @return string + * A string containing the multi-value delimiter. + */ + public function getDelimiter(): string; + /** * Returns the active filters. * diff --git a/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorPluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorPluginBase.php index 129a90be1..f54a94692 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorPluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/UrlProcessor/UrlProcessorPluginBase.php @@ -2,6 +2,8 @@ namespace Drupal\facets\UrlProcessor; +use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\UncacheableDependencyTrait; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\facets\Exception\InvalidProcessorException; @@ -12,8 +14,14 @@ use Symfony\Component\HttpFoundation\Request; /** * A base class for plugins that implements most of the boilerplate. + * + * By default all plugins that will extend this class will disable facets + * caching mechanism. It is strongly recommended to turn it on by implementing + * own methods for the CacheableDependencyInterface interface. */ -abstract class UrlProcessorPluginBase extends ProcessorPluginBase implements UrlProcessorInterface, ContainerFactoryPluginInterface { +abstract class UrlProcessorPluginBase extends ProcessorPluginBase implements UrlProcessorInterface, ContainerFactoryPluginInterface, CacheableDependencyInterface { + + use UncacheableDependencyTrait; /** * The query string variable. @@ -27,10 +35,18 @@ abstract class UrlProcessorPluginBase extends ProcessorPluginBase implements Url * The url separator variable. * * @var string - * The sepatator to use between field and value. + * The separator to use between field and value. */ protected $separator; + /** + * The delimiter for multiple values. + * + * @var string + * The delimiter to use between multiple values. + */ + protected $delimiter = '|'; + /** * The clone of the current request object. * @@ -68,6 +84,13 @@ abstract class UrlProcessorPluginBase extends ProcessorPluginBase implements Url return $this->separator; } + /** + * {@inheritdoc} + */ + public function getDelimiter(): string { + return $this->delimiter; + } + /** * Constructs a new instance of the class. * @@ -119,9 +142,7 @@ abstract class UrlProcessorPluginBase extends ProcessorPluginBase implements Url $configuration, $plugin_id, $plugin_definition, - // Support 9.3+. - // @todo remove switch after 9.3 or greater is required. - version_compare(\Drupal::VERSION, '9.3', '>=') ? $request_stack->getMainRequest() : $request_stack->getMasterRequest(), + $request_stack->getMainRequest(), $container->get('entity_type.manager') ); } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsDateHandler.php b/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsDateHandler.php index 8ba55429f..60e3b7257 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsDateHandler.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsDateHandler.php @@ -150,8 +150,8 @@ class FacetsDateHandler { // Gets gap numbers for both the gap and minimum gap, checks if the next gap // is within the limit set by the $min_gap parameter. - $gap_num = isset($gap_numbers[$gap]) ? $gap_numbers[$gap] : 6; - $min_num = isset($gap_numbers[$min_gap]) ? $gap_numbers[$min_gap] : 1; + $gap_num = $gap_numbers[$gap] ?? 6; + $min_num = $gap_numbers[$min_gap] ?? 1; return ($gap_num > $min_num) ? array_search($gap_num - 1, $gap_numbers) : $min_gap; } @@ -383,8 +383,8 @@ class FacetsDateHandler { static::FACETS_DATE_SECOND => 1, ]; - $gap1_num = isset($gap_numbers[$gap1]) ? $gap_numbers[$gap1] : 6; - $gap2_num = isset($gap_numbers[$gap2]) ? $gap_numbers[$gap2] : 6; + $gap1_num = $gap_numbers[$gap1] ?? 6; + $gap2_num = $gap_numbers[$gap2] ?? 6; if ($gap1_num == $gap2_num) { return 0; diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsUrlGenerator.php b/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsUrlGenerator.php index 3a28181fa..cd5ea4f5f 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsUrlGenerator.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Utility/FacetsUrlGenerator.php @@ -3,8 +3,11 @@ namespace Drupal\facets\Utility; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Url; use Drupal\facets\Result\Result; use Drupal\facets\UrlProcessor\UrlProcessorPluginManager; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; /** * Facets Url Generator service. @@ -109,4 +112,69 @@ class FacetsUrlGenerator { return NULL; } + /** + * Gets the URL object for a request. + * + * This method statically caches the URL object for a request based on the + * facet source path. This reduces subsequent calls to the processor from + * having to regenerate the URL object. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * @param string $facet_source_path + * The facet source path. + * + * @return \Drupal\Core\Url + * The URL. + */ + public function getUrlForRequest(Request $request, $facet_source_path = NULL): Url { + /** @var \Drupal\Core\Url[] $requestUrlsByPath */ + $requestUrlsByPath = &drupal_static(__CLASS__ . __FUNCTION__, []); + $request_uri = $request->getRequestUri(); + + if (array_key_exists($request_uri, $requestUrlsByPath)) { + return $requestUrlsByPath[$request_uri]; + } + + // Try to grab any route params from the original request. + // In case of request path not having a matching route, Url generator will + // fail with. + try { + $requestUrl = Url::createFromRequest($request); + } + catch (ResourceNotFoundException $e) { + // Bypass exception if no path available. + // Should be unreachable in default FacetSource implementations, + // but you never know. + if ($facet_source_path) { + $requestUrl = Url::fromUserInput($facet_source_path, [ + 'query' => [ + '_format' => \Drupal::request()->get('_format'), + ], + ]); + } + else { + if ('system.404' === $request->attributes->get('_route')) { + // It seems that a facet that is configured to be rendered without its + // facet source is currently rendered on a dedicated "page not found" + // page. If the facet source has a valid path we would not land here + // but in the condition above. So the facet source must be view block + // display or something similar. In this case we could assume that + // such a facet takes care about its link target itself and doesn't + // depend on the current path or the facet source path. Let's provide + // the front page as valid fallback to let the facet do its job. + $requestUrl = Url::fromRoute(''); + } + else { + throw $e; + } + } + } + + $requestUrl->setOption('attributes', ['rel' => 'nofollow']); + $requestUrlsByPath[$request_uri] = $requestUrl; + + return $requestUrl; + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/src/Widget/WidgetPluginBase.php b/frontend/drupal9/web/modules/contrib/facets/src/Widget/WidgetPluginBase.php index 2cbfb1f67..7bbda4b31 100644 --- a/frontend/drupal9/web/modules/contrib/facets/src/Widget/WidgetPluginBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/src/Widget/WidgetPluginBase.php @@ -81,12 +81,6 @@ abstract class WidgetPluginBase extends PluginBase implements WidgetPluginInterf 'class' => [$facet->getActiveItems() ? 'facet-active' : 'facet-inactive'], ], '#context' => !empty($widget['type']) ? ['list_style' => $widget['type']] : [], - '#cache' => [ - 'contexts' => [ - 'url.path', - 'url.query_args', - ], - ], ]; } diff --git a/frontend/drupal9/web/modules/contrib/facets/templates/facets-item-list.html.twig b/frontend/drupal9/web/modules/contrib/facets/templates/facets-item-list.html.twig index 7fc9e444a..4a136d2c6 100644 --- a/frontend/drupal9/web/modules/contrib/facets/templates/facets-item-list.html.twig +++ b/frontend/drupal9/web/modules/contrib/facets/templates/facets-item-list.html.twig @@ -24,6 +24,20 @@ * @ingroup themeable */ #} +{% if cache_hash %} + +{%- endif %}
{% if facet.widget.type %} {%- set attributes = attributes.addClass('item-list__' ~ facet.widget.type) %} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_custom_widget/facets_custom_widget.info.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_custom_widget/facets_custom_widget.info.yml index 866ad784a..95b915ffa 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/facets_custom_widget/facets_custom_widget.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_custom_widget/facets_custom_widget.info.yml @@ -5,7 +5,7 @@ package: 'Testing' hidden: true core_version_requirement: ^9.2 || ^10.0 -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_events_test/facets_events_test.info.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_events_test/facets_events_test.info.yml index ae76c68d5..84cf57401 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/facets_events_test/facets_events_test.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_events_test/facets_events_test.info.yml @@ -5,7 +5,7 @@ package: 'Testing' hidden: true core_version_requirement: ^9.2 || ^10.0 -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.info.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.info.yml new file mode 100644 index 000000000..0b5b986ae --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.info.yml @@ -0,0 +1,13 @@ +name: 'Facets processors collection' +type: module +description: 'Contains collection of test facet processors' +package: 'Testing' +hidden: true +core_version_requirement: ^9.2 || ^10.0 +dependencies: + - facets:facets + +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' +project: 'facets' +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.module b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.module new file mode 100644 index 000000000..0861b625f --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.module @@ -0,0 +1,19 @@ +get('facets_processors_collection_alter_string_query_handler', FALSE) + ) { + $query_types['string'] = 'search_api_string_cached'; + } +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.services.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.services.yml new file mode 100644 index 000000000..4e9e133cd --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/facets_processors_collection.services.yml @@ -0,0 +1,28 @@ +services: + cache_context.fpc_build: + class: Drupal\facets_processors_collection\Cache\FpcCacheContext + argumets: + type: build + tags: + - { name: cache.context } + + cache_context.fpc_sort: + class: Drupal\facets_processors_collection\Cache\FpcCacheContext + argumets: + type: sort + tags: + - { name: cache.context } + + cache_context.fpc_post_query: + class: Drupal\facets_processors_collection\Cache\FpcCacheContext + argumets: + type: post_query + tags: + - { name: cache.context } + + cache_context.fpc_query_type_plugin: + class: Drupal\facets_processors_collection\Cache\FpcCacheContext + argumets: + type: query_type_plugin + tags: + - { name: cache.context } diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Cache/FpcCacheContext.php b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Cache/FpcCacheContext.php new file mode 100644 index 000000000..00760033f --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Cache/FpcCacheContext.php @@ -0,0 +1,86 @@ +type = $type; + } + + /** + * Get all allowed context types. + * + * @return array + * Array of context types: all processor stages + query_type plugin. + */ + protected static function getAllowedTypes() { + return array_merge(static::$processorStages, [static::QUERY_PLUGIN]); + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t( + 'FPC: cache context, cab be one of the following: %stages.', + ['%stages' => implode(', ', static::getAllowedTypes())] + ); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return 'fpc_' . $this->type; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return new CacheableMetadata(); + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php new file mode 100644 index 000000000..dca3dad97 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcBuildProcessor.php @@ -0,0 +1,59 @@ +setDisplayValue('Test ' . $result->getDisplayValue()); + } + // An example cache tag that can be added from the ::build(). + $facet->addCacheTags(['fpc:added_within_build_method']); + + return $results; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return Cache::mergeTags(parent::getCacheTags(), ['fpc:build_processor']); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return Cache::mergeContexts(parent::getCacheContexts(), ['fpc_build']); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php new file mode 100644 index 000000000..6fe8a4610 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcPostQueryProcessor.php @@ -0,0 +1,52 @@ +addCacheTags(['fpc:added_within_postQuery_method']); + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php new file mode 100644 index 000000000..c2d2038b5 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/processor/FpcSortProcessor.php @@ -0,0 +1,60 @@ +disables cache"), + * stages = { + * "sort" = 50 + * } + * ) + */ +class FpcSortRandomProcessor extends FpcSortProcessor { + + /** + * {@inheritdoc} + */ + public function sortResults(Result $a, Result $b) { + return random_int(-1, 1); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // As sorting should be random, we can't cache results. + return 0; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php new file mode 100644 index 000000000..bb1d38bc9 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_processors_collection/src/Plugin/facets/query_type/CacheableQueryTypePlugin.php @@ -0,0 +1,27 @@ +query->addCacheTags(['fpc:query_plugin_type_plugin']); + $this->query->addCacheContexts(['fpc_query_type_plugin']); + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.info.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.info.yml index 901b23d36..6eeb1cc76 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.info.yml @@ -5,7 +5,7 @@ package: 'Testing' hidden: true core_version_requirement: ^9.2 || ^10.0 -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.services.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.services.yml new file mode 100644 index 000000000..5163aeb58 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/facets_query_processor.services.yml @@ -0,0 +1,14 @@ +services: + cache_context.dummy_query_build: + class: Drupal\facets_query_processor\Cache\DummyQuery + argumets: + type: build + tags: + - { name: cache.context } + + cache_context.dummy_query_pre_query: + class: Drupal\facets_query_processor\Cache\DummyQuery + argumets: + type: pre_query + tags: + - { name: cache.context } diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/src/Plugin/facets/url_processor/DummyQuery.php b/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/src/Plugin/facets/url_processor/DummyQuery.php index 9bb268ba7..105f571f7 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/src/Plugin/facets/url_processor/DummyQuery.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_query_processor/src/Plugin/facets/url_processor/DummyQuery.php @@ -2,8 +2,11 @@ namespace Drupal\facets_query_processor\Plugin\facets\url_processor; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\facets\FacetInterface; use Drupal\facets\Plugin\facets\url_processor\QueryString; +use Drupal\facets\Utility\FacetsUrlGenerator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; @@ -21,10 +24,33 @@ class DummyQuery extends QueryString { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $eventDispatcher) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $eventDispatcher, FacetsUrlGenerator $urlGenerator) { // Override the default separator. $configuration['separator'] = '||'; - parent::__construct($configuration, $plugin_id, $plugin_definition, $request, $entity_type_manager, $eventDispatcher); + parent::__construct($configuration, $plugin_id, $plugin_definition, $request, $entity_type_manager, $eventDispatcher, $urlGenerator); + } + + /** + * {@inheritdoc} + */ + public function buildUrls(FacetInterface $facet, array $results) { + $facet->addCacheTags(['dummy_query_build_urls_tag']); + $facet->addCacheContexts(['dummy_query_build']); + return parent::buildUrls($facet, $results); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return Cache::mergeTags(parent::getCacheTags(), ['dummy_query_pre_query_tag']); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return Cache::mergeContexts(parent::getCacheContexts(), ['dummy_query_pre_query']); } } diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml index d13feac71..0b617b038 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/config/install/views.view.search_api_test_view.yml @@ -1,76 +1,34 @@ -base_field: search_api_id -base_table: search_api_index_database_search_index -core: 8.x -description: '' +langcode: en status: true +dependencies: + config: + - search_api.index.database_search_index + module: + - search_api +id: search_api_test_view +label: 'Search API Test Fulltext search view' +module: views +description: '' +tag: '' +base_table: search_api_index_database_search_index +base_field: search_api_id display: default: - display_plugin: default id: default display_title: Master + display_plugin: default position: 0 display_options: - access: - type: none - options: { } - cache: - type: none - options: { } - query: - type: search_api_query - options: - skip_access: true - exposed_form: - type: basic - options: - submit_button: Search - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: full - options: - items_per_page: 10 - offset: 0 - id: 0 - total_pages: null - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 20, 40, 60' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - tags: - previous: '‹ previous' - next: 'next ›' - first: '« first' - last: 'last »' - quantity: 9 - style: - type: default - row: - type: search_api - options: - view_modes: - bundle: - 'article': default - 'page': default - datasource: - 'entity:entity_test': default + title: 'Fulltext test index' fields: search_api_id: + id: search_api_id table: search_api_index_database_search_index field: search_api_id - id: search_api_id - plugin_id: numeric relationship: none group_type: group admin_label: '' + plugin_id: numeric label: 'Entity ID' exclude: false alter: @@ -117,9 +75,81 @@ display: decimal: . separator: ',' format_plural: false - format_plural_string: "1\x03@count" + format_plural_string: !!binary MQNAY291bnQ= prefix: '' suffix: '' + pager: + type: full + options: + offset: 0 + items_per_page: 10 + total_pages: null + id: 0 + tags: + next: 'next ›' + previous: '‹ previous' + first: '« first' + last: 'last »' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 20, 40, 60' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + quantity: 9 + exposed_form: + type: basic + options: + submit_button: Search + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + access: + type: none + options: { } + cache: + type: none + options: { } + empty: { } + sorts: + search_api_id: + id: search_api_id + table: search_api_index_database_search_index + field: search_api_id + relationship: none + group_type: group + admin_label: '' + plugin_id: search_api + order: ASC + expose: + label: '' + field_identifier: search_api_id + exposed: false + arguments: + search_api_datasource: + id: search_api_datasource + table: search_api_index_database_search_index + field: search_api_datasource + plugin_id: search_api + break_phrase: true + type: + id: type + table: search_api_index_database_search_index + field: type + plugin_id: search_api + break_phrase: false + not: true + keywords: + id: keywords + table: search_api_index_database_search_index + field: keywords + plugin_id: search_api + break_phrase: true filters: search_api_fulltext: id: search_api_fulltext @@ -128,6 +158,7 @@ display: relationship: none group_type: group admin_label: '' + plugin_id: search_api_fulltext operator: and value: '' group: 1 @@ -138,6 +169,8 @@ display: description: '' use_operator: true operator: search_api_fulltext_op + operator_limit_selection: false + operator_list: { } identifier: search_api_fulltext required: false remember: false @@ -160,14 +193,13 @@ display: group_items: { } min_length: 3 fields: { } - plugin_id: search_api_fulltext id: - plugin_id: search_api_numeric id: id table: search_api_index_database_search_index field: id relationship: none admin_label: '' + plugin_id: search_api_numeric operator: '=' group: 1 exposed: true @@ -177,6 +209,8 @@ display: description: '' use_operator: true operator: id_op + operator_limit_selection: false + operator_list: { } identifier: id required: false remember: false @@ -187,12 +221,12 @@ display: administrator: '0' is_grouped: false created: - plugin_id: search_api_date id: created table: search_api_index_database_search_index field: created relationship: none admin_label: '' + plugin_id: search_api_date operator: '=' group: 1 exposed: true @@ -202,6 +236,8 @@ display: description: '' use_operator: true operator: created_op + operator_limit_selection: false + operator_list: { } identifier: created required: false remember: false @@ -212,12 +248,12 @@ display: administrator: '0' is_grouped: false keywords: - plugin_id: search_api_string id: keywords table: search_api_index_database_search_index field: keywords relationship: none admin_label: '' + plugin_id: search_api_string operator: '=' group: 1 exposed: true @@ -227,6 +263,8 @@ display: description: '' use_operator: true operator: keywords_op + operator_limit_selection: false + operator_list: { } identifier: keywords required: false remember: false @@ -237,13 +275,13 @@ display: administrator: '0' is_grouped: false search_api_language: - plugin_id: search_api_language id: search_api_language table: search_api_index_database_search_index field: search_api_language relationship: none admin_label: '' - operator: 'in' + plugin_id: search_api_language + operator: in group: 1 exposed: true expose: @@ -252,6 +290,8 @@ display: description: '' use_operator: true operator: language_op + operator_limit_selection: false + operator_list: { } identifier: language required: false remember: false @@ -261,20 +301,22 @@ display: anonymous: '0' administrator: '0' is_grouped: false - sorts: - search_api_id: - id: search_api_id - table: search_api_index_database_search_index - field: search_api_id - relationship: none - group_type: group - admin_label: '' - order: ASC - exposed: false - expose: - label: '' - plugin_id: search_api - title: 'Fulltext test index' + style: + type: default + row: + type: search_api + options: + view_modes: + bundle: + article: default + page: default + datasource: + 'entity:entity_test': default + query: + type: search_api_query + options: + skip_access: true + relationships: { } header: result: id: result @@ -283,54 +325,117 @@ display: relationship: none group_type: group admin_label: '' - content: 'Displaying @total search results' plugin_id: result + content: 'Displaying @total search results' footer: { } - empty: { } - relationships: { } - arguments: - search_api_datasource: - plugin_id: search_api - id: search_api_datasource - table: search_api_index_database_search_index - field: search_api_datasource - break_phrase: true - type: - plugin_id: search_api - id: type - table: search_api_index_database_search_index - field: type - break_phrase: false - not: true - keywords: - plugin_id: search_api - id: keywords - table: search_api_index_database_search_index - field: keywords - break_phrase: true - page_1: - display_plugin: page - id: page_1 - display_title: Page - position: 1 - display_options: - path: search-api-test-fulltext + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:search_api.index.database_search_index' block_1: - display_plugin: block id: block_1 display_title: Block - position: 2 + display_plugin: block + position: 3 display_options: - display_extenders: { } defaults: use_ajax: false use_ajax: true -label: 'Search API Test Fulltext search view' -module: views -id: search_api_test_view -tag: '' -langcode: en -dependencies: - module: - - search_api - - facets_search_api_dependency + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:search_api.index.database_search_index' + block_1_sapi_tag: + id: block_1_sapi_tag + display_title: 'Block Search API cache tag' + display_plugin: block + position: 4 + display_options: + cache: + type: search_api_tag + options: { } + defaults: + cache: false + use_ajax: false + use_ajax: true + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:search_api.index.database_search_index' + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 1 + display_options: + display_extenders: { } + path: search-api-test-fulltext + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:search_api.index.database_search_index' + page_2_sapi_tag: + id: page_2_sapi_tag + display_title: 'Page Search API cache tag' + display_plugin: page + position: 2 + display_options: + cache: + type: search_api_tag + options: { } + defaults: + cache: false + display_extenders: { } + path: search-api-test-fulltext-cache-tag + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:search_api.index.database_search_index' + page_2_sapi_time: + id: page_2_sapi_time + display_title: 'Page Search API cache time' + display_plugin: page + position: 2 + display_options: + cache: + type: search_api_time + options: + results_lifespan: 21600 + results_lifespan_custom: 0 + output_lifespan: 518400 + output_lifespan_custom: 0 + defaults: + cache: false + display_extenders: { } + path: search-api-test-fulltext-cache-time + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:search_api.index.database_search_index' diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/facets_search_api_dependency.info.yml b/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/facets_search_api_dependency.info.yml index 581616aef..57efe0587 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/facets_search_api_dependency.info.yml +++ b/frontend/drupal9/web/modules/contrib/facets/tests/facets_search_api_dependency/facets_search_api_dependency.info.yml @@ -9,7 +9,7 @@ dependencies: - drupal:views core_version_requirement: ^9.2 || ^10.0 -# Information added by Drupal.org packaging script on 2022-04-04 -version: '2.0.2' +# Information added by Drupal.org packaging script on 2022-07-09 +version: '2.0.4' project: 'facets' -datestamp: 1649070272 +datestamp: 1657367472 diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BlockTestTrait.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BlockTestTrait.php index 748b8e2bd..49432a587 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BlockTestTrait.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BlockTestTrait.php @@ -85,9 +85,7 @@ trait BlockTestTrait { * The id of the block. */ protected function deleteBlock($id) { - // Delete a facet block through the UI, the text for the success message has - // changed in Drupal::VERSION 9.3. - $orig_success_message = 'The block ' . $this->blocks[$id]->label() . ' has been removed' . (\Drupal::VERSION >= 9.3 ? ' from the Footer region' : '') . '.'; + $orig_success_message = 'The block ' . $this->blocks[$id]->label() . ' has been removed from the Footer region.'; $this->drupalGet('admin/structure/block/manage/' . $this->blocks[$id]->id(), ['query' => ['destination' => 'admin']]); $this->clickLink('Remove block'); diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BreadcrumbIntegrationTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BreadcrumbIntegrationTest.php index 7aa9b48b0..47d29c702 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BreadcrumbIntegrationTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/BreadcrumbIntegrationTest.php @@ -121,7 +121,7 @@ class BreadcrumbIntegrationTest extends FacetsTestBase { */ protected function editFacetConfig(array $config = []) { $this->drupalGet('admin/config/search/facets'); - $this->clickLink('Configure', 1); + $this->clickLink('Configure', 2); $default_config = [ 'filter_key' => 'f', 'url_processor' => 'query_string', diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/HierarchicalFacetIntegrationTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/HierarchicalFacetIntegrationTest.php index a3052cd39..19536b6e2 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/HierarchicalFacetIntegrationTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/HierarchicalFacetIntegrationTest.php @@ -383,7 +383,7 @@ class HierarchicalFacetIntegrationTest extends FacetsTestBase { */ public function testHierarchyBreadcrumb() { $this->drupalGet('admin/config/search/facets'); - $this->clickLink('Configure', 1); + $this->clickLink('Configure', 2); $default_config = [ 'filter_key' => 'f', 'url_processor' => 'query_string', diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationCacheTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationCacheTest.php new file mode 100644 index 000000000..24674c446 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationCacheTest.php @@ -0,0 +1,741 @@ +enableWebsiteCache(); + $this->setUpExampleStructure(); + $this->insertExampleContent(); + $this->assertEquals(5, $this->indexItems($this->indexId), '5 items were indexed.'); + + $this->facetStorage = $this->container->get('entity_type.manager') + ->getStorage('facets_facet'); + $this->entityTestStorage = \Drupal::entityTypeManager() + ->getStorage('entity_test_mulrev_changed'); + } + + /** + * Tests various operations via the Facets' admin UI. + * + * Cached implementation of testBlockView integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testBlockView() + */ + public function testFramework() { + $facet_id = 'test_facet_name'; + $this->drupalGet(static::VIEW_URL); + // By default, the view should show all entities. + $this->assertSession()->pageTextContains('Displaying 5 search results'); + + $this->createFacet('Test Facet name', $facet_id, 'type', static::VIEW_DISPLAY); + + // Verify that the facet results are correct. + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('item'); + $this->assertSession()->pageTextContains('article'); + + // Verify that facet blocks appear as expected. + $this->assertFacetBlocksAppear(); + + // Verify that the facet only shows when the facet source is visible, it + // should not show up on the user page. + $this->drupalGet(''); + $this->assertNoFacetBlocksAppear(); + + // Do not show the block on empty behaviors. + $this->clearIndex(); + $this->drupalGet(static::VIEW_URL); + + // Verify that no facet blocks appear. Empty behavior "None" is selected by + // default. + $this->assertNoFacetBlocksAppear(); + + // Verify that the "empty_text" appears as expected. + $settings = [ + 'behavior' => 'text', + 'text' => 'No results found for this block!', + 'text_format' => 'plain_text', + ]; + $facet = $this->getFacetById($facet_id); + $facet->setEmptyBehavior($settings); + $this->facetStorage->save($facet); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->responseContains('block-test-facet-name'); + $this->assertSession()->responseContains('No results found for this block!'); + } + + /** + * Tests that a block view also works. + * + * Cached implementation of testBlockView integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testBlockView() + */ + public function testBlockView() { + $webAssert = $this->assertSession(); + $this->createFacet( + 'Block view facet', + 'block_view_facet', + 'type', + 'block_1_sapi_tag', + 'views_block__search_api_test_view' + ); + + // Place the views block in the footer of all pages. + $block_settings = [ + 'region' => 'sidebar_first', + 'id' => 'view_block', + ]; + $this->drupalPlaceBlock('views_block:search_api_test_view-block_1_sapi_tag', $block_settings); + + // By default, the view should show all entities. + $this->drupalGet(''); + $webAssert->pageTextContains('Fulltext test index'); + $webAssert->pageTextContains('Displaying 5 search results'); + $webAssert->pageTextContains('item'); + $webAssert->pageTextContains('article'); + + // Click the item link, and test that filtering of results actually works. + $this->clickLink('item'); + $webAssert->pageTextContains('Displaying 3 search results'); + } + + /** + * Tests that an url alias works correctly. + * + * Cached implementation of testUrlAlias integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testUrlAlias() + */ + public function testUrlAlias() { + $facet_id = 'ab_facet'; + $this->createFacet('ab Facet', $facet_id, 'type', static::VIEW_DISPLAY); + + $this->drupalGet(static::VIEW_URL); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + + $this->clickLink('item'); + $url = Url::fromUserInput('/' . static::VIEW_URL, ['query' => ['f' => ['ab_facet:item']]]); + $this->assertSession()->addressEquals($url); + + $this->updateFacet($facet_id, ['url_alias' => 'llama']); + + $this->drupalGet(static::VIEW_URL); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + + $this->clickLink('item'); + $url = Url::fromUserInput('/' . static::VIEW_URL, ['query' => ['f' => ['llama:item']]]); + $this->assertSession()->addressEquals($url); + } + + /** + * Tests facet dependencies. + * + * Cached implementation of testFacetDependencies integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testFacetDependencies() + */ + public function testFacetDependencies() { + $facet_name = "DependableFacet"; + $facet_id = 'dependablefacet'; + + $depending_facet_name = "DependingFacet"; + $depending_facet_id = "dependingfacet"; + + $this->createFacet($facet_name, $facet_id, 'type', static::VIEW_DISPLAY); + $this->createFacet($depending_facet_name, $depending_facet_id, 'keywords', static::VIEW_DISPLAY); + + // Go to the view and test that both facets are shown. Item and article + // come from the DependableFacet, orange and grape come from DependingFacet. + $this->drupalGet(static::VIEW_URL); + $this->assertFacetLabel('grape'); + $this->assertFacetLabel('orange'); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + $this->assertFacetBlocksAppear(); + + // Change the visiblity settings of the DependingFacet. + $facet = $this->getFacetById($depending_facet_id); + $processor = [ + 'processor_id' => 'dependent_processor', + 'weights' => ['build' => 5], + 'settings' => [ + $facet_id => [ + 'enable' => TRUE, + 'condition' => 'values', + 'values' => 'item', + 'negate' => FALSE, + ], + ], + ]; + $facet->addProcessor($processor); + $this->facetStorage->save($facet); + + // Go to the view and test that only the types are shown. + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->linkNotExists('grape'); + $this->assertSession()->linkNotExists('orange'); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + + // Click on the item, and test that this shows the keywords. + $this->clickLink('item'); + $this->assertFacetLabel('grape'); + $this->assertFacetLabel('orange'); + + // Go back to the view, click on article and test that the keywords are + // hidden. + $this->drupalGet(static::VIEW_URL); + $this->clickLink('article'); + $this->assertSession()->linkNotExists('grape'); + $this->assertSession()->linkNotExists('orange'); + + // Change the visibility settings to negate the previous settings. + $processor['settings'][$facet_id]['negate'] = TRUE; + $facet->addProcessor($processor); + $this->facetStorage->save($facet); + + // Go to the view and test only the type facet is shown. + $this->drupalGet(static::VIEW_URL); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + $this->assertFacetLabel('grape'); + $this->assertFacetLabel('orange'); + + // Click on the article, and test that this shows the keywords. + $this->clickLink('article'); + $this->assertFacetLabel('grape'); + $this->assertFacetLabel('orange'); + + // Go back to the view, click on item and test that the keywords are + // hidden. + $this->drupalGet(static::VIEW_URL); + $this->clickLink('item'); + $this->assertSession()->linkNotExists('grape'); + $this->assertSession()->linkNotExists('orange'); + + // Disable negation again. + $processor['settings'][$facet_id]['negate'] = FALSE; + $facet->addProcessor($processor); + $this->facetStorage->save($facet); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertSession()->linkNotExists('grape'); + $this->clickLink('item'); + $this->assertSession()->pageTextContains('Displaying 3 search results'); + $this->assertSession()->linkExists('grape'); + $this->clickLink('grape'); + $this->assertSession()->pageTextContains('Displaying 1 search results'); + // Disable item again, and the grape should not be reflected in the search + // result anymore. + $this->clickLink('item'); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + } + + /** + * Tests the facet's and/or functionality. + * + * Cached implementation of testAndOrFacet integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testAndOrFacet() + */ + public function testAndOrFacet() { + $facet_id = 'test_facet'; + + $this->createFacet('test & facet', $facet_id, 'type', static::VIEW_DISPLAY); + $this->updateFacet($facet_id, ['query_operator' => 'and']); + + $this->drupalGet(static::VIEW_URL); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + + $this->clickLink('item'); + $this->checkFacetIsActive('item'); + $this->assertSession()->linkNotExists('article'); + + $this->updateFacet($facet_id, ['query_operator' => 'or']); + + $this->drupalGet(static::VIEW_URL); + $this->assertFacetLabel('item'); + $this->assertFacetLabel('article'); + + $this->clickLink('item (3)'); + $this->checkFacetIsActive('item'); + $this->assertFacetLabel('article (2)'); + } + + /** + * Tests the facet's exclude functionality. + * + * Cached implementation of testExcludeFacet integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testExcludeFacet() + */ + public function testExcludeFacet() { + $facet_id = 'test_facet'; + $this->createFacet('test & facet', $facet_id, 'type', static::VIEW_DISPLAY); + $this->updateFacet($facet_id, ['exclude' => TRUE]); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('foo bar baz'); + $this->assertSession()->pageTextContains('foo baz'); + $this->assertFacetLabel('item'); + + $this->clickLink('item'); + $this->checkFacetIsActive('item'); + $this->assertSession()->pageTextContains('foo baz'); + $this->assertSession()->pageTextContains('bar baz'); + $this->assertSession()->pageTextNotContains('foo bar baz'); + + $this->updateFacet($facet_id, ['exclude' => FALSE]); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('foo bar baz'); + $this->assertSession()->pageTextContains('foo baz'); + $this->assertFacetLabel('item'); + + $this->clickLink('item'); + $this->checkFacetIsActive('item'); + $this->assertSession()->pageTextContains('foo bar baz'); + $this->assertSession()->pageTextContains('foo test'); + $this->assertSession()->pageTextContains('bar'); + $this->assertSession()->pageTextNotContains('foo baz'); + } + + /** + * Tests the facet's exclude functionality for a date field. + * + * Cached implementation of testExcludeFacetDate integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testExcludeFacetDate() + */ + public function testExcludeFacetDate() { + $facet_id = $field_name = 'created'; + + $this->entityTestStorage->create([ + 'name' => 'foo new', + 'body' => 'test test', + 'type' => 'item', + 'keywords' => ['orange'], + 'category' => 'item_category', + $field_name => 1490000000, + ])->save(); + + $this->entityTestStorage->create([ + 'name' => 'foo old', + 'body' => 'test test', + 'type' => 'item', + 'keywords' => ['orange'], + 'category' => 'item_category', + $field_name => 1460000000, + ])->save(); + + $this->assertEquals(2, $this->indexItems($this->indexId), '2 items were indexed.'); + + $this->createFacet('Created', $facet_id, $field_name, static::VIEW_DISPLAY); + $facet = $this->getFacetById($facet_id); + $facet->addProcessor([ + 'processor_id' => 'date_item', + 'weights' => ['build' => 35], + 'settings' => [ + 'date_display' => 'actual_date', + 'granularity' => SearchApiDate::FACETAPI_DATE_MONTH, + 'hierarchy' => FALSE, + 'date_format' => '', + ], + ]); + $this->facetStorage->save($facet); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('foo old'); + $this->assertSession()->pageTextContains('foo new'); + $this->clickLink('March 2017'); + $this->checkFacetIsActive('March 2017'); + $this->assertSession()->pageTextContains('foo new'); + $this->assertSession()->pageTextNotContains('foo old'); + + $this->updateFacet($facet->id(), ['exclude' => TRUE]); + + $this->drupalGet(static::VIEW_URL); + $this->clickLink('March 2017'); + $this->checkFacetIsActive('March 2017'); + $this->assertSession()->pageTextContains('foo old'); + $this->assertSession()->pageTextNotContains('foo new'); + } + + /** + * Tests allow only one active item. + * + * Cached implementation of testAllowOneActiveItem integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testAllowOneActiveItem() + */ + public function testAllowOneActiveItem() { + $this->createFacet('Spotted wood owl', 'spotted_wood_owl', 'keywords', static::VIEW_DISPLAY); + + $facet = $this->getFacetById('spotted_wood_owl'); + $facet->setShowOnlyOneResult(TRUE); + $this->facetStorage->save($facet); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('grape'); + $this->assertFacetLabel('orange'); + + $this->clickLink('grape'); + $this->assertSession()->pageTextContains('Displaying 3 search results'); + $this->checkFacetIsActive('grape'); + $this->assertFacetLabel('orange'); + + $this->clickLink('orange'); + $this->assertSession()->pageTextContains('Displaying 3 search results'); + $this->assertFacetLabel('grape'); + $this->checkFacetIsActive('orange'); + } + + /** + * Tests calculations of facet count. + * + * Cached implementation of testFacetCountCalculations integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testFacetCountCalculations() + */ + public function testFacetCountCalculations() { + $this->createFacet('Type', 'type', 'type', static::VIEW_DISPLAY); + $this->createFacet('Keywords', 'keywords', 'keywords', static::VIEW_DISPLAY); + foreach (['type', 'keywords'] as $facet_id) { + $this->updateFacet($facet_id, ['query_operator' => 'and']); + } + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('grape (3)'); + + // Make sure that after clicking on article, which has only 2 entities, + // there are only 2 items left in the results for other facets as well. + // In this case, that means we can't have 3 entities tagged with grape. Both + // remaining entities are tagged with grape and strawberry. + $this->clickPartialLink('article'); + $this->assertSession()->pageTextNotContains('(3)'); + $this->assertFacetLabel('grape (2)'); + $this->assertFacetLabel('strawberry (2)'); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('grape (3)'); + + // Make sure that after clicking on grape, which has only 3 entities, there + // are only 3 items left in the results for other facets as well. In this + // case, that means 2 entities of type article and 1 item. + $this->clickPartialLink('grape'); + $this->assertSession()->pageTextContains('Displaying 3 search results'); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('item (1)'); + } + + /** + * Tests the hard limit setting. + * + * Cached implementation of testHardLimit integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testHardLimit() + */ + public function testHardLimit() { + $this->createFacet('Owl', 'owl', 'keywords', static::VIEW_DISPLAY); + $facet = $this->getFacetById('owl'); + $facet->addProcessor([ + 'processor_id' => 'active_widget_order', + 'weights' => ['sort' => 20], + 'settings' => [], + ]); + $facet->addProcessor([ + 'processor_id' => 'display_value_widget_order', + 'weights' => ['build' => 40], + 'settings' => [], + ]); + $this->facetStorage->save($facet); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('grape (3)'); + $this->assertFacetLabel('orange (3)'); + $this->assertFacetLabel('apple (2)'); + $this->assertFacetLabel('banana (1)'); + $this->assertFacetLabel('strawberry (2)'); + + $this->updateFacet($facet->id(), ['hard_limit' => 3]); + + $this->drupalGet(static::VIEW_URL); + // We're still testing for 5 search results here, the hard limit only limits + // the facets, not the search results. + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('grape (3)'); + $this->assertFacetLabel('orange (3)'); + $this->assertFacetLabel('apple (2)'); + $this->assertSession()->pageTextNotContains('banana (0)'); + $this->assertSession()->pageTextNotContains('strawberry (0)'); + } + + /** + * Test minimum amount of items. + * + * Cached implementation of testMinimumAmount integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testMinimumAmount() + */ + public function testMinimumAmount() { + $this->createFacet('Elf owl', 'elf_owl', 'type', static::VIEW_DISPLAY); + $this->updateFacet('elf_owl', ['min_count' => 1]); + + // See that both article and item are showing. + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('item (3)'); + + $this->updateFacet('elf_owl', ['min_count' => 3]); + + // See that article is now hidden, item should still be showing. + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertSession()->pageTextNotContains('article'); + $this->assertFacetLabel('item (3)'); + } + + /** + * Tests the visibility of facet source. + * + * Cached implementation of testFacetSourceVisibility integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testFacetSourceVisibility() + */ + public function testFacetSourceVisibility() { + $this->createFacet('Vicuña', 'vicuna', 'type', static::VIEW_DISPLAY); + // Facet appears only on the search page for which it was created. + $this->drupalGet(static::VIEW_URL); + $this->assertFacetBlocksAppear(); + $this->drupalGet(''); + $this->assertNoFacetBlocksAppear(); + + $facet = $this->getFacetById('vicuna'); + $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE); + $this->facetStorage->save($facet); + + // Test that the facet source is visible on the search page and user/2 page. + $this->drupalGet(static::VIEW_URL); + $this->assertFacetBlocksAppear(); + $this->drupalGet(''); + $this->assertFacetBlocksAppear(); + } + + /** + * Tests behavior with multiple enabled facets and their interaction. + * + * Cached implementation of testMultipleFacets integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testMultipleFacets() + */ + public function testMultipleFacets() { + // Create 2 facets. + $this->createFacet('Snow Owl', 'snow_owl', 'type', static::VIEW_DISPLAY); + $this->createFacet('Forest Owl', 'forest_owl', 'category', static::VIEW_DISPLAY); + + foreach (['snow_owl', 'forest_owl'] as $facet_id) { + $this->updateFacet($facet_id, ['min_count' => 0]); + } + + // Go to the view and check the default behavior. + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->assertFacetLabel('item (3)'); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('item_category (2)'); + $this->assertFacetLabel('article_category (2)'); + + // Start filtering. + $this->clickPartialLink('item_category'); + $this->assertSession()->pageTextContains('Displaying 2 search results'); + $this->checkFacetIsActive('item_category'); + $this->assertFacetLabel('item (2)'); + + // Go back to the overview and start another filter, from the second facet + // block this time. + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextContains('Displaying 5 search results'); + $this->clickPartialLink('article (2)'); + $this->assertSession()->pageTextContains('Displaying 2 search results'); + $this->checkFacetIsActive('article'); + $this->assertFacetLabel('article_category (2)'); + $this->assertFacetLabel('item_category (0)'); + } + + /** + * Tests that the configuration for showing a title works. + * + * Cached implementation of testShowTitle integration test. + * + * @see \Drupal\Tests\facets\Functional\IntegrationTest::testShowTitle() + */ + public function testShowTitle() { + $this->createFacet('Llama', 'llama', 'type', static::VIEW_DISPLAY); + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->pageTextNotContains('Llama'); + + $this->updateFacet('llama', ['show_title' => TRUE]); + + $this->drupalGet(static::VIEW_URL); + $this->assertSession()->responseContains('

Llama

'); + $this->assertSession()->pageTextContains('Llama'); + } + + /** + * Test facet blocks cache invalidation. + * + * Test covers search page with a facets and standalone facet block on FP. + */ + public function testFacetBlockCacheNewContentIndexing() { + $this->createFacet('Test Facet name', 'test_facet_name', 'type', static::VIEW_DISPLAY); + + $facet = $this->getFacetById('test_facet_name'); + $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE); + $this->facetStorage->save($facet); + + foreach (['', static::VIEW_URL] as $url) { + $this->drupalGet($url); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('item (3)'); + } + + $this->entityTestStorage->create([ + 'name' => 'foo jiz baz', + 'body' => 'test test and a bit more test', + 'type' => 'item', + 'keywords' => ['orange', 'black'], + 'category' => 'item_category', + ])->save(); + + // Entity was added but not indexed yet, so facet state should remain the + // same. + foreach (['', static::VIEW_URL] as $url) { + $this->drupalGet($url); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('item (3)'); + } + + // Index 1 remaining item and check that count has been updated. + $this->assertEquals(1, $this->indexItems($this->indexId), '1 item was indexed.'); + foreach (['', static::VIEW_URL] as $url) { + $this->drupalGet($url); + $this->assertFacetLabel('article (2)'); + $this->assertFacetLabel('item (4)'); + } + } + + /** + * Enable website page caching, set 1 day max age. + */ + protected function enableWebsiteCache() { + $max_age = 86400; + $this->config('system.performance') + ->set('cache.page.max_age', $max_age) + ->save(); + $this->drupalGet(static::VIEW_URL); + $this->assertSession() + ->responseHeaderContains('Cache-Control', 'max-age=' . $max_age); + } + + /** + * Get facet entity by ids. + * + * @param string $id + * Facet id. + * + * @return \Drupal\facets\FacetInterface + * Loaded facet object. + */ + protected function getFacetById(string $id): FacetInterface { + return $this->facetStorage->load($id); + } + + /** + * Update facet tith with given values. + * + * @param string $id + * The facet entity ID. + * @param array $settings + * Array with values keyed by property names. + * + * @return \Drupal\facets\FacetInterface + * An updated facet entity. + */ + protected function updateFacet(string $id, array $settings): FacetInterface { + $facet = $this->getFacetById($id); + foreach ($settings as $name => $value) { + $facet->set($name, $value); + } + $this->facetStorage->save($facet); + + return $facet; + } + +} diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationTest.php index 59974200c..a7c3e809b 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/IntegrationTest.php @@ -202,7 +202,7 @@ class IntegrationTest extends FacetsTestBase { $this->assertFacetLabel('article'); $this->clickLink('item'); - $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f[0]' => 'ab_facet:item']]); + $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f' => ['ab_facet:item']]]); $this->assertSession()->addressEquals($url); $this->drupalGet($facet_edit_page); @@ -213,7 +213,7 @@ class IntegrationTest extends FacetsTestBase { $this->assertFacetLabel('article'); $this->clickLink('item'); - $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f[0]' => 'llama:item']]); + $url = Url::fromUserInput('/search-api-test-fulltext', ['query' => ['f' => ['llama:item']]]); $this->assertSession()->addressEquals($url); } @@ -811,7 +811,7 @@ class IntegrationTest extends FacetsTestBase { // Make sure numbers are displayed. $edit = [ 'widget_config[show_numbers]' => 1, - 'facet_settings[min_count]' => 0, + 'facet_settings[min_count]' => 1, ]; $this->drupalGet('admin/config/search/facets/snow_owl/edit'); $this->submitForm($edit, 'Save'); @@ -840,7 +840,9 @@ class IntegrationTest extends FacetsTestBase { $this->assertSession()->pageTextContains('Displaying 2 search results'); $this->checkFacetIsActive('article'); $this->assertFacetLabel('article_category (2)'); - $this->assertFacetLabel('item_category (0)'); + // As min_count=1 and query_operator='and' we expect zero-result + // item_category to be hidden, see testMultipleFacets(). + $this->assertSession()->pageTextNotContains('item_category'); } /** @@ -873,29 +875,34 @@ class IntegrationTest extends FacetsTestBase { * Check that the disabling of the cache works. */ public function testViewsCacheDisable() { - // Load the view, verify cache settings. - $view = Views::getView('search_api_test_view'); - $view->setDisplay('page_1'); - $current_cache = $view->display_handler->getOption('cache'); - $this->assertEquals('none', $current_cache['type']); - $view->display_handler->setOption('cache', ['type' => 'tag']); - $view->save(); - $current_cache = $view->display_handler->getOption('cache'); - $this->assertEquals('tag', $current_cache['type']); - - // Create a facet and check for the cache disabled message. - $id = "western_screech_owl"; - $name = "Western screech owl"; - $this->createFacet($name, $id); - $this->drupalGet('admin/config/search/facets/' . $id . '/settings'); - $this->submitForm([], 'Save'); - $this->assertSession()->pageTextContains('Caching of view Search API Test Fulltext search view has been disabled.'); - - // Check the view's cache settings again to see if they've been updated. - $view = Views::getView('search_api_test_view'); - $view->setDisplay('page_1'); - $current_cache = $view->display_handler->getOption('cache'); - $this->assertEquals('none', $current_cache['type']); + $caches = [ + // Tag cache plugin should be replaced by none, as it's not supported. + 'page_1' => 'none', + // Search API cache plugin shouldn't be changed. + 'page_2_sapi_tag' => 'search_api_tag', + 'page_2_sapi_time' => 'search_api_time', + ]; + foreach ($caches as $display_id => $expected_cache_plugin) { + // Create a facet and check for the cache disabled message. + $id = 'western_screech_owl_' . $display_id; + $name = 'Western screech owl'; + $this->createFacet($name, $id, 'type', $display_id); + $this->drupalGet('admin/config/search/facets/' . $id . '/settings'); + $this->submitForm([], 'Save'); + $warning = 'You may experience issues, because Search API Test Fulltext search view use cache. In case you will try to turn set cache plugin to none.'; + if ($display_id === 'page_1') { + // Make sure that user will get a warning about source cache plugin. + $this->assertSession()->pageTextNotContains($warning); + } + else { + $this->assertSession()->pageTextContains($warning); + } + // Check the view's cache settings again to see if they've been updated. + $view = Views::getView('search_api_test_view'); + $view->setDisplay($display_id); + $current_cache = $view->display_handler->getOption('cache'); + $this->assertEquals($expected_cache_plugin, $current_cache['type']); + } } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/Rest/FacetResourceTestBase.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/Rest/FacetResourceTestBase.php index 88b22934a..5301a54fc 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/Rest/FacetResourceTestBase.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/Rest/FacetResourceTestBase.php @@ -77,6 +77,8 @@ abstract class FacetResourceTestBase extends EntityResourceTestBase { 'id' => 'owl', 'langcode' => 'en', 'min_count' => 1, + 'missing' => FALSE, + 'missing_label' => 'others', 'name' => NULL, 'only_visible_when_facet_source_is_visible' => NULL, 'processor_configs' => [ @@ -113,4 +115,17 @@ abstract class FacetResourceTestBase extends EntityResourceTestBase { // @todo Update after https://www.drupal.org/node/2300677. } + /** + * The expected cache contexts for the GET/HEAD response of the test entity. + * + * @see ::testGet + * + * @return string[] + */ + protected function getExpectedCacheContexts() { + return array_merge(parent::getExpectedCacheContexts(), [ + 'url.query_args', + ]); + } + } diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/UrlIntegrationTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/UrlIntegrationTest.php index b8afb95d1..027ae868e 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/UrlIntegrationTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Functional/UrlIntegrationTest.php @@ -66,7 +66,7 @@ class UrlIntegrationTest extends FacetsTestBase { // Go to the only enabled facet source's config and change the filter key. $this->drupalGet('admin/config/search/facets'); - $this->clickLink('Configure', 1); + $this->clickLink('Configure', 2); $edit = [ 'filter_key' => 'y', @@ -90,7 +90,7 @@ class UrlIntegrationTest extends FacetsTestBase { // Go to the only enabled facet source's config and change the url // processor. $this->drupalGet('admin/config/search/facets'); - $this->clickLink('Configure', 1); + $this->clickLink('Configure', 2); $edit = [ 'filter_key' => 'y', diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/Entity/FacetFacetSourceTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/Entity/FacetFacetSourceTest.php index e29daf4ca..c01a545ce 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/Entity/FacetFacetSourceTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/Entity/FacetFacetSourceTest.php @@ -31,7 +31,6 @@ class FacetFacetSourceTest extends EntityKernelTestBase { 'search_api_db', 'search_api_test_db', 'search_api_test_example_content', - 'search_api_test_views', 'views', 'rest', 'serialization', @@ -60,7 +59,7 @@ class FacetFacetSourceTest extends EntityKernelTestBase { 'search_api_test_db', ]); - $this->installConfig('search_api_test_views'); + $this->installConfig('facets_search_api_dependency'); } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php index a840b44c4..72d19d74c 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Kernel/FacetManager/DefaultFacetManagerTest.php @@ -2,16 +2,18 @@ namespace Drupal\Tests\facets\Kernel\FacetManager; -use Drupal\facets\Entity\Facet; -use Drupal\KernelTests\KernelTestBase; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\facets\FacetInterface; +use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; /** * Provides the DefaultFacetManager test. * * @group facets - * @coversDefaultClass Drupal\facets\FacetManager\DefaultFacetManager + * @coversDefaultClass \Drupal\facets\FacetManager\DefaultFacetManager */ -class DefaultFacetManagerTest extends KernelTestBase { +class DefaultFacetManagerTest extends EntityKernelTestBase { /** * {@inheritdoc} @@ -19,16 +21,58 @@ class DefaultFacetManagerTest extends KernelTestBase { protected static $modules = [ 'facets', 'search_api', + 'search_api_db', + 'search_api_test_db', + 'facets_processors_collection', + 'facets_search_api_dependency', + 'facets_query_processor', 'system', 'user', + 'views', + 'rest', + 'serialization', ]; + /** + * Facets entity storage. + * + * @var \Drupal\Core\Config\Entity\ConfigEntityStorage + */ + protected $facetStorage; + + /** + * An instance of the "facets.manager" service. + * + * @var \Drupal\facets\FacetManager\DefaultFacetManager + */ + protected $facetManager; + /** * {@inheritdoc} */ public function setUp(): void { parent::setUp(); + $this->installEntitySchema('facets_facet'); + $this->installEntitySchema('entity_test_mulrev_changed'); + $this->installEntitySchema('search_api_task'); + + $state_service = \Drupal::state(); + $state_service->set('search_api_use_tracking_batch', FALSE); + + // Set tracking page size so tracking will work properly. + \Drupal::configFactory() + ->getEditable('search_api.settings') + ->set('tracking_page_size', 100) + ->save(); + + $this->installConfig([ + 'search_api_test_db', + 'facets_search_api_dependency', + ]); + + $this->facetStorage = $this->entityTypeManager->getStorage('facets_facet'); + $this->facetManager = $this->container->get('facets.manager'); } /** @@ -38,16 +82,15 @@ class DefaultFacetManagerTest extends KernelTestBase { */ public function testGetEnabledFacets() { /** @var \Drupal\facets\FacetManager\DefaultFacetManager $dfm */ - $dfm = \Drupal::service('facets.manager'); - $returnValue = $dfm->getEnabledFacets(); + $returnValue = $this->facetManager->getEnabledFacets(); $this->assertEmpty($returnValue); // Create a facet. - $entity = $this->createAndSaveFacet('test_facet'); + $entity = $this->createAndSaveFacet('Mercury', 'planets'); - $returnValue = $dfm->getEnabledFacets(); + $returnValue = $this->facetManager->getEnabledFacets(); $this->assertNotEmpty($returnValue); - $this->assertSame($entity->id(), $returnValue['test_facet']->id()); + $this->assertSame($entity->id(), $returnValue['Mercury']->id()); } /** @@ -56,73 +99,286 @@ class DefaultFacetManagerTest extends KernelTestBase { * @covers ::getFacetsByFacetSourceId */ public function testGetFacetsByFacetSourceId() { - /** @var \Drupal\facets\FacetManager\DefaultFacetManager $dfm */ - $dfm = \Drupal::service('facets.manager'); - $this->assertEmpty($dfm->getFacetsByFacetSourceId('planets')); + $this->assertEmpty($this->facetManager->getFacetsByFacetSourceId('planets')); // Create 2 different facets with a unique facet source id. - $entity = $this->createAndSaveFacet('Jupiter'); - $entity->setFacetSourceId('planets'); - $entity->save(); - $entity = $this->createAndSaveFacet('Pluto'); - $entity->setFacetSourceId('former_planets'); - $entity->save(); + $this->createAndSaveFacet('Jupiter', 'planets'); + $this->createAndSaveFacet('Pluto', 'former_planets'); - $planetFacets = $dfm->getFacetsByFacetSourceId('planets'); + $planetFacets = $this->facetManager->getFacetsByFacetSourceId('planets'); $this->assertNotEmpty($planetFacets); $this->assertCount(1, $planetFacets); $this->assertSame('Jupiter', $planetFacets['Jupiter']->id()); - $formerPlanetFacets = $dfm->getFacetsByFacetSourceId('former_planets'); + $formerPlanetFacets = $this->facetManager->getFacetsByFacetSourceId('former_planets'); $this->assertNotEmpty($formerPlanetFacets); $this->assertCount(1, $formerPlanetFacets); $this->assertSame('Pluto', $formerPlanetFacets['Pluto']->id()); // Make pluto a planet again. + $entity = $this->facetStorage->load('Pluto'); $entity->setFacetSourceId('planets'); - $entity->save(); + $this->facetStorage->save($entity); // Test that we now hit the static cache. - $planetFacets = $dfm->getFacetsByFacetSourceId('planets'); + $planetFacets = $this->facetManager->getFacetsByFacetSourceId('planets'); $this->assertNotEmpty($planetFacets); $this->assertCount(1, $planetFacets); // Change the 'facets' property on the manager to public, so we can // overwrite it here. This is because otherwise we run into the static // caches. - $facetsProperty = new \ReflectionProperty($dfm, 'facets'); - $facetsProperty->setAccessible(TRUE); - $facetsProperty->setValue($dfm, []); + $this->resetFacetsManagerStaticCache(); // Now that the static cache is reset, test that we have 2 planets. - $planetFacets = $dfm->getFacetsByFacetSourceId('planets'); + $planetFacets = $this->facetManager->getFacetsByFacetSourceId('planets'); $this->assertNotEmpty($planetFacets); $this->assertCount(2, $planetFacets); $this->assertSame('Jupiter', $planetFacets['Jupiter']->id()); $this->assertSame('Pluto', $planetFacets['Pluto']->id()); } + /** + * Tests the cachebillity data passed into search query. + */ + public function testAlterQueryCacheabilityMetadata() { + // @see facets_processors_collection_facets_search_api_query_type_mapping_alter(). + \Drupal::state()->set('facets_processors_collection_alter_string_query_handler', TRUE); + + $view = $this->entityTypeManager + ->getStorage('view') + ->load('search_api_test_view') + ->getExecutable(); + $view->setDisplay('page_1'); + + $query = $view->getQuery()->getSearchApiQuery(); + + // Create facets for a SAPI view display. + $facet_source = 'search_api:views_page__search_api_test_view__page_1'; + $facet_mars = $this->createAndSaveFacet('Mars', $facet_source); + $facet_neptune = $this->createAndSaveFacet('Neptune', $facet_source); + $expected_tags = array_unique(array_merge( + $query->getCacheTags(), + $facet_mars->getCacheTags(), + $facet_neptune->getCacheTags(), + [ + 'fpc:query_plugin_type_plugin', + 'dummy_query_pre_query_tag', + ] + )); + $this->resetFacetsManagerStaticCache(); + $expected_contexts = array_unique(array_merge( + $query->getCacheContexts(), + $facet_mars->getFacetSource()->getCacheContexts(), + [ + 'fpc_query_type_plugin', + 'dummy_query_pre_query', + ] + )); + + // Make sure that query cachebillity will include facets cache tags e.g. + // view results will depends on the facet configuration. + $this->facetManager->alterQuery($query, $facet_source); + $this->assertCacheabillityArrays($expected_contexts, $query->getCacheContexts()); + $this->assertCacheabillityArrays($expected_tags, $query->getCacheTags()); + } + + /** + * Tests the cachebillity data passed into search query. + * + * @param string $facet_source_id + * The tested facet source ID. + * @param array $expected_metadata + * The expected cache metadata for the given facet source. + * + * @dataProvider testBuildCacheabilityMetadataProvider + */ + public function testBuildCacheabilityMetadata(string $facet_source_id, array $expected_metadata) { + $facet = $this->createAndSaveFacet('mars', $facet_source_id); + + $cacheable_processors = [ + 'fpc_post_query_processor', + 'fpc_build_processor', + 'fpc_sort_processor', + ]; + + foreach ($cacheable_processors as $processor) { + $facet->addProcessor([ + 'processor_id' => $processor, + 'weights' => [], + 'settings' => [], + ]); + } + + $facet->setOnlyVisibleWhenFacetSourceIsVisible(FALSE); + $this->facetStorage->save($facet); + // Make sure that new processor is taken into consideration. + $this->resetFacetsManagerStaticCache(); + + $build = []; + $metadata = CacheableMetadata::createFromObject($facet); + $metadata->applyTo($build); + $this->assertEquals($expected_metadata['max-age'], $build['#cache']['max-age']); + $this->assertCacheabillityArrays($expected_metadata['contexts'], $build['#cache']['contexts']); + $this->assertCacheabillityArrays($expected_metadata['tags'], $build['#cache']['tags']); + + $facet->removeProcessor('fpc_sort_processor'); + // Test that un-cacheable plugin kills the cache. + $facet->addProcessor([ + 'processor_id' => 'fpc_sort_random_processor', + 'settings' => [], + 'weights' => [], + ]); + + $this->facetStorage->save($facet); + $this->resetFacetsManagerStaticCache(); + + $build = []; + $metadata = CacheableMetadata::createFromObject($facet); + $metadata->applyTo($build); + + $this->assertEquals(0, $build['#cache']['max-age']); + $this->assertCacheabillityArrays($expected_metadata['contexts'], $build['#cache']['contexts']); + $this->assertCacheabillityArrays($expected_metadata['tags'], $build['#cache']['tags']); + } + /** * Create and save a facet, for usage in test-scenario's. * * @param string $id * The id. + * @param string $source + * The source id. * * @return \Drupal\facets\FacetInterface * The newly created facet. */ - protected function createAndSaveFacet($id) { - // Create a facet. - $entity = Facet::create([ + protected function createAndSaveFacet(string $id, string $source): FacetInterface { + /** @var \Drupal\facets\FacetInterface $facet */ + $facet = $this->facetStorage->create([ 'id' => $id, 'name' => 'Test facet', ]); - $entity->setWidget('links'); - $entity->setEmptyBehavior(['behavior' => 'none']); - $entity->setFacetSourceId('fluffy'); - $entity->save(); + $facet->setWidget('links'); + $facet->setFieldIdentifier('type'); + $facet->setEmptyBehavior(['behavior' => 'none']); + $facet->setFacetSourceId($source); - return $entity; + $facet->addProcessor([ + 'processor_id' => 'url_processor_handler', + 'settings' => [], + 'weights' => [], + ]); + + $this->facetStorage->save($facet); + // Add dummy processor instead of default, to test its cachebillity. + $source = $facet->getFacetSourceConfig(); + $source->setUrlProcessor('dummy_query'); + $source->save(); + + return $facet; + } + + /** + * Reset static facets.manager static cache. + * + * @todo discuss whether or not this should be done automatically when facet + * gets inserted/updated or deleted. + */ + protected function resetFacetsManagerStaticCache() { + foreach (['builtFacets', 'facets', 'processedFacets'] as $prop) { + $facetsProperty = new \ReflectionProperty($this->facetManager, $prop); + $facetsProperty->setAccessible(TRUE); + $facetsProperty->setValue($this->facetManager, []); + $facetsProperty->setAccessible(FALSE); + } + } + + /** + * Assert that actual cachebillity matches expected one. + */ + public function assertCacheabillityArrays($expected, $actual, string $message = ''): void { + foreach ([&$expected, &$actual] as &$array) { + foreach ($array as &$value) { + if (is_array($value)) { + sort($value); + } + } + } + sort($expected); + sort($actual); + $this->assertEquals($expected, $actual, $message); + } + + /** + * Data provider for testBuildCacheabilityMetadata(). + * + * @return array + * Array of method call argument arrays for testBuildCacheabilityMetadata(). + * + * @see ::testBuildCacheabilityMetadata + */ + public function testBuildCacheabilityMetadataProvider() { + $basic = [ + 'contexts' => [ + // Facet API uses Request query params to populate active facets values. + 'url.query_args', + // Added by build fpc_post_query_processor process plugin. + 'fpc_post_query', + // Added by build fpc_build_processor process plugin. + 'fpc_build', + // Added by build fpc_sort_processor process plugin. + 'fpc_sort', + // Added by Url "dummy_query" url processor. + 'dummy_query_pre_query', + // Added by views view source plugin. + 'url', + 'languages:language_interface', + ], + 'tags' => [ + // Facet controls query and look & feel of the facet results, so it's + // config should be present as a cache dependency. + 'config:facets.facet.mars', + // Added by build fpc_post_query_processor process plugin. + 'fpc:post_query_processor', + // Added by Url "dummy_query" url processor. + 'dummy_query_pre_query_tag', + // Added by build fpc_build_processor process plugin. + 'fpc:build_processor', + // Added by build fpc_sort_processor process plugin. + 'fpc:sort_processor', + // Added by views view source plugin. + 'config:views.view.search_api_test_view', + 'config:search_api.index.database_search_index', + ], + ]; + return [ + // Expected cacheability for the facet with a source with disabled cache. + [ + 'search_api:views_page__search_api_test_view__page_1', + $basic + ['max-age' => 0], + ], + // Expected cacheability for the facet with a source that has TAG cache + // strategy. + [ + 'search_api:views_page__search_api_test_view__page_2_sapi_tag', + array_merge_recursive( + $basic, + [ + 'tags' => [ + 'search_api_list:database_search_index', + ], + 'max-age' => Cache::PERMANENT, + ] + ), + ], + // Expected cacheability for the facet with a source that has TIME cache + // strategy. + [ + 'search_api:views_page__search_api_test_view__page_2_sapi_time', + $basic + ['max-age' => 518400], + ], + ]; } } diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/DependentFacetProcessorTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/DependentFacetProcessorTest.php index ac717d472..6d90b716d 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/DependentFacetProcessorTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/DependentFacetProcessorTest.php @@ -2,13 +2,18 @@ namespace Drupal\Tests\facets\Unit\Plugin\processor; +use Drupal\Core\Config\Entity\ConfigEntityType; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\facets\Entity\Facet; use Drupal\facets\FacetManager\DefaultFacetManager; use Drupal\facets\Plugin\facets\processor\DependentFacetProcessor; +use Drupal\facets\Processor\ProcessorPluginManager; use Drupal\facets\Result\Result; use Drupal\Tests\UnitTestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; /** * Unit test for processor. @@ -36,6 +41,35 @@ class DependentFacetProcessorTest extends UnitTestCase { new Result($facet, 'church_owl', 'Church owl', 1), new Result($facet, 'barn_owl', 'Barn owl', 1), ]; + + $facet_entity_type = $this->getMockBuilder(ConfigEntityType::class) + ->disableOriginalConstructor() + ->getMock(); + $facet_entity_type->method('getConfigPrefix') + ->willReturn('facets.facet'); + + $entity_type_manager = $this->getMockBuilder(EntityTypeManager::class) + ->disableOriginalConstructor() + ->getMock(); + $entity_type_manager->method('getDefinition') + ->with('facets_facet') + ->willReturn($facet_entity_type); + + $processor_plugin_manager = $this->getMockBuilder(ProcessorPluginManager::class) + ->disableOriginalConstructor() + ->getMock(); + $processor_plugin_manager->method('getDefinitions') + ->willReturn([]); + + $event_dispatcher = $this->getMockBuilder(EventDispatcher::class) + ->disableOriginalConstructor() + ->getMock(); + + $container = new ContainerBuilder(); + $container->set('entity_type.manager', $entity_type_manager); + $container->set('plugin.manager.facets.processor', $processor_plugin_manager); + $container->set('event_dispatcher', $event_dispatcher); + \Drupal::setContainer($container); } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/ListItemProcessorTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/ListItemProcessorTest.php index 46d95746b..9e9d96e77 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/ListItemProcessorTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/processor/ListItemProcessorTest.php @@ -2,19 +2,24 @@ namespace Drupal\Tests\facets\Unit\Plugin\processor; +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityTypeBundleInfo; +use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\TypedData\ComplexDataDefinitionInterface; use Drupal\facets\Entity\Facet; use Drupal\facets\FacetSource\FacetSourcePluginInterface; use Drupal\facets\FacetSource\FacetSourcePluginManager; use Drupal\facets\Plugin\facets\processor\ListItemProcessor; +use Drupal\facets\Processor\ProcessorPluginManager; use Drupal\facets\Result\Result; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\UnitTestCase; use Drupal\Core\Config\ConfigManager; use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Field\BaseFieldDefinition; +use Symfony\Component\EventDispatcher\EventDispatcher; /** * Unit test for processor. @@ -37,6 +42,8 @@ class ListItemProcessorTest extends UnitTestCase { */ protected $results; + protected $processor_plugin_manager; + /** * Creates a new processor object for use in the tests. */ @@ -74,6 +81,15 @@ class ListItemProcessorTest extends UnitTestCase { $facet_source->expects($this->any()) ->method('getDataDefinition') ->willReturn($data_definition); + $facet_source->expects($this->any()) + ->method('getCacheContexts') + ->willReturn([]); + $facet_source->expects($this->any()) + ->method('getCacheTags') + ->willReturn([]); + $facet_source->expects($this->any()) + ->method('getCacheMaxAge') + ->willReturn(CacheBackendInterface::CACHE_PERMANENT); // Add the plugin manager. $pluginManager = $this->getMockBuilder(FacetSourcePluginManager::class) @@ -88,8 +104,34 @@ class ListItemProcessorTest extends UnitTestCase { $this->processor = new ListItemProcessor([], 'list_item', [], $config_manager, $entity_field_manager, $entity_type_bundle_info); + $facet_entity_type = $this->getMockBuilder(ConfigEntityType::class) + ->disableOriginalConstructor() + ->getMock(); + $facet_entity_type->method('getConfigPrefix') + ->willReturn('facets.facet'); + + $entity_type_manager = $this->getMockBuilder(EntityTypeManager::class) + ->disableOriginalConstructor() + ->getMock(); + $entity_type_manager->method('getDefinition') + ->with('facets_facet') + ->willReturn($facet_entity_type); + + $this->processor_plugin_manager = $this->getMockBuilder(ProcessorPluginManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->processor_plugin_manager->method('getDefinitions') + ->willReturn(['list_item' => ['class' => ListItemProcessor::class]]); + + $event_dispatcher = $this->getMockBuilder(EventDispatcher::class) + ->disableOriginalConstructor() + ->getMock(); + $container = new ContainerBuilder(); $container->set('plugin.manager.facets.facet_source', $pluginManager); + $container->set('entity_type.manager', $entity_type_manager); + $container->set('plugin.manager.facets.processor', $this->processor_plugin_manager); + $container->set('event_dispatcher', $event_dispatcher); \Drupal::setContainer($container); } @@ -100,6 +142,16 @@ class ListItemProcessorTest extends UnitTestCase { $module_field = $this->getMockBuilder(FieldStorageConfig::class) ->disableOriginalConstructor() ->getMock(); + // Return cache field metadata. + $module_field->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $module_field->expects($this->exactly(1)) + ->method('getCacheTags') + ->willReturn(['module_field_tag']); + $module_field->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(12345); // Make sure that when the processor calls loadConfigEntityByName the field // we created here is called. @@ -119,6 +171,8 @@ class ListItemProcessorTest extends UnitTestCase { ->getMock(); $processor = new ListItemProcessor([], 'list_item', [], $config_manager, $entity_field_manager, $entity_type_bundle_info); + $this->processor_plugin_manager->method('createInstance') + ->willReturn($processor); // Config entity field facet. $module_field_facet = new Facet([], 'facets_facet'); @@ -131,13 +185,15 @@ class ListItemProcessorTest extends UnitTestCase { 'settings' => [], ]); - /** @var \Drupal\facets\Result\Result[] $module_field_facet- */ + /** @var \Drupal\facets\Result\Result[] $module_field_results */ $module_field_results = $processor->build($module_field_facet, $this->results); $this->assertCount(3, $module_field_results); $this->assertEquals('llama', $module_field_results[0]->getDisplayValue()); $this->assertEquals('badger', $module_field_results[1]->getDisplayValue()); $this->assertEquals('kitten', $module_field_results[2]->getDisplayValue()); + $this->assertContains('module_field_tag', $module_field_facet->getCacheTags()); + $this->assertEquals(12345, $module_field_facet->getCacheMaxAge()); } /** @@ -147,6 +203,16 @@ class ListItemProcessorTest extends UnitTestCase { $module_field = $this->getMockBuilder(FieldStorageConfig::class) ->disableOriginalConstructor() ->getMock(); + // Return cache field metadata. + $module_field->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $module_field->expects($this->exactly(1)) + ->method('getCacheTags') + ->willReturn(['module_field_tag']); + $module_field->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(54321); $config_manager = $this->getMockBuilder(ConfigManager::class) ->disableOriginalConstructor() @@ -164,6 +230,8 @@ class ListItemProcessorTest extends UnitTestCase { ->getMock(); $processor = new ListItemProcessor([], 'list_item', [], $config_manager, $entity_field_manager, $entity_type_bundle_info); + $this->processor_plugin_manager->method('createInstance') + ->willReturn($processor); // Config entity field facet. $module_field_facet = new Facet([], 'facets_facet'); @@ -175,6 +243,8 @@ class ListItemProcessorTest extends UnitTestCase { 'weights' => [], 'settings' => [], ]); + $cache_tags = $module_field_facet->getCacheTags(); + /** @var \Drupal\facets\Result\Result[] $module_field_facet- */ $module_field_results = $processor->build($module_field_facet, $this->results); @@ -182,6 +252,8 @@ class ListItemProcessorTest extends UnitTestCase { $this->assertEquals('llama', $module_field_results[0]->getDisplayValue()); $this->assertEquals('badger', $module_field_results[1]->getDisplayValue()); $this->assertEquals('kitten', $module_field_results[2]->getDisplayValue()); + $this->assertSame(array_merge($cache_tags, ['module_field_tag']), $module_field_facet->getCacheTags()); + $this->assertEquals(54321, $module_field_facet->getCacheMaxAge()); } /** @@ -195,6 +267,16 @@ class ListItemProcessorTest extends UnitTestCase { $base_field = $this->getMockBuilder(BaseFieldDefinition::class) ->disableOriginalConstructor() ->getMock(); + // Return cache field metadata. + $base_field->expects($this->exactly(1)) + ->method('getCacheContexts') + ->willReturn([]); + $base_field->expects($this->exactly(1)) + ->method('getCacheTags') + ->willReturn(['base_field_tag']); + $base_field->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->willReturn(1235813); $entity_field_manager = $this->getMockBuilder(EntityFieldManager::class) ->disableOriginalConstructor() @@ -211,6 +293,8 @@ class ListItemProcessorTest extends UnitTestCase { ->getMock(); $processor = new ListItemProcessor([], 'list_item', [], $config_manager, $entity_field_manager, $entity_type_bundle_info); + $this->processor_plugin_manager->method('createInstance') + ->willReturn($processor); // Base prop facet. $base_prop_facet = new Facet([], 'facets_facet'); @@ -222,6 +306,7 @@ class ListItemProcessorTest extends UnitTestCase { 'weights' => [], 'settings' => [], ]); + $cache_tags = $base_prop_facet->getCacheTags(); /** @var \Drupal\facets\Result\Result[] $base_prop_results */ $base_prop_results = $processor->build($base_prop_facet, $this->results); @@ -230,6 +315,8 @@ class ListItemProcessorTest extends UnitTestCase { $this->assertEquals('llama', $base_prop_results[0]->getDisplayValue()); $this->assertEquals('badger', $base_prop_results[1]->getDisplayValue()); $this->assertEquals('kitten', $base_prop_results[2]->getDisplayValue()); + $this->assertSame(array_merge($cache_tags, ['base_field_tag']), $base_prop_facet->getCacheTags()); + $this->assertEquals(1235813, $base_prop_facet->getCacheMaxAge()); } /** diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/url_processor/QueryStringTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/url_processor/QueryStringTest.php index 6e5ecdef5..39e7312e8 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/url_processor/QueryStringTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/url_processor/QueryStringTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\facets\Unit\Plugin\url_processor; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Http\RequestStack; use Drupal\Core\Path\PathValidatorInterface; use Drupal\facets\Entity\Facet; use Drupal\facets\Entity\FacetSource; @@ -13,6 +14,8 @@ use Drupal\facets\FacetSource\FacetSourcePluginManager; use Drupal\facets\Plugin\facets\url_processor\QueryString; use Drupal\facets\Result\Result; use Drupal\facets\Result\ResultInterface; +use Drupal\facets\UrlProcessor\UrlProcessorPluginManager; +use Drupal\facets\Utility\FacetsUrlGenerator; use Drupal\Tests\Core\Routing\TestRouterInterface; use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -56,6 +59,13 @@ class QueryStringTest extends UnitTestCase { */ protected $eventDispatcher; + /** + * The URL generator. + * + * @var \Drupal\facets\Utility\FacetsUrlGenerator + */ + protected $urlGenerator; + /** * Creates a new processor object for use in the tests. */ @@ -63,6 +73,7 @@ class QueryStringTest extends UnitTestCase { parent::setUp(); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->urlGenerator = new FacetsUrlGenerator($this->createMock(UrlProcessorPluginManager::class), $this->createMock(EntityTypeManagerInterface::class)); $facet = new Facet([], 'facets_facet'); $this->originalResults = [ @@ -74,6 +85,8 @@ class QueryStringTest extends UnitTestCase { ]; $this->setContainer(); + + drupal_static_reset(); } /** @@ -82,7 +95,7 @@ class QueryStringTest extends UnitTestCase { public function testEmptyProcessorConfiguration() { $this->expectException(InvalidProcessorException::class); $this->expectExceptionMessage("The url processor doesn't have the required 'facet' in the configuration array."); - new QueryString([], 'test', [], new Request(), $this->entityManager, $this->eventDispatcher); + new QueryString([], 'test', [], new Request(), $this->entityManager, $this->eventDispatcher, $this->urlGenerator); } /** @@ -116,7 +129,7 @@ class QueryStringTest extends UnitTestCase { $request = new Request(); $request->query->set('f', ['test:badger']); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $entityTypeManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $entityTypeManager, $this->eventDispatcher, $this->urlGenerator); $this->processor->setActiveItems($facet); $this->assertEquals(['badger'], $facet->getActiveItems()); @@ -153,7 +166,7 @@ class QueryStringTest extends UnitTestCase { $request = new Request(); $request->query->set('f', ['test:badger', 'test:mushroom', 'donkey:kong']); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $entityTypeManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $entityTypeManager, $this->eventDispatcher, $this->urlGenerator); $this->processor->setActiveItems($facet); $this->assertEquals(['badger', 'mushroom'], $facet->getActiveItems()); @@ -170,7 +183,7 @@ class QueryStringTest extends UnitTestCase { $request = new Request(); $request->query->set('f', []); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, []); $this->assertEmpty($results); } @@ -187,7 +200,7 @@ class QueryStringTest extends UnitTestCase { $request = new Request(); $request->query->set('f', []); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $this->originalResults); $this->assertEquals('f', $this->processor->getFilterKey()); @@ -237,7 +250,7 @@ class QueryStringTest extends UnitTestCase { $request = new Request(); $request->query->set('f', ['king:kong']); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $entityTypeManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $entityTypeManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $original_results); /** @var \Drupal\facets\Result\ResultInterface $r */ @@ -265,7 +278,7 @@ class QueryStringTest extends UnitTestCase { $this->originalResults[1]->setActiveState(TRUE); $this->originalResults[2]->setActiveState(TRUE); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $this->originalResults); $this->assertEquals('route:test?f%5B0%5D=test%3A' . $results[0]->getRawValue(), $results[0]->getUrl()->toUriString()); @@ -305,7 +318,7 @@ class QueryStringTest extends UnitTestCase { $request = new Request(); $request->query->set('ab', []); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $this->originalResults); /** @var \Drupal\facets\Result\ResultInterface $r */ @@ -324,7 +337,7 @@ class QueryStringTest extends UnitTestCase { $facet->setUrlAlias('test'); $facet->setFacetSourceId('facet_source__dummy'); - $this->processor = new QueryString(['facet' => $facet, 'separator' => '__'], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet, 'separator' => '__'], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $this->originalResults); foreach ($results as $result) { @@ -359,7 +372,7 @@ class QueryStringTest extends UnitTestCase { $facet->setUrlAlias('test'); $facet->setFacetSourceId('facet_source__dummy'); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $this->originalResults); foreach ($results as $result) { @@ -379,10 +392,20 @@ class QueryStringTest extends UnitTestCase { ->method('matchRequest') ->willThrowException(new ResourceNotFoundException()); + $request = new Request(); + + $request_stack = $this->getMockBuilder(RequestStack::class) + ->disableOriginalConstructor() + ->getMock(); + $request_stack->expects($this->any()) + ->method('getCurrentRequest') + ->willReturn($request); + // Get the container from the setUp method and change it with the // implementation created here, that has the route parameters. $container = \Drupal::getContainer(); $container->set('router.no_access_checks', $router); + $container->set('request_stack', $request_stack); \Drupal::setContainer($container); // Create facet. @@ -391,7 +414,7 @@ class QueryStringTest extends UnitTestCase { $facet->setUrlAlias('test'); $facet->setFacetSourceId('facet_source__dummy'); - $this->processor = new QueryString(['facet' => $facet], 'query_string', [], new Request(), $this->entityManager, $this->eventDispatcher); + $this->processor = new QueryString(['facet' => $facet], 'query_string', [], $request, $this->entityManager, $this->eventDispatcher, $this->urlGenerator); $results = $this->processor->buildUrls($facet, $this->originalResults); diff --git a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/widget/LinksWidgetTest.php b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/widget/LinksWidgetTest.php index e344e282d..e23a4ae87 100644 --- a/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/widget/LinksWidgetTest.php +++ b/frontend/drupal9/web/modules/contrib/facets/tests/src/Unit/Plugin/widget/LinksWidgetTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\facets\FacetSource\FacetSourcePluginManager; use Drupal\facets\UrlProcessor\UrlProcessorInterface; +use Drupal\facets\Utility\FacetsUrlGenerator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\ParameterBag; @@ -273,10 +274,15 @@ class LinksWidgetTest extends WidgetTestBase { ->method('getStorage') ->willReturn($storage); + $facets_url_generator = $this->getMockBuilder(FacetsUrlGenerator::class) + ->disableOriginalConstructor() + ->getMock(); + $container = new ContainerBuilder(); $container->set('router.no_access_checks', $router); $container->set('entity_type.manager', $em); $container->set('plugin.manager.facets.url_processor', $manager); + $container->set('facets.utility.url_generator', $facets_url_generator); \Drupal::setContainer($container); } diff --git a/frontend/drupal9/web/modules/contrib/metatag/CHANGELOG.txt b/frontend/drupal9/web/modules/contrib/metatag/CHANGELOG.txt index b4abaf33e..4553d4786 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/CHANGELOG.txt +++ b/frontend/drupal9/web/modules/contrib/metatag/CHANGELOG.txt @@ -1,3 +1,69 @@ +Metatag 8.x-1.20, 2022-07-12 +---------------------------- +#3257588 by Eugene Bocharov, lobodacyril, jippie1948, Kasey_MK, djween, awasson, + DamienMcKenna, Valdes14, laura.florey, stacypendell: DB update hangs at + metatag 8109 - Update mask_icon values to the new structure. +#3258978 by heddn, DamienMcKenna: tokenize not in config schema for + metatag_display_extender. +#3258346 by Eugene Bocharov, jedgar1mx, 4kant, Renrhaf, DamienMcKenna, kriboogh: + Error: Call to a member function __wakeup() on null. +#3113761 by DamienMcKenna, thalles, rokzabukovec, Diego_Mow, urvashi_vora: + Coding standards cleanup. +#3261505 by DamienMcKenna, Eugene Bocharov, Sergiu Stici: In php 8.1 the explode + function throws a notice when default value is null. +#3265679 by DamienMcKenna: Additional coding standards bugs. +#3241256 by ndf, sabina.h, idebr, DamienMcKenna: Remove redundant HTTP headers + for canonical and short_link tags. +#3262308 by YahyaAlHamad, DamienMcKenna: metatag_update_8109 fails because of + unknown column. +#3261467 by DamienMcKenna, HnLn: in php 8.1 preg_match in + metatag_get_route_entity throws notice when route is null. +#3266326 by victoria-marina, Guilherme Rabelo, DamienMcKenna: Use Dependency + Injection in MetatagManager. +#3265567 by DamienMcKenna, Anatolij Zajika, PCate: PHP8 undefined array key + "description". +#3266406 by szato, DamienMcKenna: Don't assume the 'first_row_tokens' cache key + is defined. +#3264449 by DamienMcKenna, gilles_webstanz: Trim og:description doesn't work. +#3258427 by Eugene Bocharov, mrshowerman, saranchuk_hys, phthlaap, apaderno, + Kulturmensch, DamienMcKenna, quicksketch: TypeError: preg_match_all(): + Arg #2 ($subject) must be of type string, array given in preg_match_all(). +#3258358 by Eugene Bocharov, sashken2, DamienMcKenna, modernrockstar: Empty + values saved in 'mask_icon' meta tag. +#3269670 by DamienMcKenna, sd123: Trimming even trims metatags shorter than the + limit. +#3269742 by DamienMcKenna, z3cka: Migrate D7 Product Display node metatag data + to D9 Drupal Commerce Product entities. +#3280904 by DamienMcKenna, 3li: Automatic conversion of false to array is + deprecated. +#3278924 by lamp5, DamienMcKenna: Translation of title metatag is limited to + 128 characters. +#3271284 by Shashwat Purav: Double occurrences of word "the". +#3257520 by er.garg.karan, DamienMcKenna: Default Drupal favicon icon on core + before 9.3. +#3281987 by DamienMcKenna: Sort configuration items prior to saving. +#3284465 by DamienMcKenna, murilohp: Deprecated obsolete meta + http-equiv="content-language". +#3270951 by marciaibanez, tmaiochi, Guilherme Rabelo, chakkche, DamienMcKenna: + Coding standards improvements. +#3112509 by DamienMcKenna, ciprian.stavovei, Renrhaf, sitiveni: Convert + "author" tag to HTML 5.2 spec, deprecate Google+ Author tag. +#3280745 by DamienMcKenna, Anybody: Trimming should use multibyte functions. +#3112509 by DamienMcKenna: Fix regression in Google+ tests. +#2563655 by DamienMcKenna, waldomero: Absorb Metatag Routes module to allow + per-path/route configurations. +#3268439 by chakkche, bygeoffthompson, DamienMcKenna, Ruturaj Chaubey: Optional + overflow control on expanded Metatag field widget. +#3231981 by sanduhrs, DamienMcKenna: Add support for SIWECOS website security + scanner. +#3198100 by thejimbirch, DamienMcKenna, sriharsha.uppuluri: Two Google meta tags + conflict with each other. +#3077442 by daniel.bosen, DamienMcKenna, esdrasterrero, Vivek Panicker, + Supreetam09, rigoucr: Current language through the language manager doesn't + work (decoupled site). +#3295569 by DamienMcKenna: 8.x-1.x tests fail against PHP 7.3. + + Metatag 8.x-1.19, 2022-01-06 ---------------------------- #3104170 by DamienMcKenna, tobiasb, marcoliver, hosterholz, Berdir: Do not send diff --git a/frontend/drupal9/web/modules/contrib/metatag/README.md b/frontend/drupal9/web/modules/contrib/metatag/README.md index aa178116d..b16a888ff 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/README.md +++ b/frontend/drupal9/web/modules/contrib/metatag/README.md @@ -21,7 +21,6 @@ Metatag for Drupal 9 requires the following: * [Token](https://www.drupal.org/project/token): Provides a popup browser to see the available tokens for use in meta tag fields. - ## Features The primary features include: @@ -36,6 +35,9 @@ The primary features include: tags, site verification tags and more; all but the basic meta tags are kept in separate submodules. +* Configuration can be added for individual paths using the Metatag Custom + Routes submodule. + * The fifteen Dublin Core Basic Element Set 1.1 meta tags may be added by enabling the "Metatag: Dublin Core" submodule. diff --git a/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.metatag_tag.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.metatag_tag.schema.yml index 8ad5e98cc..bc6555702 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.metatag_tag.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.metatag_tag.schema.yml @@ -69,7 +69,7 @@ metatag.metatag_tag.standout: type: label label: 'Standout' metatag.metatag_tag.title: - type: label + type: text label: 'Page title' metatag.metatag_tag.icbm: type: label @@ -89,3 +89,6 @@ metatag.metatag_tag.set_cookie: metatag.metatag_tag.google: type: label label: 'Google' +metatag.metatag_tag.author: + type: label + label: 'Author' diff --git a/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.settings.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.settings.schema.yml index 97d74ae8d..645608228 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.settings.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/config/schema/metatag.settings.schema.yml @@ -23,3 +23,6 @@ metatag.settings: sequence: type: integer label: 'Tag maximum length in characters' + tag_scroll_max_height: + type: string + label: 'Add max height value' diff --git a/frontend/drupal9/web/modules/contrib/metatag/css/firehose_widget.css b/frontend/drupal9/web/modules/contrib/metatag/css/firehose_widget.css new file mode 100644 index 000000000..c601bce58 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/css/firehose_widget.css @@ -0,0 +1,9 @@ +/** + * @file + * Custom CSS for the Firehose widget. + */ + +.metatags { + max-height: 500px; + overflow-y: overlay; +} diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag.info.yml index 64ce8e22a..aab4eb2b3 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.info.yml @@ -10,7 +10,7 @@ dependencies: test_dependencies: - devel:devel -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.install b/frontend/drupal9/web/modules/contrib/metatag/metatag.install index ef2b19830..55b08764c 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag.install +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.install @@ -91,6 +91,7 @@ function metatag_update_8109(&$sandbox) { $field_counter = 0; // Get all of the field storage entities of type metatag. + /** @var \Drupal\field\FieldStorageConfigInterface[] $field_storage_configs */ $field_storage_configs = \Drupal::entityTypeManager() ->getStorage('field_storage_config') ->loadByProperties(['type' => 'metatag']); @@ -101,7 +102,10 @@ function metatag_update_8109(&$sandbox) { // Get the individual fields (field instances) associated with bundles. $fields = \Drupal::entityTypeManager() ->getStorage('field_config') - ->loadByProperties(['field_name' => $field_name]); + ->loadByProperties([ + 'field_name' => $field_name, + 'entity_type' => $field_storage->getTargetEntityTypeId(), + ]); foreach ($fields as $field) { // Get the bundle this field is attached to. @@ -157,30 +161,27 @@ function metatag_update_8109(&$sandbox) { $field_value_field = $sandbox['fields'][$current_field]['field_value_field']; // Loop through the field(s) and update the mask_icon values if necessary. - while ($counter <= $max_per_batch) { - if (isset($current_field_records[$current_record])) { - $record = $current_field_records[$current_record]; + while ($counter <= $max_per_batch && isset($current_field_records[$current_record])) { + $record = $current_field_records[$current_record]; - // Strip any empty tags or ones matching the field's defaults and leave - // only the overridden tags in $new_tags. - $tags = unserialize($record->$field_value_field); - if (isset($tags['mask-icon'])) { - $tags['mask_icon'] = [ - 'href' => $tags['mask-icon'], - ]; + // Strip any empty tags or ones matching the field's defaults and leave + // only the overridden tags in $new_tags. + $tags = unserialize($record->$field_value_field, ['allowed_classes' => FALSE]); + if (isset($tags['mask-icon'])) { + $tags['mask_icon'] = [ + 'href' => $tags['mask-icon'], + ]; - $tags_string = serialize($tags); - \Drupal::database()->update($field_table) - ->fields([ - $field_value_field => $tags_string, - ]) - ->condition('entity_id', $record->entity_id) - ->condition('revision_id', $record->revision_id) - ->condition('langcode', $record->langcode) - ->execute(); - } + $tags_string = serialize($tags); + \Drupal::database()->update($field_table) + ->fields([ + $field_value_field => $tags_string, + ]) + ->condition('entity_id', $record->entity_id) + ->condition('revision_id', $record->revision_id) + ->condition('langcode', $record->langcode) + ->execute(); } - $counter++; $current_record++; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.libraries.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag.libraries.yml new file mode 100644 index 000000000..6db3adf4b --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.libraries.yml @@ -0,0 +1,6 @@ +# Custom libraries for the Metatag module. + +firehose_widget: + css: + theme: + css/firehose_widget.css: {} diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.module b/frontend/drupal9/web/modules/contrib/metatag/metatag.module index 1c7e66cf3..11efd0b2c 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag.module +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.module @@ -5,8 +5,8 @@ * Contains metatag.module. */ +use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Plugin\Factory\DefaultFactory; -use Drupal\Component\Utility\Html; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; @@ -26,8 +26,8 @@ use Drupal\taxonomy\Plugin\migrate\source\d7\Term as Term7; use Drupal\taxonomy\TermInterface; use Drupal\user\Plugin\migrate\source\d6\User as User6; use Drupal\user\Plugin\migrate\source\d7\User as User7; -use Drupal\Core\Render\HtmlResponseAttachmentsProcessor; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\commerce_migrate_commerce\Plugin\migrate\source\commerce1\ProductDisplay; /** * Implements hook_help(). @@ -143,7 +143,6 @@ function metatag_page_attachments(array &$attachments) { $attachments['#attached']['html_head'] = []; } - $head_links = []; foreach ($metatag_attachments['#attached']['html_head'] as $item) { // Do not attach a title meta tag as this unnecessarily duplicates the // title tag. @@ -153,32 +152,6 @@ function metatag_page_attachments(array &$attachments) { } $attachments['#attached']['html_head'][] = $item; - - // Also add a HTTP header "Link:" for canonical URLs and shortlinks. - // See HtmlResponseAttachmentsProcessor::processHtmlHeadLink() for the - // implementation of the functionality in core. - if (isset($item[0]['#attributes']['href'])) { - if (in_array($item[1], ['canonical_url', 'shortlink'])) { - $attributes = $item[0]['#attributes']; - - $href = '<' . Html::escape($attributes['href']) . '>'; - unset($attributes['href']); - $param = HtmlResponseAttachmentsProcessor::formatHttpHeaderAttributes($attributes); - if (!empty($param)) { - $href .= ';' . $param; - } - $head_links[] = $href; - } - } - } - - // If any HTTP Header items were found, add them too. - if (!empty($head_links)) { - $attachments['#attached']['http_header'][] = [ - 'Link', - implode(', ', $head_links), - FALSE, - ]; } } } @@ -301,11 +274,12 @@ function _metatag_remove_duplicate_entity_tags(array &$build) { // Check to see if the page currently outputs a canonical and/or shortlink // tag. if (isset($metatag_attachments['#attached']['html_head'])) { + $tags_to_look_for = ['canonical', 'shortlink']; foreach ($metatag_attachments['#attached']['html_head'] as $metatag_item) { if (in_array($metatag_item[1], ['canonical_url', 'shortlink'])) { // Metatag provides rel="canonical" and/or rel="shortlink" tags. foreach ($build['#attached']['html_head_link'] as $key => $item) { - if (isset($item[0]['rel']) && in_array($item[0]['rel'], ['canonical', 'shortlink'])) { + if (isset($item[0]['rel']) && in_array($item[0]['rel'], $tags_to_look_for)) { // Remove the link rel="canonical" or link rel="shortlink" tag // from the entity's build array. unset($build['#attached']['html_head_link'][$key]); @@ -348,13 +322,18 @@ function metatag_is_current_route_supported() { /** * Returns the entity of the current route. * - * @return Drupal\Core\Entity\EntityInterface + * @return Drupal\Core\Entity\EntityInterface|null * The entity or NULL if this is not an entity route. */ function metatag_get_route_entity() { $route_match = \Drupal::routeMatch(); $route_name = $route_match->getRouteName(); + // In certain circumstances the route can be empty. + if (is_null($route_name)) { + return NULL; + } + // Look for a canonical entity view page, e.g. node/{nid}, user/{uid}, etc. $matches = []; preg_match('/entity\.(.*)\.(latest[_-]version|canonical)/', $route_name, $matches); @@ -732,6 +711,11 @@ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, M // E.g. d7_user. $source_type = 'user'; } + // Custom handling for Drupal Commerce. + elseif (\Drupal::moduleHandler()->moduleExists('commerce_migrate_commerce') && is_a($source, ProductDisplay::class)) { + // Products were nodes in Drupal 7 so map the $source_type to node. + $source_type = 'node'; + } else { // Not supported now, nothing to do. return; @@ -808,24 +792,31 @@ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, M // @todo Write a more general version rather than a switch statement. switch ($source_type) { case 'node': - // define('NODEWORDS_TYPE_NODE', 5); + // @code + // define('NODEWORDS_TYPE_NODE', 5); + // @endcode $nodeword_type = 5; $entity_id = $row->getSourceProperty('nid'); break; case 'taxonomy_term': - // define('NODEWORDS_TYPE_TERM', 6); + // @code + // define('NODEWORDS_TYPE_TERM', 6); + // @endcode $nodeword_type = 6; $entity_id = $row->getSourceProperty('tid'); break; case 'user': - // define('NODEWORDS_TYPE_USER', 8); + // @code + // define('NODEWORDS_TYPE_USER', 8); + // @endcode $nodeword_type = 8; $entity_id = $row->getSourceProperty('uid'); break; } - // @todo + // @todo Migrate these configuration items. + // @code // define('NODEWORDS_TYPE_BLOG', 13); // define('NODEWORDS_TYPE_DEFAULT', 1); // define('NODEWORDS_TYPE_ERRORPAGE', 2); @@ -837,7 +828,7 @@ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, M // define('NODEWORDS_TYPE_PAGER', 4); // define('NODEWORDS_TYPE_TRACKER', 7); // define('NODEWORDS_TYPE_VOCABULARY', 9); - + // @endcode /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source */ /** @var \Drupal\Core\Database\Query\SelectInterface $query */ $query = $source->getDatabase()->select('nodewords', 'nw') @@ -847,7 +838,7 @@ function metatag_migrate_prepare_row(Row $row, MigrateSourceInterface $source, M ->orderBy('nw.name'); $value = $query->execute()->fetchAllKeyed(); - $row->setSourceProperty('pseudo_metatag_entities', $value); + $row->setSourceProperty('pseudo_metatag_entities', $value); } } } @@ -1001,7 +992,7 @@ function _metatag_is_migration_plugin_supported(array $definition) { return FALSE; } } - catch (\Drupal\Component\Plugin\Exception\PluginNotFoundException $e) { + catch (PluginNotFoundException $e) { // If the entity type doesn't exist, neither with the migration plugin. return FALSE; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.post_update.php b/frontend/drupal9/web/modules/contrib/metatag/metatag.post_update.php index ba9beeb64..641c77c14 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag.post_update.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.post_update.php @@ -26,3 +26,160 @@ function metatag_post_update_convert_mask_icon_to_array_values(&$sandbox) { return FALSE; }); } + +/** + * The author meta tag was moved into the main module: configuration. + */ +function metatag_post_update_convert_author_config(&$sandbox) { + $config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class); + $config_entity_updater->update($sandbox, 'metatag_defaults', function (MetatagDefaults $metatag_defaults) { + if ($metatag_defaults->hasTag('google_plus_author')) { + $tags = $metatag_defaults->get('tags'); + $tags['author'] = $metatag_defaults->getTag('google_plus_author'); + unset($tags['google_plus_author']); + $metatag_defaults->set('tags', $tags); + return TRUE; + } + return FALSE; + }); +} + +/** + * The author meta tag was moved into the main module: entity data. + */ +function metatag_post_update_convert_author_data(&$sandbox) { + // This whole top section only needs to be done the first time. + if (!isset($sandbox['records_processed'])) { + $sandbox['records_processed'] = 0; + $sandbox['total_records'] = 0; + $sandbox['current_field'] = 0; + $sandbox['current_record'] = 0; + + // Counter to enumerate the fields so we can access them in the array + // by number rather than name. + $field_counter = 0; + + // Get all of the field storage entities of type metatag. + /** @var \Drupal\field\FieldStorageConfigInterface[] $field_storage_configs */ + $field_storage_configs = \Drupal::entityTypeManager() + ->getStorage('field_storage_config') + ->loadByProperties(['type' => 'metatag']); + + foreach ($field_storage_configs as $field_storage) { + $field_name = $field_storage->getName(); + + // Get the individual fields (field instances) associated with bundles. + $fields = \Drupal::entityTypeManager() + ->getStorage('field_config') + ->loadByProperties([ + 'field_name' => $field_name, + 'entity_type' => $field_storage->getTargetEntityTypeId(), + ]); + + foreach ($fields as $field) { + // Get the bundle this field is attached to. + $bundle = $field->getTargetBundle(); + + // Determine the table and "value" field names. + $table_mapping = Drupal::entityTypeManager() + ->getStorage($field->getTargetEntityTypeId()) + ->getTableMapping(); + $field_table = $table_mapping->getFieldTableName($field_name); + $field_value_field = $table_mapping->getFieldColumnName($field_storage, 'value'); + + // Get all records where the field data does not match the default. + $query = \Drupal::database()->select($field_table); + $query->addField($field_table, 'entity_id'); + $query->addField($field_table, 'revision_id'); + $query->addField($field_table, 'langcode'); + $query->addField($field_table, $field_value_field); + $query->condition('bundle', $bundle, '='); + $result = $query->execute(); + $records = $result->fetchAll(); + + if (empty($records)) { + continue; + } + + // Fill in all the sandbox information so we can batch the individual + // record comparing and updating. + $sandbox['fields'][$field_counter]['field_table'] = $field_table; + $sandbox['fields'][$field_counter]['field_value_field'] = $field_value_field; + $sandbox['fields'][$field_counter]['records'] = $records; + + $sandbox['total_records'] += count($sandbox['fields'][$field_counter]['records'] = $records); + $field_counter++; + } + } + } + + if ($sandbox['total_records'] == 0) { + // No partially overridden fields so we can skip the whole batch process. + $sandbox['#finished'] = 1; + } + else { + // Begin the batch processing of individual field records. + $max_per_batch = 10; + $counter = 1; + + $current_field = $sandbox['current_field']; + $current_field_records = $sandbox['fields'][$current_field]['records']; + $current_record = $sandbox['current_record']; + + $field_table = $sandbox['fields'][$current_field]['field_table']; + $field_value_field = $sandbox['fields'][$current_field]['field_value_field']; + + // Loop through the field(s) and update the mask_icon values if necessary. + while ($counter <= $max_per_batch && isset($current_field_records[$current_record])) { + $record = $current_field_records[$current_record]; + + // Strip any empty tags or ones matching the field's defaults and leave + // only the overridden tags in $new_tags. + $tags = unserialize($record->$field_value_field); + if (isset($tags['google_plus_author'])) { + $tags['author'] = $tags['google_plus_author']; + $tags_string = serialize($tags); + \Drupal::database()->update($field_table) + ->fields([ + $field_value_field => $tags_string, + ]) + ->condition('entity_id', $record->entity_id) + ->condition('revision_id', $record->revision_id) + ->condition('langcode', $record->langcode) + ->execute(); + } + $counter++; + $current_record++; + } + + // We ran out of records for the field so start the next batch out with the + // next field. + if (!isset($current_field_records[$current_record])) { + $current_field++; + $current_record = 0; + } + + // We have finished all the fields. All done. + if (!isset($sandbox['fields'][$current_field])) { + $sandbox['records_processed'] += $counter - 1; + $sandbox['#finished'] = 1; + } + // Update the sandbox values to prepare for the next round. + else { + $sandbox['current_field'] = $current_field; + $sandbox['current_record'] = $current_record; + $sandbox['records_processed'] += $counter - 1; + $sandbox['#finished'] = $sandbox['records_processed'] / $sandbox['total_records']; + } + } + + if ($sandbox['total_records'] > 0) { + return (string) t('Processed @processed of @total overridden Metatag records.', [ + '@processed' => $sandbox['records_processed'], + '@total' => $sandbox['total_records'], + ]); + } + else { + return (string) t("There were no overridden Metatag records."); + } +} diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.services.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag.services.yml index 81504131f..9a0c9cfcf 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag.services.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.services.yml @@ -13,7 +13,7 @@ services: metatag.manager: class: Drupal\metatag\MetatagManager - arguments: ['@plugin.manager.metatag.group', '@plugin.manager.metatag.tag', '@metatag.token', '@logger.factory', '@entity_type.manager'] + arguments: ['@plugin.manager.metatag.group', '@plugin.manager.metatag.tag', '@metatag.token', '@logger.factory', '@entity_type.manager', '@path.matcher', '@current_route_match', '@request_stack', '@language_manager'] metatag.trimmer: class: Drupal\metatag\MetatagTrimmer diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag.tokens.inc b/frontend/drupal9/web/modules/contrib/metatag/metatag.tokens.inc index 1ec124d4e..9ababdc29 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag.tokens.inc +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag.tokens.inc @@ -208,7 +208,7 @@ function metatag_tokens($type, $tokens, array $data, array $options, BubbleableM // For [metatag:tag_name:0], [metatag:tag_name:0:value] and // [metatag:tag_name:value] tokens. else { - list($tag_name, $delta) = explode(':', $name, 2); + [$tag_name, $delta] = explode(':', $name, 2); if (!is_numeric($delta)) { unset($delta); } @@ -227,7 +227,7 @@ function metatag_tokens($type, $tokens, array $data, array $options, BubbleableM } else { if (is_array($processed_tags[$tag_name])) { - $replacements[$original] = implode(',', $processed_tags[$tag_name]); + $replacements[$original] = implode(',', array_filter($processed_tags[$tag_name])); } else { $replacements[$original] = $processed_tags[$tag_name]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_app_links/metatag_app_links.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_app_links/metatag_app_links.info.yml index 6e726838f..5b973e1a8 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_app_links/metatag_app_links.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_app_links/metatag_app_links.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/config/schema/metatag_dc.metatag_tag.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/config/schema/metatag_dc.metatag_tag.schema.yml index afda126fb..a6e1255e0 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/config/schema/metatag_dc.metatag_tag.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/config/schema/metatag_dc.metatag_tag.schema.yml @@ -42,7 +42,7 @@ metatag.metatag_tag.dcterms_subject: type: label label: 'Dublin Core: Subject' metatag.metatag_tag.dcterms_title: - type: label + type: text label: 'Dublin Core: Title' metatag.metatag_tag.dcterms_type: type: label diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/metatag_dc.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/metatag_dc.info.yml index af56ed981..4239455f5 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/metatag_dc.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_dc/metatag_dc.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml index ffdc46d5a..90f6bc055 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_dc_advanced/metatag_dc_advanced.info.yml @@ -7,7 +7,7 @@ dependencies: - metatag:metatag - metatag:metatag_dc -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/metatag_extended_perms.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/metatag_extended_perms.info.yml index be84dd8a0..3ddc228e5 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/metatag_extended_perms.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/metatag_extended_perms.info.yml @@ -4,9 +4,9 @@ description: "Adds individual permissions for each meta tag, allowing for fine-g core_version_requirement: '^9' package: SEO dependencies: - - metatag + - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/tests/src/Functional/PermissionsTest.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/tests/src/Functional/PermissionsTest.php index 153a112ed..1380fc710 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/tests/src/Functional/PermissionsTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_extended_perms/tests/src/Functional/PermissionsTest.php @@ -70,11 +70,13 @@ class PermissionsTest extends BrowserTestBase { 'revisit_after' => 'Revisit After', 'rights' => 'Rights', // This one is more complicated, so skip it. + // @code // 'robots' => 'Robots', + // @endcode 'set_cookie' => 'Set cookie', 'shortlink' => 'Shortlink URL', 'standout' => 'Standout', - ] + ], ]; /** diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_facebook/metatag_facebook.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_facebook/metatag_facebook.info.yml index 147b8b8ed..146253402 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_facebook/metatag_facebook.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_facebook/metatag_facebook.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.info.yml index a2b7651d1..c0229bf46 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.module b/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.module index c816e8e9a..25035109a 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.module +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/metatag_favicons.module @@ -34,10 +34,11 @@ function metatag_favicons_page_attachments_alter(array &$attachments) { } // Remove the default shortcut icon if one was set by Metatag. + $valid_meta_tags = ['shortcut icon', 'shortcut_icon', 'icon']; foreach ($attachments['#attached']['html_head'] as $element) { - if (isset($element[1]) && in_array($element[1], ['shortcut_icon', 'icon'])) { + if (isset($element[1]) && in_array($element[1], $valid_meta_tags)) { foreach ($attachments['#attached']['html_head_link'] as $key => $value) { - if (isset($value[0]['rel']) && in_array($value[0]['rel'], ['shortcut_icon', 'icon'])) { + if (isset($value[0]['rel']) && in_array($value[0]['rel'], $valid_meta_tags)) { unset($attachments['#attached']['html_head_link'][$key]); } } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/src/Plugin/metatag/Tag/MaskIcon.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/src/Plugin/metatag/Tag/MaskIcon.php index d8f6785f3..630b83cdb 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/src/Plugin/metatag/Tag/MaskIcon.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_favicons/src/Plugin/metatag/Tag/MaskIcon.php @@ -41,9 +41,9 @@ class MaskIcon extends LinkRelBase { $form['href'] = [ '#type' => 'textfield', '#title' => $this->label(), - '#default_value' => isset($defaults['href']) ? $defaults['href'] : '', + '#default_value' => $defaults['href'] ?? '', '#maxlength' => 255, - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#description' => $this->description(), '#element_validate' => [[get_class($this), 'validateTag']], ]; @@ -52,7 +52,7 @@ class MaskIcon extends LinkRelBase { $form['color'] = [ '#type' => 'textfield', '#title' => $this->t('Mask icon color'), - '#default_value' => isset($defaults['color']) ? $defaults['color'] : '', + '#default_value' => $defaults['color'] ?? '', '#required' => FALSE, '#description' => $this->t("Color attribute for SVG (mask) icon in hexadecimal format, e.g. '#0000ff'. Setting it will break HTML validation. If not set macOS Safari ignores the Mask Icon entirely, making the Icon: SVG completely useless."), ]; @@ -81,4 +81,12 @@ class MaskIcon extends LinkRelBase { return $element; } + /** + * {@inheritdoc} + */ + public function setValue($value) { + // Do not store array with empty values. + $this->value = is_array($value) && empty(array_filter($value)) ? NULL : $value; + } + } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_google_cse/metatag_google_cse.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_google_cse/metatag_google_cse.info.yml index d30f1aaf6..41a532710 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_google_cse/metatag_google_cse.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_google_cse/metatag_google_cse.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/metatag_google_plus.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/metatag_google_plus.info.yml index 27380b9be..689b5de53 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/metatag_google_plus.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/metatag_google_plus.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/src/Plugin/metatag/Tag/Author.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/src/Plugin/metatag/Tag/Author.php index 2cbfa3ada..4fb9b6cf3 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/src/Plugin/metatag/Tag/Author.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_google_plus/src/Plugin/metatag/Tag/Author.php @@ -10,7 +10,7 @@ use Drupal\metatag\Plugin\metatag\Tag\LinkRelBase; * @MetatagTag( * id = "google_plus_author", * label = @Translation("Author"), - * description = @Translation("Used by some search engines to confirm authorship of the content on a page. Should be either the full URL for the author's Google+ profile page or a local page with information about the author."), + * description = @Translation("DEPRECATED, use Advanced-Author instead."), * name = "author", * group = "google_plus", * weight = 4, @@ -18,6 +18,10 @@ use Drupal\metatag\Plugin\metatag\Tag\LinkRelBase; * secure = FALSE, * multiple = FALSE * ) + * + * @deprecated in metatag:8.x-1.20 and is removed from metatag:2.0.0. No replacement is provided. + * + * @see https://www.drupal.org/project/metatag/issues/3284464 */ class Author extends LinkRelBase { // Nothing here yet. Just a placeholder class for a plugin. diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.info.yml index 85eb1f0ae..30abb7ac4 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.module b/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.module index dcb75d0f2..fe3a46bf0 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.module +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_hreflang/metatag_hreflang.module @@ -37,7 +37,7 @@ function metatag_hreflang_page_attachments_alter(array &$attachments) { foreach ($attachments['#attached']['html_head'] as $element) { // Check for Metatag's identifier "hreflang_per_language". if (!empty($element[1])) { - if (strpos($element[1], 'hreflang_per_language') !== false && isset($element[0]['#attributes']['hreflang'])) { + if (strpos($element[1], 'hreflang_per_language') !== FALSE && isset($element[0]['#attributes']['hreflang'])) { $hreflang_per_language[] = $element[0]['#attributes']['hreflang']; } } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/config/schema/metatag_mobile.metatag_tag.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/config/schema/metatag_mobile.metatag_tag.schema.yml index ef95ca6ea..a5bd38dd3 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/config/schema/metatag_mobile.metatag_tag.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/config/schema/metatag_mobile.metatag_tag.schema.yml @@ -21,7 +21,7 @@ metatag.metatag_tag.apple_mobile_web_app_status_bar_style: type: label label: 'Apple Mobile: Web App Status bar color' metatag.metatag_tag.apple_mobile_web_app_title: - type: label + type: text label: 'Apple Mobile: Web App Title' metatag.metatag_tag.application_name: type: label diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/metatag_mobile.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/metatag_mobile.info.yml index b2f1a67fd..83651c22c 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/metatag_mobile.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_mobile/metatag_mobile.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/config/schema/metatag_open_graph.metatag_tag.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/config/schema/metatag_open_graph.metatag_tag.schema.yml index 68bdff47f..6481f98e1 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/config/schema/metatag_open_graph.metatag_tag.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/config/schema/metatag_open_graph.metatag_tag.schema.yml @@ -114,7 +114,7 @@ metatag.metatag_tag.og_street_address: type: label label: 'Open Graph: Street address' metatag.metatag_tag.og_title: - type: label + type: text label: 'Open Graph: Title' metatag.metatag_tag.og_type: type: label diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.info.yml index 82982f516..2ea6da8e8 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.install b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.install index a2856db16..f128ee50e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.install +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph/metatag_open_graph.install @@ -16,7 +16,7 @@ use Drupal\metatag\Entity\MetatagDefaults; * The "article:tags" meta tag was renamed to the correct "article:tag". */ function metatag_open_graph_update_8101() { - /** @var $configs Drupal\metatag\Entity\MetatagDefaults */ + /** @var Drupal\metatag\Entity\MetatagDefaults $configs */ $configs = MetatagDefaults::loadMultiple(); foreach ($configs as $config) { @@ -72,7 +72,7 @@ function metatag_open_graph_update_8102(&$sandbox) { foreach ($sandbox['todo'] as $entity_type => $fields) { - /** @var $def Drupal\Core\Entity\ContentEntityType */ + /** @var Drupal\Core\Entity\ContentEntityType $def */ $def = Drupal::entityTypeManager()->getDefinition($entity_type); // Grab the primary key field for this entity type @@ -95,7 +95,7 @@ function metatag_open_graph_update_8102(&$sandbox) { $entities = $etm->getStorage($entity_type)->loadMultiple($res); foreach ($entities as $entity) { - /** @var $entity ContentEntityBase */ + /** @var \Drupal\Core\Entity\ContentEntityBase $entity */ if ($entity instanceof ContentEntityBase) { if ($entity->hasField($field_name)) { /** @var LanguageInterface $langcode */ @@ -105,7 +105,7 @@ function metatag_open_graph_update_8102(&$sandbox) { $tags_serialized = $trans->get($field_name)->value; if ($tags_serialized) { // Change key from article_tags to article_tag. - $tags = unserialize($tags_serialized); + $tags = unserialize($tags_serialized, ['allowed_classes' => FALSE]); if (array_key_exists("article_tags", $tags)) { $tags['article_tag'] = $tags['article_tags']; unset($tags['article_tags']); diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml index a7e4a30bc..72c0bfc38 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/metatag_open_graph_products.info.yml @@ -7,7 +7,7 @@ dependencies: - metatag:metatag - metatag:metatag_open_graph -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductAvailability.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductAvailability.php index 272720caa..f6ea6d9ea 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductAvailability.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductAvailability.php @@ -22,5 +22,3 @@ use Drupal\metatag\Plugin\metatag\Tag\MetaPropertyBase; class ProductAvailability extends MetaPropertyBase { // Nothing here yet. Just a placeholder class for a plugin. } - - diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductCondition.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductCondition.php index d1e19d50f..ec5d626e7 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductCondition.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductCondition.php @@ -22,5 +22,3 @@ use Drupal\metatag\Plugin\metatag\Tag\MetaPropertyBase; class ProductCondition extends MetaPropertyBase { // Nothing here yet. Just a placeholder class for a plugin. } - - diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductRetailerItemId.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductRetailerItemId.php index ee08bce65..90ad105f7 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductRetailerItemId.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_open_graph_products/src/Plugin/metatag/Tag/ProductRetailerItemId.php @@ -22,4 +22,3 @@ use Drupal\metatag\Plugin\metatag\Tag\MetaPropertyBase; class ProductRetailerItemId extends MetaPropertyBase { // Nothing here yet. Just a placeholder class for a plugin. } - diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/metatag_page_manager.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/metatag_page_manager.info.yml index 967166a36..e60d99335 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/metatag_page_manager.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/metatag_page_manager.info.yml @@ -7,7 +7,7 @@ dependencies: - page_manager:page_manager - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/MetatagPageManagerTest.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/MetatagPageManagerTest.txt similarity index 99% rename from frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/MetatagPageManagerTest.php rename to frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/MetatagPageManagerTest.txt index 02c464ca6..fa35a9411 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/MetatagPageManagerTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_page_manager/tests/src/Functional/MetatagPageManagerTest.txt @@ -40,7 +40,7 @@ class MetatagPageManagerTest extends BrowserTestBase { /** * {@inheritdoc} */ - public function setUp() { + public function setUp(): void { parent::setUp(); $this->assertSession = $this->assertSession(); diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/metatag_pinterest.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/metatag_pinterest.info.yml index 8eb46e1b5..67c1f0308 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/metatag_pinterest.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/metatag_pinterest.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNohover.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNohover.php index f725e441a..1d7b29a0e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNohover.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNohover.php @@ -30,7 +30,7 @@ class PinterestNohover extends MetaNameBase { '#title' => $this->label(), '#description' => $this->description(), '#default_value' => ($this->value === 'nohover') ?: '', - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#element_validate' => [[get_class($this), 'validateTag']], '#return_value' => 'nohover', ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNopin.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNopin.php index 5e0265340..362eb69d8 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNopin.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNopin.php @@ -30,7 +30,7 @@ class PinterestNopin extends MetaNameBase { '#title' => $this->label(), '#description' => $this->description(), '#default_value' => ($this->value === 'nopin') ?: '', - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#element_validate' => [[get_class($this), 'validateTag']], '#return_value' => 'nopin', ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNosearch.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNosearch.php index 9c0c9437b..714ed6881 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNosearch.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_pinterest/src/Plugin/metatag/Tag/PinterestNosearch.php @@ -30,7 +30,7 @@ class PinterestNosearch extends MetaNameBase { '#title' => $this->label(), '#description' => $this->description(), '#default_value' => ($this->value === 'nosearch') ?: '', - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#element_validate' => [[get_class($this), 'validateTag']], '#return_value' => 'nosearch', ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/README.md b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/README.md new file mode 100644 index 000000000..8181a453e --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/README.md @@ -0,0 +1,25 @@ +# Metatag Routes + +The Metatag Routes module allows a user to configure metatags for custom +controllers generated by code. + +It uses the metatags entity to store a configuration for any route using the +metatags administration. + +The module adds a button in the default metatag dashboard page giving the user +the option to enter the route path and validates if the path has a defined +route, the route is not administrative and the route is "metatag-able". + +## Configuration + +1. Navigate to Administration > Extend and enable the module. +2. Navigate to Administration > Configuration > Search and Metadata > + Metatag to configure metatags. +3. Select "Add meta tag for custom route" and enter the path for which you + want to add the metatags. +4. Submit. + +## Credits / contact + +Written by [Walter Velasquez (waldomero)](https://www.drupal.org/u/waldomero) +and sponsored by [Globant](https://www.drupal.org/globant). diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.info.yml new file mode 100644 index 000000000..9945fea8e --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.info.yml @@ -0,0 +1,11 @@ +name: 'Metatag Custom Routes (Paths)' +type: module +description: Allows assigning meta tags to be used on custom routes, equivalent to customn paths. +core_version_requirement: ^9 +dependencies: + - metatag:metatag + +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' +project: 'metatag' +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.links.action.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.links.action.yml new file mode 100644 index 000000000..99b259f3a --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.links.action.yml @@ -0,0 +1,6 @@ +metatag_routes.create_custom_metatag.action: + route_name: metatag_routes.create + title: 'Add meta tag for custom route' + weight: 1 + appears_on: + - entity.metatag_defaults.collection diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.module b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.module new file mode 100644 index 000000000..6cf44ff86 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.module @@ -0,0 +1,46 @@ +' . t('About') . ''; + $output .= '

' . t('Enables metatags for custom routes') . '

'; + return $output; + + default: + } +} + +/** + * Implements hook_metatags_alter(). + */ +function metatag_routes_metatags_alter(array &$metatags, array $context) { + // Ignore some system routes that are not approrpriate for meta tags. + if (metatag_is_current_route_supported()) { + // Look to see if a configuration was assigned for this route. + $current_route = \Drupal::routeMatch()->getRouteName(); + if (!empty($current_route)) { + $defaults = \Drupal::entityTypeManager() + ->getStorage('metatag_defaults') + ->load($current_route); + if (!empty($defaults)) { + $tags = $defaults->get('tags'); + + // Replace the new values and keep on the global values. + $metatags = array_merge($metatags, $tags); + } + } + } +} diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.routing.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.routing.yml new file mode 100644 index 000000000..13b8898de --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/metatag_routes.routing.yml @@ -0,0 +1,9 @@ +metatag_routes.create: + path: '/admin/config/search/metatag/custom/create' + defaults: + _form: '\Drupal\metatag_routes\Form\MetatagCustomCreateForm' + _title: 'Add custom metatag' + requirements: + _permission: 'administer meta tags' + options: + _admin_route: TRUE diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php new file mode 100644 index 000000000..6ab77f597 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_routes/src/Form/MetatagCustomCreateForm.php @@ -0,0 +1,194 @@ +entityTypeManager = $entity_type_manager; + $this->routeProvider = $route_provider; + $this->pathValidator = $path_validator; + $this->adminContext = $admin_context; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('router.route_provider'), + $container->get('path.validator'), + $container->get('router.admin_context') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'metatag_custom_create_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['metatag_url'] = [ + '#type' => 'textfield', + '#title' => $this->t('Route / Path'), + '#description' => $this->t('Enter the route (path) for this new configuration, starting with a leading slash.
Note: this must already exist as a path in Drupal.'), + '#maxlength' => 200, + '#required' => TRUE, + ]; + + $form['route_name'] = [ + '#type' => 'hidden', + ]; + + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Submit'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + + // Get the path given by the user. + $url = trim($form_state->getValue('metatag_url')); + + // Validate the url format. + if (strpos($url, '/') === FALSE) { + $form_state->setErrorByName('metatag_url', $this->t('The path must begin with /')); + return FALSE; + } + + // Get route name from path. + $url_object = $this->pathValidator->getUrlIfValid($url); + if ($url_object) { + $route_name = $url_object->getRouteName(); + $route_object = $this->routeProvider->getrouteByName($route_name); + // Avoid administrative routes to have metatags. + if ($this->adminContext->isAdminRoute($route_object)) { + $form_state->setErrorByName('metatag_url', + $this->t('The admin routes should not have metatags.')); + return FALSE; + } + + // Avoid including entity routes. + $params = $url_object->getRouteParameters(); + $entity_type = !empty($params) ? key($params) : NULL; + $entity_types = ['node', 'taxonomy_term', 'user']; + if (isset($entity_type) && in_array($entity_type, $entity_types)) { + $form_state->setErrorByName('metatag_url', + $this->t('The entities routes metatags must be added by fields. @entity_type - @id', [ + '@entity_type' => $entity_type, + '@id' => $params[$entity_type], + ])); + return FALSE; + } + + // Validate that the route doesn't have metatags created already. + $ids = $this->entityTypeManager->getStorage('metatag_defaults')->getQuery()->condition('id', $route_name)->execute(); + if ($ids) { + $form_state->setErrorByName('metatag_url', + $this->t('There are already metatags created for this route.')); + return FALSE; + } + $form_state->setValue('route_name', $route_name); + } + else { + $form_state->setErrorByName('metatag_url', $this->t('The path does not exist as an internal Drupal route.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // Get values for form submission. + $route_name = $form_state->getValue('route_name'); + $url = $form_state->getValue('metatag_url'); + if ($route_name && $url) { + // Create the new metatag entity. + $entity = $this->entityTypeManager->getStorage('metatag_defaults')->create([ + 'id' => $route_name, + 'label' => $url, + ]); + $entity->save(); + $this->messenger()->addStatus($this->t('Created metatags for the path: @url. Internal route: @route.', [ + '@url' => $url, + '@route' => $route_name, + ])); + + // Redirect to metatag edit page. + $form_state->setRedirect('entity.metatag_defaults.edit_form', [ + 'metatag_defaults' => $route_name, + ]); + } + else { + $this->messenger()->addError($this->t('The metatags could not be created for the path: @url.', [ + '@url' => $url, + ])); + + // Redirect to metatag edit page. + $form_state->setRedirect('entity.metatag_defaults.collection'); + } + } + +} diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/config/schema/metatag_twitter_cards.metatag_tag.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/config/schema/metatag_twitter_cards.metatag_tag.schema.yml index 78ce79f2f..19b3e68ac 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/config/schema/metatag_twitter_cards.metatag_tag.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/config/schema/metatag_twitter_cards.metatag_tag.schema.yml @@ -105,7 +105,7 @@ metatag.metatag_tag.twitter_cards_site_id: type: label label: "Twitter Cards: Site's Twitter account ID" metatag.metatag_tag.twitter_cards_title: - type: label + type: text label: 'Twitter Cards: Title' metatag.metatag_tag.twitter_cards_type: type: label diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml index 79185f0a0..414bdf90e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/metatag_twitter_cards.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/src/Plugin/metatag/Tag/TwitterCardsType.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/src/Plugin/metatag/Tag/TwitterCardsType.php index eeeac57f1..34e128d59 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/src/Plugin/metatag/Tag/TwitterCardsType.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_twitter_cards/src/Plugin/metatag/Tag/TwitterCardsType.php @@ -44,7 +44,7 @@ class TwitterCardsType extends MetaNameBase { '#empty_option' => $this->t('- None -'), '#empty_value' => '', '#default_value' => $this->value(), - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#element_validate' => [[get_class($this), 'validateTag']], ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/config/schema/metatag_verification.metatag_tag.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/config/schema/metatag_verification.metatag_tag.schema.yml index 177592d88..711f8fe71 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/config/schema/metatag_verification.metatag_tag.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/config/schema/metatag_verification.metatag_tag.schema.yml @@ -14,7 +14,7 @@ metatag.metatag_tag.bing: metatag.metatag_tag.facebook_domain_verification: type: label label: 'Site validation: Facebook' -metatag.metatag_tag.google: +metatag.metatag_tag.google_site_verification: type: label label: 'Site validation: Google' metatag.metatag_tag.norton_safe_web: @@ -26,6 +26,9 @@ metatag.metatag_tag.pinterest: metatag.metatag_tag.pocket: type: label label: 'Site validation: Pocket' +metatag.metatag_tag.siwecos: + type: label + label: 'Site validation: SIWECOS' metatag.metatag_tag.yahoo: type: label label: 'Site validation: Yahoo' diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/metatag_verification.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/metatag_verification.info.yml index e67e6a41c..a8fdffc7e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/metatag_verification.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/metatag_verification.info.yml @@ -6,7 +6,7 @@ package: SEO dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Google.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Google.php index c57152d3a..6154dbe78 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Google.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Google.php @@ -8,8 +8,8 @@ use Drupal\metatag\Plugin\metatag\Tag\MetaNameBase; * Provides a plugin for the 'google-site-verification' meta tag. * * @MetatagTag( - * id = "google", - * label = @Translation("Google"), + * id = "google_site_verification", + * label = @Translation("Google Site Verification"), * description = @Translation("A string provided by Google, full details are available from the Google online help.", arguments = { ":google" = "https://www.google.com/", ":verify_url" = "https://support.google.com/webmasters/answer/35179?hl=en" }), * name = "google-site-verification", * group = "site_verification", diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Siwecos.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Siwecos.php new file mode 100644 index 000000000..bcf3181f7 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/src/Plugin/metatag/Tag/Siwecos.php @@ -0,0 +1,23 @@ +SIWECOS, the free website security scanner.", arguments = {":siwecos" = "https://siwecos.de/"}), + * name = "siwecostoken", + * group = "site_verification", + * weight = 7, + * type = "label", + * secure = FALSE, + * multiple = FALSE + * ) + */ +class Siwecos extends MetaNameBase { +} diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/tests/src/Functional/MetatagVerificationTagsTest.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/tests/src/Functional/MetatagVerificationTagsTest.php index 9e01deb5d..fe823c56b 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/tests/src/Functional/MetatagVerificationTagsTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_verification/tests/src/Functional/MetatagVerificationTagsTest.php @@ -25,10 +25,11 @@ class MetatagVerificationTagsTest extends MetatagTagsTestBase { 'baidu', 'bing', 'facebook_domain_verification', - 'google', + 'google_site_verification', 'norton_safe_web', 'pinterest', 'pocket', + 'siwecos', 'yandex', 'zoom_domain_verification', ]; @@ -46,7 +47,7 @@ class MetatagVerificationTagsTest extends MetatagTagsTestBase { elseif ($tag_name == 'facebook_domain_verification') { $tag_name = 'facebook-domain-verification'; } - elseif ($tag_name == 'google') { + elseif ($tag_name == 'google_site_verification') { $tag_name = 'google-site-verification'; } elseif ($tag_name == 'norton_safe_web') { @@ -58,6 +59,9 @@ class MetatagVerificationTagsTest extends MetatagTagsTestBase { elseif ($tag_name == 'pocket') { $tag_name = 'pocket-site-verification'; } + elseif ($tag_name == 'siwecos') { + $tag_name = 'siwecostoken'; + } elseif ($tag_name == 'yandex') { $tag_name = 'yandex-verification'; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/config/schema/metatag_views.views.schema.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/config/schema/metatag_views.views.schema.yml index b98db8cad..e4e3eb23e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/config/schema/metatag_views.views.schema.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/config/schema/metatag_views.views.schema.yml @@ -6,3 +6,6 @@ views.display_extender.metatag_display_extender: label: 'Metatags' sequence: type: metatag.metatag_tag.[%key] + tokenize: + type: boolean + label: 'Should replacement tokens be used from the first row' diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.info.yml b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.info.yml index 5b692a10a..d15b68c2a 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.info.yml @@ -7,7 +7,7 @@ dependencies: - metatag:metatag - drupal:views -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.module b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.module index ca09116be..3f293c62e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.module +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/metatag_views.module @@ -5,7 +5,6 @@ * Contains hook implementations for the metatag_views module. */ -use Drupal\Core\Cache\Cache; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\metatag_views\MetatagViewsCacheWrapper; use Drupal\metatag_views\Plugin\views\display_extender\MetatagDisplayExtender; diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsController.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsController.php index 2ddf0b824..d67cfc376 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsController.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsController.php @@ -10,7 +10,7 @@ use Drupal\views\Views; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Class MetatagViewsController. + * Controller for managing the Views integration. * * @package Drupal\metatag_views\Controller */ @@ -101,7 +101,7 @@ class MetatagViewsController extends ControllerBase { foreach ($tagged_views as $view_id => $displays) { $elements[$view_id] = [ '#type' => 'details', - '#title' => $this->t($this->viewLabels[$view_id]['#label']), + '#title' => $this->t(':label', [':label' => $this->viewLabels[$view_id]['#label']]), 'details' => $this->buildViewDetails($view_id, $displays), ]; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsTranslationController.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsTranslationController.php index cf2314278..d1f8670b9 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsTranslationController.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Controller/MetatagViewsTranslationController.php @@ -36,7 +36,7 @@ class MetatagViewsTranslationController extends ControllerBase { * @var \Drupal\Core\Language\LanguageManagerInterface */ protected $languageManager; - + /** * The request stack. * @@ -161,9 +161,9 @@ class MetatagViewsTranslationController extends ControllerBase { '#links' => $operations, // Even if the mapper contains multiple language codes, the source // configuration can still be edited. - // {@code} + // @code // '#access' => ($langcode == $original_langcode) || $operations_access, - // {@endcode} + // @endcode ]; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsAddForm.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsAddForm.php index 476293df9..b46d93b0d 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsAddForm.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsAddForm.php @@ -7,7 +7,7 @@ use Drupal\metatag_views\Controller\MetatagViewsController; use Drupal\views\Views; /** - * Class MetatagViewsAddForm. + * The add form for the Metatag field, which extends the edit form. * * @package Drupal\metatag_views\Form */ diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsEditForm.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsEditForm.php index 59ddb01dc..d78dfba68 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsEditForm.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Form/MetatagViewsEditForm.php @@ -10,7 +10,7 @@ use Drupal\metatag_views\MetatagViewsValuesCleanerTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Class MetatagViewsEditForm. + * The edit form for the Metatag field. * * @package Drupal\metatag_views\Form */ @@ -139,7 +139,7 @@ class MetatagViewsEditForm extends FormBase { $tag = $this->tagPluginManager->createInstance($tag_id); // Set the value to the stored value, if any. - $tag_value = isset($values[$tag_id]) ? $values[$tag_id] : NULL; + $tag_value = $values[$tag_id] ?? NULL; $tag->setValue($tag_value); // Create the bit of form for this tag. @@ -158,7 +158,7 @@ class MetatagViewsEditForm extends FormBase { public function submitForm(array &$form, FormStateInterface $form_state) { // Get the submitted form values. $view_name = $form_state->getValue('view'); - list($view_id, $display_id) = explode(':', $view_name); + [$view_id, $display_id] = explode(':', $view_name); $metatags = $form_state->getValues(); unset($metatags['view']); @@ -178,6 +178,9 @@ class MetatagViewsEditForm extends FormBase { $configuration->clear($config_path); } else { + // Sort the values prior to saving. so that they are easier to manage. + ksort($metatags); + $configuration->set($config_path, $metatags); } $configuration->save(); diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCachePluginManager.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCachePluginManager.php index 8e0187c09..418cae7af 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCachePluginManager.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCachePluginManager.php @@ -8,9 +8,14 @@ use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\views\Plugin\views\cache\CachePluginBase; use Drupal\views\Plugin\ViewsPluginManager; +/** + * Custom cache plugin system for Views. + */ class MetatagViewsCachePluginManager implements PluginManagerInterface, CachedDiscoveryInterface, CacheableDependencyInterface { /** + * {@inheritdoc} + * * @var \Drupal\views\Plugin\ViewsPluginManager */ protected $viewsPluginManager; @@ -19,15 +24,20 @@ class MetatagViewsCachePluginManager implements PluginManagerInterface, CachedDi * MetatagViewsCachePluginManager constructor. * * @param \Drupal\views\Plugin\ViewsPluginManager $views_plugin_manager + * The ViewsPluginManager as argument. */ public function __construct(ViewsPluginManager $views_plugin_manager) { $this->viewsPluginManager = $views_plugin_manager; } /** + * {@inheritdoc} + * * @param \Drupal\views\Plugin\views\cache\CachePluginBase $plugin + * The CachePluginBase as argument. * * @return \Drupal\metatag_views\MetatagViewsCacheWrapper + * Return new MetatagViewsCacheWrapper */ protected function wrap(CachePluginBase $plugin) { return new MetatagViewsCacheWrapper($plugin); diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCacheWrapper.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCacheWrapper.php index 526ce3b2f..040894b06 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCacheWrapper.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/MetatagViewsCacheWrapper.php @@ -25,6 +25,8 @@ class MetatagViewsCacheWrapper extends CachePluginBase { const RESULTS = 'results'; /** + * {@inheritdoc} + * * @var \Drupal\views\Plugin\views\cache\CachePluginBase */ protected $plugin; @@ -72,7 +74,7 @@ class MetatagViewsCacheWrapper extends CachePluginBase { $view = $plugin->view; $data = [ 'result' => $plugin->prepareViewResult($view->result), - 'total_rows' => isset($view->total_rows) ? $view->total_rows : 0, + 'total_rows' => $view->total_rows ?? 0, 'current_page' => $view->getCurrentPage(), 'first_row_tokens' => MetatagDisplayExtender::getFirstRowTokensFromStylePlugin($view), ]; @@ -100,7 +102,7 @@ class MetatagViewsCacheWrapper extends CachePluginBase { $view->execute_time = 0; $extenders = $view->getDisplay()->getExtenders(); if (isset($extenders['metatag_display_extender'])) { - $extenders['metatag_display_extender']->setFirstRowTokens($cache->data['first_row_tokens']); + $extenders['metatag_display_extender']->setFirstRowTokens($cache->data['first_row_tokens'] ?? []); } return TRUE; } @@ -295,8 +297,7 @@ class MetatagViewsCacheWrapper extends CachePluginBase { /** * {@inheritdoc} */ - public function getAvailableGlobalTokens($prepared = FALSE, array $types = [ - ]) { + public function getAvailableGlobalTokens($prepared = FALSE, array $types = []) { return $this->plugin->getAvailableGlobalTokens($prepared, $types); } @@ -384,20 +385,6 @@ class MetatagViewsCacheWrapper extends CachePluginBase { return $this->plugin->setStringTranslation($translation); } - /** - * {@inheritdoc} - */ - public function __sleep() { - return $this->plugin->__sleep(); - } - - /** - * {@inheritdoc} - */ - public function __wakeup() { - $this->plugin->__wakeup(); - } - /** * {@inheritdoc} */ @@ -412,13 +399,18 @@ class MetatagViewsCacheWrapper extends CachePluginBase { $this->plugin->messenger(); } + /** + * {@inheritdoc} + */ public function __get($name) { return $this->plugin->$name; } + /** + * {@inheritdoc} + */ public function __set($name, $value) { $this->plugin->$name = $value; } - } diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Plugin/views/display_extender/MetatagDisplayExtender.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Plugin/views/display_extender/MetatagDisplayExtender.php index 4a2e06365..592be74ea 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Plugin/views/display_extender/MetatagDisplayExtender.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/src/Plugin/views/display_extender/MetatagDisplayExtender.php @@ -59,6 +59,9 @@ class MetatagDisplayExtender extends DisplayExtenderPluginBase { return $instance; } + /** + * {@inheritdoc} + */ protected function defineOptions() { $options = parent::defineOptions(); @@ -68,7 +71,6 @@ class MetatagDisplayExtender extends DisplayExtenderPluginBase { return $options; } - /** * Provide a form to edit options for this plugin. */ @@ -260,8 +262,8 @@ class MetatagDisplayExtender extends DisplayExtenderPluginBase { /** * Store first row tokens on the class. * - * metatag_views_metatag_route_entity() loads the View fresh, to avoid - * rebuilding and re-rendering it, preserve the first row tokens. + * The function metatag_views_metatag_route_entity() loads the View fresh, to + * avoid rebuilding and re-rendering it, preserve the first row tokens. */ public function setFirstRowTokens(array $first_row_tokens) { self::$firstRowTokens = $first_row_tokens; @@ -285,6 +287,7 @@ class MetatagDisplayExtender extends DisplayExtenderPluginBase { * * @param \Drupal\views\ViewExecutable $view * The view. + * * @return array * The first row tokens. */ @@ -294,9 +297,13 @@ class MetatagDisplayExtender extends DisplayExtenderPluginBase { } /** + * Get the first row tokens for this Views object iteration. + * * @param \Drupal\views\Plugin\views\style\StylePluginBase $style + * The style plugin used for this request. * * @return \ReflectionProperty + * The rawTokens property. */ protected static function getFirstRowTokensReflection(StylePluginBase $style): \ReflectionProperty { $r = new \ReflectionObject($style); diff --git a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/tests/src/Functional/MetatagViewsTokenTest.php b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/tests/src/Functional/MetatagViewsTokenTest.php index 1268ab195..5715fd428 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/metatag_views/tests/src/Functional/MetatagViewsTokenTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/metatag_views/tests/src/Functional/MetatagViewsTokenTest.php @@ -50,6 +50,8 @@ class MetatagViewsTokenTest extends BrowserTestBase { $this->loginUser1(); $page_path = $this->randomMachineName(); $this->drupalGet('/admin/structure/views/add'); + // @todo Also verify the form loads correctly. + $this->assertSession()->statusCodeEquals(200); $edit = [ 'label' => $this->randomString(), 'id' => 'test', @@ -62,12 +64,18 @@ class MetatagViewsTokenTest extends BrowserTestBase { $node_title = $this->randomString(); $this->createContentTypeNode($node_title); $this->drupalGet("/$page_path"); + $this->assertSession()->statusCodeEquals(200); $this->assertSession()->titleEquals("$title_prefix $node_title"); + // Test caching by asserting a change of the View changes the page as well. $title_prefix = $this->updateView(); $this->drupalGet("/$page_path"); + $this->assertSession()->statusCodeEquals(200); $this->assertSession()->titleEquals("$title_prefix $node_title"); + + // Reload the page and confirm the values persist. $this->drupalGet("/$page_path"); + $this->assertSession()->statusCodeEquals(200); $this->assertSession()->titleEquals("$title_prefix $node_title"); } @@ -78,6 +86,7 @@ class MetatagViewsTokenTest extends BrowserTestBase { * Also assert the Views UI behaves correctly. * * @return string + * The title with its full prefix. */ protected function updateView(bool $assert_ui = FALSE): string { $title_prefix = $this->randomMachineName(); @@ -87,11 +96,16 @@ class MetatagViewsTokenTest extends BrowserTestBase { ]; $metatag_settings_path = '/admin/structure/views/nojs/display/test/page_1/metatags'; $this->drupalGet($metatag_settings_path); + $this->assertSession()->statusCodeEquals(200); $this->submitForm($edit, 'Apply'); + // @todo Also verify the page contains the correct response. + $this->assertSession()->statusCodeEquals(200); + // Make sure the UI does not tokenize away {{ title }}. if ($assert_ui) { - // Reload the form + // Reload the form. $this->drupalGet($metatag_settings_path); + $this->assertSession()->statusCodeEquals(200); $actual = $this->getSession() ->getPage() ->find('css', '#edit-title') @@ -99,7 +113,12 @@ class MetatagViewsTokenTest extends BrowserTestBase { $this->assertSame($edit['title'], $actual); } $this->drupalGet('/admin/structure/views/view/test'); + // @todo Also verify the page contains the correct response. + $this->assertSession()->statusCodeEquals(200); $this->submitForm([], 'Save'); + // @todo Also verify the page contains the correct response. + $this->assertSession()->statusCodeEquals(200); + return $title_prefix; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Controller/MetatagController.php b/frontend/drupal9/web/modules/contrib/metatag/src/Controller/MetatagController.php index 897697330..b3cc732e3 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Controller/MetatagController.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Controller/MetatagController.php @@ -61,7 +61,10 @@ class MetatagController extends ControllerBase { public function reportPlugins() { // Get tags. $tag_definitions = $this->tagManager->getDefinitions(); - uasort($tag_definitions, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + uasort($tag_definitions, [ + 'Drupal\Component\Utility\SortArray', + 'sortByWeightElement', + ]); $tags = []; foreach ($tag_definitions as $tag_name => $tag_definition) { $tags[$tag_definition['group']][$tag_name] = $tag_definition; @@ -69,7 +72,10 @@ class MetatagController extends ControllerBase { // Get groups. $group_definitions = $this->groupManager->getDefinitions(); - uasort($group_definitions, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + uasort($group_definitions, [ + 'Drupal\Component\Utility\SortArray', + 'sortByWeightElement', + ]); // Build plugin by group. $build = []; @@ -83,7 +89,7 @@ class MetatagController extends ControllerBase { ]; // Group description. $build[$group_name]['description'] = [ - '#markup' => $group_definition['description'], + '#markup' => $group_definition['description'] ?? '', '#prefix' => '

', '#suffix' => '

', ]; @@ -115,7 +121,7 @@ class MetatagController extends ControllerBase { $row = []; $row['description'] = [ 'data' => [ - '#markup' => $definition['description'], + '#markup' => $definition['description'] ?? '', ], 'colspan' => 8, ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagDefaultsForm.php b/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagDefaultsForm.php index e1f802857..7ac696db8 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagDefaultsForm.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagDefaultsForm.php @@ -17,7 +17,7 @@ use Drupal\page_manager\Entity\PageVariant; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Class MetatagDefaultsForm. + * Form handler for the Metatag Defaults entity type. * * @package Drupal\metatag\Form */ @@ -157,16 +157,15 @@ class MetatagDefaultsForm extends EntityForm { $entity_type_groups = $settings->get('entity_type_groups'); // Find the current entity type and bundle. - $metatag_defaults_id = $metatag_defaults->id(); - $type_parts = explode('__', $metatag_defaults_id); - $entity_type = $type_parts[0]; - $entity_bundle = isset($type_parts[1]) ? $type_parts[1] : NULL; + if ($metatag_defaults_id = $metatag_defaults->id()) { + $type_parts = explode('__', $metatag_defaults_id); + $entity_type = $type_parts[0]; + $entity_bundle = $type_parts[1] ?? NULL; + } // See if there are requested groups for this entity type and bundle. - $groups = !empty($entity_type_groups[$entity_type]) && !empty($entity_type_groups[$entity_type][$entity_bundle]) ? $entity_type_groups[$entity_type][$entity_bundle] : []; - // Limit the form to requested groups, if any. - if (!empty($groups)) { - $form = $this->metatagManager->form($values, $form, [$entity_type], $groups, NULL, TRUE); + if (isset($entity_type) && !empty($entity_type_groups[$entity_type]) && !empty($entity_type_groups[$entity_type][$entity_bundle])) { + $form = $this->metatagManager->form($values, $form, [$entity_type], $entity_type_groups[$entity_type][$entity_bundle], NULL, TRUE); } // Otherwise, display all groups. else { @@ -240,7 +239,7 @@ class MetatagDefaultsForm extends EntityForm { $type_parts = explode('__', $metatag_defaults_id); $entity_type = $type_parts[0]; - $entity_bundle = isset($type_parts[1]) ? $type_parts[1] : NULL; + $entity_bundle = $type_parts[1] ?? NULL; // Get the entity label. $entity_info = $this->entityTypeManager->getDefinitions(); @@ -283,6 +282,10 @@ class MetatagDefaultsForm extends EntityForm { } } } + + // Sort the values prior to saving. so that they are easier to manage. + ksort($tag_values); + $metatag_defaults->set('tags', $tag_values); $status = $metatag_defaults->save(); diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagSettingsForm.php b/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagSettingsForm.php index 1fa2354c6..1f61f8796 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagSettingsForm.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Form/MetatagSettingsForm.php @@ -126,14 +126,16 @@ class MetatagSettingsForm extends ConfigFormBase { '#tree' => TRUE, ]; - foreach ($metatags as $metatag_name => $metatag_info) { + // Name the variable "metatag_id" to avoid confusing this with the "name" + // value from the meta tag plugin as it's actually the plugin ID. + foreach ($metatags as $metatag_id => $metatag_info) { if (!empty($metatag_info['trimmable'])) { - $form['tag_trim']['maxlength']['metatag_maxlength_' . $metatag_name] = [ - '#title' => $this->t('Meta Tags:') . ' ' . $metatag_name . ' ' . $this->t('length'), + $form['tag_trim']['maxlength']['metatag_maxlength_' . $metatag_id] = [ + '#title' => $this->t('Meta Tags:') . ' ' . $metatag_id . ' ' . $this->t('length'), '#type' => 'number', '#required' => FALSE, - '#default_value' => $trimSettingsMaxlength['metatag_maxlength_' . $metatag_name] ?? NULL, + '#default_value' => $trimSettingsMaxlength['metatag_maxlength_' . $metatag_id] ?? NULL, '#min' => 0, '#step' => 1, ]; @@ -151,6 +153,24 @@ class MetatagSettingsForm extends ConfigFormBase { 'beforeValue' => $this->t('Trim the Meta Tag before the word on the given value'), ], ]; + + $scrollheight = $this->config('metatag.settings')->get('tag_scroll_max_height'); + + $form['firehose_widget'] = [ + '#title' => $this->t('Metatag widget options'), + '#type' => 'details', + '#tree' => TRUE, + '#open' => TRUE, + '#description' => $this->t("Various options for the field widget used on entity forms, e.g. on content type forms."), + ]; + + $form['firehose_widget']['tag_scroll_max_height'] = [ + '#title' => $this->t('Scroll maximum height'), + '#type' => 'textfield', + '#default_value' => $scrollheight, + '#placeholder' => $this->t('eg 500px or 8rem'), + '#description' => $this->t("To enable scrolling please enter a value and its units, e.g. 500px, 8rem, etc. Removing this value will remove the scroll."), + ]; return parent::buildForm($form, $form_state); } @@ -176,6 +196,10 @@ class MetatagSettingsForm extends ConfigFormBase { $trimmingValues = $form_state->getValue(['tag_trim', 'maxlength']); $settings->set('tag_trim_maxlength', $trimmingValues); + // Widget settings. + $scrollheightvalue = $form_state->getValue(['firehose_widget', 'tag_scroll_max_height']); + $settings->set('tag_scroll_max_height', $scrollheightvalue); + $settings->save(); parent::submitForm($form, $form_state); } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/MetatagDefaultsListBuilder.php b/frontend/drupal9/web/modules/contrib/metatag/src/MetatagDefaultsListBuilder.php index b68d456f8..ed86df823 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/MetatagDefaultsListBuilder.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/MetatagDefaultsListBuilder.php @@ -125,7 +125,7 @@ class MetatagDefaultsListBuilder extends ConfigEntityListBuilder { '; foreach ($tags as $tag_id => $tag_value) { if (is_array($tag_value)) { - $tag_value = implode(', ', $tag_value); + $tag_value = implode(', ', array_filter($tag_value)); } $output .= '' . $tag_id . ':' . $tag_value . ''; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/MetatagManager.php b/frontend/drupal9/web/modules/contrib/metatag/src/MetatagManager.php index 4035cc8eb..3e1c6a256 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/MetatagManager.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/MetatagManager.php @@ -10,9 +10,13 @@ use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\views\ViewEntityInterface; +use Drupal\Core\Path\PathMatcherInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Drupal\Core\Language\LanguageManagerInterface; /** - * Class MetatagManager. + * Primary logic for the Metatag module. * * @package Drupal\metatag */ @@ -55,6 +59,34 @@ class MetatagManager implements MetatagManagerInterface { */ protected $logger; + /** + * The path matcher. + * + * @var \Drupal\Core\Path\PathMatcherInterface + */ + protected $pathMatcher; + + /** + * The route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + /** * Caches processed strings, keyed by tag name. * @@ -68,32 +100,48 @@ class MetatagManager implements MetatagManagerInterface { * @param \Drupal\metatag\MetatagGroupPluginManager $groupPluginManager * The MetatagGroupPluginManager object. * @param \Drupal\metatag\MetatagTagPluginManager $tagPluginManager - * The MetatagTagPluginMÏ€anager object. + * The MetatagTagPluginManager object. * @param \Drupal\metatag\MetatagToken $token * The MetatagToken object. * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $channelFactory * The LoggerChannelFactoryInterface object. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The EntityTypeManagerInterface object. + * @param \Drupal\Core\Path\PathMatcherInterface $pathMatcher + * The path matcher. + * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch + * The route match. + * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack + * The request stack. + * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager + * The language manager. */ public function __construct(MetatagGroupPluginManager $groupPluginManager, MetatagTagPluginManager $tagPluginManager, MetatagToken $token, LoggerChannelFactoryInterface $channelFactory, - EntityTypeManagerInterface $entityTypeManager + EntityTypeManagerInterface $entityTypeManager, + PathMatcherInterface $pathMatcher, + RouteMatchInterface $routeMatch, + RequestStack $requestStack, + LanguageManagerInterface $languageManager ) { $this->groupPluginManager = $groupPluginManager; $this->tagPluginManager = $tagPluginManager; $this->tokenService = $token; $this->logger = $channelFactory->get('metatag'); $this->metatagDefaults = $entityTypeManager->getStorage('metatag_defaults'); + $this->pathMatcher = $pathMatcher; + $this->routeMatch = $routeMatch; + $this->requestStack = $requestStack; + $this->languageManager = $languageManager; } /** * Returns the list of protected defaults. * * @return array - * Th protected defaults. + * The protected defaults. */ public static function protectedDefaults() { return [ @@ -186,7 +234,7 @@ class MetatagManager implements MetatagManagerInterface { foreach ($metatag_groups as $group_name => $group_info) { $groups[$group_name]['id'] = $group_info['id']; $groups[$group_name]['label'] = $group_info['label']->render(); - $groups[$group_name]['description'] = $group_info['description']; + $groups[$group_name]['description'] = $group_info['description'] ?? ''; $groups[$group_name]['weight'] = $group_info['weight']; } @@ -243,7 +291,10 @@ class MetatagManager implements MetatagManagerInterface { if (!isset($groups[$tag_group])) { // If the tag is claiming a group that has no matching plugin, log an // error and force it to the basic group. - $this->logger->error("Undefined group '%group' on tag '%tag'", ['%group' => $tag_group, '%tag' => $tag_name]); + $this->logger->error("Undefined group '%group' on tag '%tag'", [ + '%group' => $tag_group, + '%tag' => $tag_name, + ]); $tag['group'] = 'basic'; $tag_group = 'basic'; } @@ -280,7 +331,7 @@ class MetatagManager implements MetatagManagerInterface { // Create the fieldset. $element[$group_name]['#type'] = 'details'; $element[$group_name]['#title'] = $group['label']; - $element[$group_name]['#description'] = $group['description']; + $element[$group_name]['#description'] = $group['description'] ?? ''; $element[$group_name]['#open'] = FALSE; foreach ($group['tags'] as $tag_name => $tag) { @@ -290,7 +341,7 @@ class MetatagManager implements MetatagManagerInterface { $tag = $this->tagPluginManager->createInstance($tag_name); // Set the value to the stored value, if any. - $tag_value = isset($values[$tag_name]) ? $values[$tag_name] : NULL; + $tag_value = $values[$tag_name] ?? NULL; $tag->setValue($tag_value); // Open any groups that have non-empty values. @@ -359,7 +410,7 @@ class MetatagManager implements MetatagManagerInterface { // Get serialized value and break it into an array of tags with values. $serialized_value = $item->get('value')->getValue(); if (!empty($serialized_value)) { - $tags += unserialize($serialized_value); + $tags += unserialize($serialized_value, ['allowed_classes' => FALSE]); } } @@ -380,7 +431,7 @@ class MetatagManager implements MetatagManagerInterface { $metatags = $this->getGlobalMetatags(); // If that is empty something went wrong. if (!$metatags) { - return; + return []; } // Check if this is a special page. @@ -430,13 +481,13 @@ class MetatagManager implements MetatagManagerInterface { public function getSpecialMetatags() { $metatags = NULL; - if (\Drupal::service('path.matcher')->isFrontPage()) { + if ($this->pathMatcher->isFrontPage()) { $metatags = $this->metatagDefaults->load('front'); } - elseif (\Drupal::service('current_route_match')->getRouteName() == 'system.403') { + elseif ($this->routeMatch->getRouteName() == 'system.403') { $metatags = $this->metatagDefaults->load('403'); } - elseif (\Drupal::service('current_route_match')->getRouteName() == 'system.404') { + elseif ($this->routeMatch->getRouteName() == 'system.404') { $metatags = $this->metatagDefaults->load('404'); } @@ -519,7 +570,7 @@ class MetatagManager implements MetatagManagerInterface { */ public function generateRawElements(array $tags, $entity = NULL, BubbleableMetadata $cache = NULL) { // Ignore the update.php path. - $request = \Drupal::request(); + $request = $this->requestStack->getCurrentRequest(); if ($request->getBaseUrl() == '/update.php') { return []; } @@ -527,6 +578,7 @@ class MetatagManager implements MetatagManagerInterface { // Prepare any tokens that might exist. $token_replacements = []; if ($entity) { + // @todo This needs a better way of discovering the context. if ($entity instanceof ViewEntityInterface) { // Views tokens require the ViewExecutable, not the config entity. @@ -537,16 +589,15 @@ class MetatagManager implements MetatagManagerInterface { $token_replacements = [$entity->getEntityTypeId() => $entity]; } } - - // Get the current language code. - $langcode = \Drupal::languageManager() - ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT) - ->getId(); - $rawTags = []; - $metatag_tags = $this->tagPluginManager->getDefinitions(); + // Use the entity's language code, if one is defined. + $langcode = NULL; + if ($entity) { + $langcode = $entity->language()->getId(); + } + // Order metatags based on the group and weight. $group = array_column($metatag_tags, 'group'); $weight = array_column($metatag_tags, 'weight'); @@ -567,24 +618,8 @@ class MetatagManager implements MetatagManagerInterface { // Get an instance of the plugin. $tag = $this->tagPluginManager->createInstance($tag_name); - // Set the value as sometimes the data needs massaging, such as when - // field defaults are used for the Robots field, which come as an array - // that needs to be filtered and converted to a string. - // @see Robots::setValue() - $tag->setValue($value); - - // Obtain the processed value. Some meta tags will store this as a - // string, so support that option. - $value = $tag->value(); - if (is_array($value)) { - $processed_value = []; - foreach ($value as $key => $value_item) { - $processed_value[$key] = htmlspecialchars_decode($this->tokenService->replace($value_item, $token_replacements, ['langcode' => $langcode])); - } - } - else { - $processed_value = htmlspecialchars_decode($this->tokenService->replace($value, $token_replacements, ['langcode' => $langcode])); - } + // Prepare value. + $processed_value = $this->processTagValue($tag, $value, $token_replacements, FALSE, $langcode); // Now store the value with processed tokens back into the plugin. $tag->setValue($processed_value); @@ -625,14 +660,20 @@ class MetatagManager implements MetatagManagerInterface { */ public function generateTokenValues(array $tags, $entity = NULL) { // Ignore the update.php path. - $request = \Drupal::request(); + $request = $this->requestStack->getCurrentRequest(); if ($request->getBaseUrl() == '/update.php') { return []; } $entity_identifier = '_none'; if ($entity) { - $entity_identifier = $entity->getEntityTypeId() . ':' . ($entity->uuid() ?: $entity->id()); + $entity_identifier = $entity->getEntityTypeId() . ':' . ($entity->uuid() ?? $entity->id()); + } + + // Use the entity's language code, if one is defined. + $langcode = NULL; + if ($entity) { + $langcode = $entity->language()->getId(); } if (!isset($this->processedTokenCache[$entity_identifier])) { @@ -659,15 +700,8 @@ class MetatagManager implements MetatagManagerInterface { $token_replacements = [$entity->getEntityTypeId() => $entity]; } } - - // Set the value as sometimes the data needs massaging, such as when - // field defaults are used for the Robots field, which come as an - // array that needs to be filtered and converted to a string. - // @see Robots::setValue() - $tag->setValue($value); - $langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); - $value = PlainTextOutput::renderFromHtml(htmlspecialchars_decode($this->tokenService->replace($value, $token_replacements, ['langcode' => $langcode]))); - $this->processedTokenCache[$entity_identifier][$tag_name] = $tag->multiple() ? explode(',', $value) : $value; + $processed_value = $this->processTagValue($tag, $value, $token_replacements, TRUE); + $this->processedTokenCache[$entity_identifier][$tag_name] = $tag->multiple() ? explode(',', $processed_value) : $processed_value; } } } @@ -687,4 +721,66 @@ class MetatagManager implements MetatagManagerInterface { return ['metatag']; } + /** + * Sets tag value and returns sanitized value with token replaced. + * + * @param \Drupal\metatag\Plugin\metatag\Tag\MetaNameBase|object $tag + * Metatag object. + * @param array|string $value + * Value to process. + * @param array $token_replacements + * Arguments for token->replace(). + * @param bool $plain_text + * (optional) If TRUE, value will be formatted as a plain text. Defaults to + * FALSE. + * @param string $langcode + * (optional) The language code to use for replacements; if not provided the + * current interface language code will be used. + * + * @return array|string + * Processed value. + */ + protected function processTagValue($tag, $value, array $token_replacements, bool $plain_text = FALSE, $langcode = FALSE) { + // Set the value as sometimes the data needs massaging, such as when + // field defaults are used for the Robots field, which come as an array + // that needs to be filtered and converted to a string. + // @see Robots::setValue() + $tag->setValue($value); + + // Obtain the processed value. Some meta tags will store this as a + // string, so support that option. + // @todo Is there a better way of doing this? It seems unclean. + $value = $tag->value(); + + // Make sure the value is always handled as an array, but track whether it + // was actually passed in as an array. + $is_array = is_array($value); + if (!$is_array) { + $value = [$value]; + } + + // If a langcode was not specified, use the current interface language. + if (empty($langcode)) { + $langcode = $this->languageManager + ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT) + ->getId(); + } + + // Loop over each item in the array. + foreach ($value as $key => $value_item) { + // Process the tokens in this value and decode any HTML characters that + // might be found. + $value[$key] = htmlspecialchars_decode($this->tokenService->replace($value_item, $token_replacements, ['langcode' => $langcode])); + + // If requested, run the value through the render system. + if ($plain_text) { + $value[$key] = PlainTextOutput::renderFromHtml($value[$key]); + } + } + + // If the original value was passed as an array return the whole value, + // otherwise return the first item from the array. + return $is_array ? $value : reset($value); + } + } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/MetatagTrimmer.php b/frontend/drupal9/web/modules/contrib/metatag/src/MetatagTrimmer.php index bf1aebf18..6fe035ba4 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/MetatagTrimmer.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/MetatagTrimmer.php @@ -21,11 +21,17 @@ class MetatagTrimmer { * The trimmed string. */ public function trimAfterValue($string, $maxlength) { - $spacePos = strpos($string, ' ', $maxlength - 1); + // If the string is shorter than the max length then skip the rest of the + // logic. + if ($maxlength > mb_strlen($string)) { + return $string; + } + + $spacePos = mb_strpos($string, ' ', $maxlength - 1); if (FALSE === $spacePos) { return $string; } - $subString = substr($string, 0, $spacePos); + $subString = mb_substr($string, 0, $spacePos); return trim($subString); } @@ -42,15 +48,21 @@ class MetatagTrimmer { * The trimmed string. */ public function trimBeforeValue($string, $maxlength) { - $subString = substr($string, 0, $maxlength + 1); - if (' ' === substr($subString, -1)) { + // If the string is shorter than the max length then skip the rest of the + // logic. + if ($maxlength > mb_strlen($string)) { + return $string; + } + + $subString = mb_substr($string, 0, $maxlength + 1); + if (' ' === mb_substr($subString, -1)) { return trim($subString); } - $spacePos = strrpos($subString, ' ', 0); + $spacePos = mb_strrpos($subString, ' ', 0); if (FALSE === $spacePos) { return $string; } - $returnedString = substr($string, 0, $spacePos); + $returnedString = mb_substr($string, 0, $spacePos); return trim($returnedString); } @@ -76,7 +88,7 @@ class MetatagTrimmer { return $this->trimAfterValue($value, $maxlength); case 'onValue': - return trim(substr($value, 0, $maxlength)); + return trim(mb_substr($value, 0, $maxlength)); case 'beforeValue': return $this->trimBeforeValue($value, $maxlength); diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php index 32584acf2..2c2612801 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldType/MetatagFieldItem.php @@ -70,7 +70,7 @@ class MetatagFieldItem extends FieldItemBase { $current_value = $this->value; // Only unserialize if still serialized string. if (is_string($current_value)) { - $current_tags = unserialize($current_value); + $current_tags = unserialize($current_value, ['allowed_classes' => FALSE]); } else { $current_tags = $current_value; @@ -85,6 +85,9 @@ class MetatagFieldItem extends FieldItemBase { } } + // Sort the values prior to saving. so that they are easier to manage. + ksort($tags_to_save); + // Update the value to only save overridden tags. $this->value = serialize($tags_to_save); } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php index 94b55f6fc..b5709c98d 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/FieldWidget/MetatagFirehose.php @@ -141,7 +141,14 @@ class MetatagFirehose extends WidgetBase implements ContainerFactoryPluginInterf // Retrieve the values for each metatag from the serialized array. $values = []; if (!empty($item->value)) { - $values = unserialize($item->value); + $values = unserialize($item->value, ['allowed_classes' => FALSE]); + } + + // Make sure that this variable is always an array to avoid problems when + // unserializing didn't work correctly and it as returned as FALSE. + // @see https://www.php.net/unserialize + if (!is_array($values)) { + $values = []; } // Populate fields which have not been overridden in the entity. @@ -193,6 +200,15 @@ class MetatagFirehose extends WidgetBase implements ContainerFactoryPluginInterf $element['#type'] = 'container'; } + // Scroll height configuration. + $scroll_height = $settings->get('tag_scroll_max_height'); + if (!empty($scrollheight)) { + $form['#attached']['drupalSettings']['metatag']['max_height'] = $scroll_height; + $form['#attached']['library'][] = 'metatag/firehose_widget'; + $element['#attributes']['class'][] = 'metatags'; + $element['#attributes']['style'][] = 'max-height:' . $scroll_height . ';'; + } + return $element; } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/MetatagEntityFieldItemList.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/MetatagEntityFieldItemList.php index f48c90dd6..bdc6a3e71 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/MetatagEntityFieldItemList.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/Field/MetatagEntityFieldItemList.php @@ -22,5 +22,4 @@ class MetatagEntityFieldItemList extends FieldItemList { return NULL; } - } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Group/GroupBase.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Group/GroupBase.php index 0c2770ce8..eac73d587 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Group/GroupBase.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Group/GroupBase.php @@ -42,7 +42,7 @@ abstract class GroupBase extends PluginBase { // @todo Should we have setProperty() methods for each of these? $this->id = $plugin_definition['id']; $this->label = $plugin_definition['label']; - $this->description = $plugin_definition['description']; + $this->description = $plugin_definition['description'] ?? ''; } /** diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Author.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Author.php new file mode 100644 index 000000000..3c9084614 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Author.php @@ -0,0 +1,23 @@ + $this->label(), '#default_value' => $this->value(), '#maxlength' => 1024, - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#description' => $this->description(), '#element_validate' => [[get_class($this), 'validateTag']], ]; @@ -482,7 +482,7 @@ abstract class MetaNameBase extends PluginBase { } $currentMaxValue = 0; foreach ($trimMaxlengthArray as $metaTagName => $maxValue) { - if ($metaTagName == 'metatag_maxlength_' . $this->name) { + if ($metaTagName == 'metatag_maxlength_' . $this->id) { $currentMaxValue = $maxValue; } } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Referrer.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Referrer.php index def080a51..48869b9c7 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Referrer.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Referrer.php @@ -44,7 +44,7 @@ class Referrer extends MetaNameBase { '#empty_option' => $this->t('- None -'), '#empty_value' => '', '#default_value' => $this->value(), - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#element_validate' => [[get_class($this), 'validateTag']], ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Robots.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Robots.php index 492546a67..e2dd377be 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Robots.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Robots.php @@ -103,7 +103,7 @@ class Robots extends MetaNameBase { ], ], '#default_value' => $default_value, - '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#required' => $element['#required'] ?? FALSE, '#element_validate' => [[get_class($this), 'validateTag']], ]; diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Title.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Title.php index 61fad6ed7..f2a4f2ec4 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Title.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/metatag/Tag/Title.php @@ -19,28 +19,5 @@ namespace Drupal\metatag\Plugin\metatag\Tag; * ) */ class Title extends MetaNameBase { - - /** - * Override the output of this tag so it's an actual TITLE tag. - * - * @todo Override the existing title tag X-) - */ - // {@code} - // public function output() { - // if (empty($this->value)) { - // // If there is no value, we don't want a tag output. - // $element = ''; - // } - // else { - // $element = [ - // '#theme' => 'hidden', - // // '#tag' => 'title', - // '#value' => $this->value(), - // ]; - // } - // - // return $element; - // } - // {@endcode} - + // Nothing here yet. Just a placeholder class for a plugin. } diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php index b6544d1b5..1e0a8ca7f 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d6/NodewordsEntities.php @@ -38,7 +38,7 @@ class NodewordsEntities extends ProcessPluginBase { // Re-shape D6 entries into for D8 entries. $old_tags = array_map(static function ($value) { - return unserialize($value); + return unserialize($value, ['allowed_classes' => FALSE]); }, $value); foreach ($old_tags as $d6_metatag_name => $metatag_value) { diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php index 74231e3d9..f5a1e11bc 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/process/d7/MetatagEntities.php @@ -28,7 +28,7 @@ class MetatagEntities extends ProcessPluginBase { } // Re-shape D7 entries into for D8 entries. - $old_tags = unserialize($value); + $old_tags = unserialize($value, ['allowed_classes' => FALSE]); // This is expected to be an array, if it isn't then something went wrong. if (!is_array($old_tags)) { @@ -75,6 +75,9 @@ class MetatagEntities extends ProcessPluginBase { $metatags[$d8_metatag_name] = $metatag_value; } + // Sort the meta tags alphabetically to make testing easier. + ksort($metatags); + return serialize($metatags); } @@ -429,8 +432,8 @@ class MetatagEntities extends ProcessPluginBase { // From metatag_verification.metatag.inc: 'baidu-site-verification' => 'baidu', 'facebook-domain-verification' => 'facebook_domain_verification', - 'google-site-verification' => 'bing', - 'msvalidate.01' => 'google', + 'google-site-verification' => 'google_site_verification', + 'msvalidate.01' => 'bing', 'norton-safeweb-site-verification' => 'norton_safe_web', 'p:domain_verify' => 'pinterest', // @todo '' => 'pocket', diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsField.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsField.php index 00a1c7f89..e03225d23 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsField.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsField.php @@ -30,17 +30,23 @@ class NodewordsField extends DrupalSqlBase { $instances = []; foreach (parent::initializeIterator() as $instance) { switch ($instance['type']) { + // @code // define('NODEWORDS_TYPE_NODE', 5); + // @endcode case 5: $instance['entity_type'] = 'node'; break; + // @code // define('NODEWORDS_TYPE_TERM', 6); + // @endcode case 6: $instance['entity_type'] = 'taxonomy_term'; break; + // @code // define('NODEWORDS_TYPE_USER', 8); + // @endcode case 8: $instance['entity_type'] = 'user'; break; diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php index ec60b69b6..d56085e92 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d6/NodewordsFieldInstance.php @@ -71,18 +71,24 @@ class NodewordsFieldInstance extends DrupalSqlBase { foreach (parent::initializeIterator() as $instance) { $entity_type = NULL; switch ($instance['type']) { + // @code + // define('NODEWORDS_TYPE_NODE', 5); + // @endcode case 5: - // define('NODEWORDS_TYPE_NODE', 5); $entity_type = 'node'; break; + // @code + // define('NODEWORDS_TYPE_TERM', 6); + // @endcode case 6: - // define('NODEWORDS_TYPE_TERM', 6); $entity_type = 'taxonomy_term'; break; + // @code + // define('NODEWORDS_TYPE_USER', 8); + // @endcode case 8: - // define('NODEWORDS_TYPE_USER', 8); $entity_type = 'user'; break; diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldDeriver.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldDeriver.php index 294dc2b47..1f4c0be00 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldDeriver.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldDeriver.php @@ -4,18 +4,16 @@ namespace Drupal\metatag\Plugin\migrate\source\d7; use Drupal\Component\Plugin\Derivative\DeriverBase; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\migrate\Exception\RequirementsException; use Drupal\migrate\Plugin\MigrationDeriverTrait; use Drupal\migrate_drupal\MigrationConfigurationTrait; -use Drupal\migrate_drupal\NodeMigrateType; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * @todo. + * Creates field plugins for each entity type. */ class MetatagFieldDeriver extends DeriverBase implements ContainerDeriverInterface { diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php index 69bec1872..eb1beaf3f 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstance.php @@ -60,8 +60,8 @@ class MetatagFieldInstance extends DrupalSqlBase { $bundle = $this->configuration['bundle']; switch ($entity_type_id) { case 'node': - // We want to get a per-node-type metatag migration. So we inner join - // the base query on node table based on the parsed node ID. + // We want to get a per-node-type metatag migration. So we inner + // join the base query on node table based on the parsed node ID. $base_query->join('node', 'n', "n.nid = m.entity_id"); $base_query->condition('n.type', $bundle); $base_query->addField('n', 'type', 'bundle'); diff --git a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstanceDeriver.php b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstanceDeriver.php index 832559232..8c49395c0 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstanceDeriver.php +++ b/frontend/drupal9/web/modules/contrib/metatag/src/Plugin/migrate/source/d7/MetatagFieldInstanceDeriver.php @@ -13,7 +13,10 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Deriver for d7_metatag_field_instance and d7_metatag_field_instance_widget_settings. + * Deriver for Metatag-D7 field instances. + * + * Covers d7_metatag_field_instance and + * d7_metatag_field_instance_widget_settings. */ class MetatagFieldInstanceDeriver extends DeriverBase implements ContainerDeriverInterface { @@ -170,6 +173,7 @@ class MetatagFieldInstanceDeriver extends DeriverBase implements ContainerDerive case 'node': $this->derivatives[$derivative_id]['migration_dependencies']['required'][] = "d7_node_type:$bundle_id"; break; + case 'taxonomy_term': $this->derivatives[$derivative_id]['migration_dependencies']['required'][] = "d7_taxonomy_vocabulary:$bundle_id"; break; diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml b/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml index 6f1922643..88dbbb85a 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_custom_route/metatag_test_custom_route.info.yml @@ -6,7 +6,7 @@ package: Testing dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml b/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml index 8d5a0504a..374b3ef55 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_integration/metatag_test_integration.info.yml @@ -5,7 +5,7 @@ package: Testing dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml b/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml index 11929a8a9..5a4fe794f 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/modules/metatag_test_tag/metatag_test_tag.info.yml @@ -6,7 +6,7 @@ package: Testing dependencies: - metatag:metatag -# Information added by Drupal.org packaging script on 2022-01-06 -version: '8.x-1.19' +# Information added by Drupal.org packaging script on 2022-07-12 +version: '8.x-1.20' project: 'metatag' -datestamp: 1641496016 +datestamp: 1657649279 diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/DefaultTags.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/DefaultTags.php index fb52570c1..485c8ddbe 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/DefaultTags.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/DefaultTags.php @@ -133,7 +133,7 @@ class DefaultTags extends BrowserTestBase { * Test the default values for the user login page, etc. */ public function testUserLoginPages() { - $front_url = $this->buildUrl('', ['absolute' => TRUE]);; + $front_url = $this->buildUrl('', ['absolute' => TRUE]); // A list of paths to examine. $routes = [ diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/LanguageHandlingTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/LanguageHandlingTest.php new file mode 100644 index 000000000..28e5b2031 --- /dev/null +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/LanguageHandlingTest.php @@ -0,0 +1,116 @@ +drupalCreateContentType(['type' => 'article']); + + // Setup admin user. + $this->adminAccount = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'administer languages', + 'administer url aliases', + 'create article content', + 'edit any article content', + 'edit own article content', + ]); + + $this->drupalLogin($this->adminAccount); + + // Add the German language. + $this->drupalGet('admin/config/regional/language/add'); + $this->submitForm(['predefined_langcode' => 'de'], 'Add language'); + $this->assertSession()->pageTextContains('The language German has been created and can now be used.'); + + // Set admin user language to German. + $this->adminAccount->set('preferred_langcode', 'de')->save(); + + // Set up detection and selection to not use URL detection. + $this->drupalGet('admin/config/regional/language/detection'); + $this->submitForm([ + 'language_interface[enabled][language-url]' => 0, + 'language_interface[enabled][language-user]' => 1, + ], 'Save settings'); + + $this->assertSession()->pageTextContains('Language detection configuration saved.'); + + $this->drupalLogout(); + } + + /** + * Tests URL aliases work. + */ + public function testPathAlias() { + // Login as admin with German as site language. + $this->drupalLogin($this->adminAccount); + + // Create article with alias in sites default language (English). + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + $alias = '/test-content'; + $edit = [ + 'path[0][alias]' => $alias, + 'title[0][value]' => 'Test content', + ]; + $this->submitForm($edit, 'Save'); + + // Check that article was created and check address. + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals($alias); + + // Test that the canonical link is the same as address. + $xpath = $this->assertSession()->buildXPathQuery("//link[@rel=:rel and contains(@href, :href)]", [ + ':href' => $alias, + ':rel' => 'canonical', + ]); + $links = $this->getSession()->getPage()->findAll('xpath', $xpath); + + $this->assertNotEmpty($links); + } + +} diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagAdminTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagAdminTest.php index 7cdef7d9c..2c09268e8 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagAdminTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagAdminTest.php @@ -521,7 +521,7 @@ class MetatagAdminTest extends BrowserTestBase { $session->pageTextNotContains('Meta Tags: robots length'); // Test if option for a trimmable metatag exists: $session->pageTextContains('Meta Tags: description length'); - // Test if the the title,abstract and description header gets trimmed: + // Test if the title,abstract and description header gets trimmed: // Change description abstract and title on the front page: $this->drupalGet('/admin/config/search/metatag/front'); $page->fillField('edit-title', 'my wonderful drupal test site'); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldTestBase.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldTestBase.php index 2a73978e9..9002fac04 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldTestBase.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldTestBase.php @@ -281,7 +281,7 @@ abstract class MetatagFieldTestBase extends BrowserTestBase { * * When there is no field for overriding the defaults. * - * @todo + * @todo Write this. */ public function testBundleDefaultsInheritance() { } @@ -353,7 +353,7 @@ abstract class MetatagFieldTestBase extends BrowserTestBase { } // Create a new entity object. - $this->submitForm($edit, $this->t($this->entitySaveButtonLabel)); + $this->submitForm($edit, $this->entitySaveButtonLabel); $entities = \Drupal::entityTypeManager() ->getStorage($this->entityType) ->loadByProperties([$this->entityTitleField => $title]); @@ -436,7 +436,7 @@ abstract class MetatagFieldTestBase extends BrowserTestBase { } // Create a new entity object. - $this->submitForm($edit, $this->t($this->entitySaveButtonLabel)); + $this->submitForm($edit, $this->entitySaveButtonLabel); $entities = \Drupal::entityTypeManager() ->getStorage($this->entityType) ->loadByProperties([$this->entityTitleField => $title]); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldUserTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldUserTest.php index baa76318a..5ffda1b7e 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldUserTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFieldUserTest.php @@ -95,14 +95,14 @@ class MetatagFieldUserTest extends MetatagFieldTestBase { /** * Confirm the metatag field can be shown on a user registration page. * - * @todo + * @todo Write this. */ public function testFieldsOnUserRegistrationForm() {} /** * Confirm the metatag field can be shown on a normal user's own edit form. * - * @todo + * @todo Write this. */ public function testFieldsOnUserEditForm() {} diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagForumTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagForumTest.php index 74d662302..1a2b9dae9 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagForumTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagForumTest.php @@ -56,7 +56,10 @@ class MetatagForumTest extends BrowserTestBase { $this->drupalLogin($this->adminUser); // Create content type. - $this->drupalCreateContentType(['type' => 'page', 'display_submitted' => FALSE]); + $this->drupalCreateContentType([ + 'type' => 'page', + 'display_submitted' => FALSE, + ]); $this->nodeId = $this->drupalCreateNode( [ 'title' => $this->randomMachineName(8), diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFrontpageTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFrontpageTest.php index a5339a60c..9f729f856 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFrontpageTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagFrontpageTest.php @@ -48,14 +48,19 @@ class MetatagFrontpageTest extends BrowserTestBase { $this->loginUser1(); // Create content type. - $this->drupalCreateContentType(['type' => 'page', 'display_submitted' => FALSE]); + $this->drupalCreateContentType([ + 'type' => 'page', + 'display_submitted' => FALSE, + ]); $this->nodeId = $this->drupalCreateNode( [ 'title' => $this->randomMachineName(8), 'promote' => 1, ])->id(); - $this->config('system.site')->set('page.front', '/node/' . $this->nodeId)->save(); + $this->config('system.site') + ->set('page.front', '/node/' . $this->nodeId) + ->save(); } /** diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagStringTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagStringTest.php index af65b2cb8..d1471a289 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagStringTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagStringTest.php @@ -63,7 +63,10 @@ class MetatagStringTest extends BrowserTestBase { $this->adminUser = $this->drupalCreateUser($this->permissions); $this->drupalLogin($this->adminUser); - $this->drupalCreateContentType(['type' => 'page', 'display_submitted' => FALSE]); + $this->drupalCreateContentType([ + 'type' => 'page', + 'display_submitted' => FALSE, + ]); // Add a Metatag field to the content type. $this->drupalGet('admin/structure/types'); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagTypesTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagTypesTest.php index ae0c4ff71..2d11b2df1 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagTypesTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagTypesTest.php @@ -92,7 +92,8 @@ class MetatagTagTypesTest extends BrowserTestBase { ]; $this->submitForm($edit, $this->t('Save and continue')); $this->submitForm([], $this->t('Save field settings')); - $this->container->get('entity_field.manager')->clearCachedFieldDefinitions(); + $this->container->get('entity_field.manager') + ->clearCachedFieldDefinitions(); } /** @@ -173,7 +174,7 @@ class MetatagTagTypesTest extends BrowserTestBase { * @todo Finish. */ public function todoTestUrl() { - // {@code} + // @code // $save_label = (floatval(\Drupal::VERSION) <= 8.3) ? $this->t('Save and publish') : $this->t('Save'); // // Tests meta tags with URLs work. // $this->drupalGet($this->entity_add_path); @@ -194,7 +195,7 @@ class MetatagTagTypesTest extends BrowserTestBase { // $elements = $this->cssSelect("meta[name='original-source']"); // $this->assertTrue(count($elements) === 1, 'Found original source metatag from defaults'); // $this->assertEquals($edit['field_metatag[0][advanced][original_source]'], (string) $elements[0]['content']); - // {@endcode} + // @endcode } } diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTest.php index 09b31628d..25f1aafcf 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTest.php @@ -14,6 +14,7 @@ class MetatagTagsTest extends MetatagTagsTestBase { */ protected $tags = [ 'abstract', + 'author', 'cache_control', 'canonical_url', 'content_language', @@ -84,20 +85,6 @@ class MetatagTagsTest extends MetatagTagsTestBase { return "//textarea[@name='abstract']"; } - /** - * Implements {tag_name}TestNameAttribute() for 'author'. - */ - protected function authorTestOutputXpath() { - return "//link[@rel='author']"; - } - - /** - * Implements {tag_name}TestValueAttribute() for 'author'. - */ - protected function authorTestValueAttribute() { - return 'href'; - } - /** * Implements {tag_name}TestNameAttribute() for 'canonical_url'. */ diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTestBase.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTestBase.php index 289611b5d..b2a57d6eb 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTestBase.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTagsTestBase.php @@ -195,9 +195,9 @@ abstract class MetatagTagsTestBase extends BrowserTestBase { // Look for a custom method named "{$tag_name}TestOutputXpath", if // found use that method to get the xpath definition for this meta tag, // otherwise it defaults to just looking for a meta tag matching: - // {@code} + // @code // <$testTag $testNameAttribute=$tag_name $testValueAttribute=$value /> - // {@endcode} + // @endcode $method = $this->getMethodFromTagCallback($tag_name, 'TestOutputXpath'); if (method_exists($this, $method)) { $xpath_string = $this->$method(); @@ -253,7 +253,11 @@ abstract class MetatagTagsTestBase extends BrowserTestBase { // Extract the meta tag from the HTML. $xpath = $this->xpath($xpath_string); - $this->assertCount(1, $xpath, new FormattableMarkup('One @tag tag found using @xpath.', ['@tag' => $tag_name, '@xpath' => $xpath_string])); + $message = new FormattableMarkup('One @tag tag found using @xpath.', [ + '@tag' => $tag_name, + '@xpath' => $xpath_string, + ]); + $this->assertCount(1, $xpath, $message); if (count($xpath) !== 1) { $this->verbose($xpath, $tag_name . ': ' . $xpath_string); } @@ -278,7 +282,11 @@ abstract class MetatagTagsTestBase extends BrowserTestBase { else { $this->verbose($xpath, $tag_name . ': ' . $xpath_string); $this->assertTrue((string) $xpath[0]); - $this->assertEquals((string) $xpath[0], $all_values[$tag_name], new FormattableMarkup("The '@tag' tag was found with the expected value '@value'.", ['@tag' => $tag_name, '@value' => $all_values[$tag_name]])); + $message = new FormattableMarkup("The '@tag' tag was found with the expected value '@value'.", [ + '@tag' => $tag_name, + '@value' => $all_values[$tag_name], + ]); + $this->assertEquals((string) $xpath[0], $all_values[$tag_name], $message); } } diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenStatus.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenStatus.php index 892a8ec23..0b2ea8ab7 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenStatus.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenStatus.php @@ -10,8 +10,7 @@ use Drupal\Tests\BrowserTestBase; * * @group metatag */ -class MetatagTokenStatus extends BrowserTestBase -{ +class MetatagTokenStatus extends BrowserTestBase { /** * {@inheritdoc} @@ -28,8 +27,7 @@ class MetatagTokenStatus extends BrowserTestBase * * @see token_get_token_problems */ - function testStatusReportTypesWarning() - { + public function testStatusReportTypesWarning() { $this->drupalLogin($this->rootUser); $this->drupalGet(Url::fromRoute('system.status')); @@ -41,8 +39,7 @@ class MetatagTokenStatus extends BrowserTestBase * * @see token_get_token_problems */ - function testStatusReportTokensWarning() - { + public function testStatusReportTokensWarning() { $this->drupalLogin($this->rootUser); $this->drupalGet(Url::fromRoute('system.status')); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenTest.php index 85af494fe..112a8e92a 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagTokenTest.php @@ -27,6 +27,7 @@ class MetatagTokenTest extends BrowserTestBase { 'token_module_test', 'metatag', 'metatag_open_graph', + 'metatag_favicons', ]; /** @@ -95,6 +96,7 @@ class MetatagTokenTest extends BrowserTestBase { 'field_metatags[0][basic][abstract]' => 'My abstract', 'field_metatags[0][open_graph][og_title]' => 'My OG Title', 'field_metatags[0][open_graph][og_image]' => 'Image 1,Image 2', + 'field_metatags[0][favicons][mask_icon][href]' => 'mask_icon.svg', ], 'Save'); $tokens = [ @@ -110,6 +112,8 @@ class MetatagTokenTest extends BrowserTestBase { '[user:field_metatags:og_image]' => 'Image 1,Image 2', '[user:field_metatags:og_image:0]' => 'Image 1', '[user:field_metatags:og_image:1]' => 'Image 2', + // Test metatags that store value as an array. + '[user:field_metatags:mask_icon]' => 'mask_icon.svg', ]; $this->assertPageTokens($user->toUrl(), $tokens, ['user' => $user]); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagXssTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagXssTest.php index e50990c17..588d8f8c0 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagXssTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Functional/MetatagXssTest.php @@ -101,7 +101,10 @@ class MetatagXssTest extends BrowserTestBase { $this->drupalLogin($this->adminUser); // Set up a content type. - $this->drupalCreateContentType(['type' => 'metatag_node', 'name' => 'Test Content Type']); + $this->drupalCreateContentType([ + 'type' => 'metatag_node', + 'name' => 'Test Content Type', + ]); // Add a metatag field to the content type. $this->drupalGet('admin/structure/types/manage/metatag_node/fields/add-field'); @@ -215,9 +218,9 @@ class MetatagXssTest extends BrowserTestBase { $this->submitForm($edit, 'Save'); // Check the body text. - // {@code} + // @code // $this->assertNoTitle($this->xssTitleString); - // {@endcode} + // @endcode $session->responseNotContains($this->xssTitleString); } diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php index 0b27bd120..2f2da019b 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/FunctionalJavascript/MetatagAvailableTokensTest.php @@ -25,7 +25,7 @@ class MetatagAvailableTokensTest extends WebDriverTestBase { /** * Test the node metatag defaults page. */ - function testNodeMetatagDefaultsPage() { + public function testNodeMetatagDefaultsPage() { $this->drupalLogin($this->rootUser); $this->drupalGet(Url::fromRoute('entity.metatag_defaults.edit_form', ['metatag_defaults' => 'node'])); $page = $this->getSession()->getPage(); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/MetatagManagerTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/MetatagManagerTest.php index 9d0dffe25..413df26f8 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/MetatagManagerTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/MetatagManagerTest.php @@ -52,7 +52,14 @@ class MetatagManagerTest extends KernelTestBase { $this->entityTypeManager = $this->container->get('entity_type.manager'); $this->metatagManager = $this->container->get('metatag.manager'); - $this->installConfig(['system', 'field', 'text', 'user', 'metatag', 'metatag_open_graph']); + $this->installConfig([ + 'system', + 'field', + 'text', + 'user', + 'metatag', + 'metatag_open_graph', + ]); $this->installEntitySchema('user'); $this->installSchema('user', ['users_data']); } diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php index 5e5b491f0..b19d186d6 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Migrate/d7/MetatagEntitiesTest.php @@ -123,8 +123,8 @@ class MetatagEntitiesTest extends MigrateDrupal7TestBase { // This should have the "current revision" keywords value, indicating it is // the current revision. $expected = [ - 'keywords' => 'current revision', 'canonical_url' => 'the-node', + 'keywords' => 'current revision', 'robots' => 'noindex, nofollow', ]; $this->assertSame(serialize($expected), $node->field_metatag->value); @@ -135,8 +135,8 @@ class MetatagEntitiesTest extends MigrateDrupal7TestBase { // This should have the "old revision" keywords value, indicating it is // a non-current revision. $expected = [ - 'keywords' => 'old revision', 'canonical_url' => 'the-node', + 'keywords' => 'old revision', 'robots' => 'noindex, nofollow', ]; $this->assertSame(serialize($expected), $node->field_metatag->value); @@ -147,9 +147,9 @@ class MetatagEntitiesTest extends MigrateDrupal7TestBase { $this->assertTrue($user->hasField('field_metatag')); // This should have the Utf8 converted description value. $expected = [ - 'keywords' => 'a user', 'canonical_url' => 'the-user', 'description' => 'Drupalâ„¢ user', + 'keywords' => 'a user', ]; $this->assertSame(serialize($expected), $user->field_metatag->value); @@ -158,8 +158,8 @@ class MetatagEntitiesTest extends MigrateDrupal7TestBase { $this->assertInstanceOf(TermInterface::class, $term); $this->assertTrue($term->hasField('field_metatag')); $expected = [ - 'keywords' => 'a taxonomy', 'canonical_url' => 'the-term', + 'keywords' => 'a taxonomy', ]; $this->assertSame(serialize($expected), $term->field_metatag->value); } diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d6/NodewordsFieldInstanceTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d6/NodewordsFieldInstanceTest.php index 553216fda..db89401ec 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d6/NodewordsFieldInstanceTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d6/NodewordsFieldInstanceTest.php @@ -39,7 +39,10 @@ class NodewordsFieldInstanceTest extends MigrateSqlSourceTestBase { 'metatag', ]; - public function setUp() { + /** + * {@inheritdoc} + */ + public function setUp(): void { parent::setUp(); $this->installEntitySchema('node'); $this->installEntitySchema('taxonomy_term'); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php index df9be4c1a..7f00292c1 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Kernel/Plugin/migrate/source/d7/MetatagFieldInstanceTest.php @@ -36,7 +36,10 @@ class MetatagFieldInstanceTest extends MigrateSqlSourceTestBase { 'metatag', ]; - public function setUp() { + /** + * {@inheritdoc} + */ + public function setUp(): void { parent::setUp(); $this->installEntitySchema('node'); $this->installEntitySchema('taxonomy_term'); @@ -54,8 +57,10 @@ class MetatagFieldInstanceTest extends MigrateSqlSourceTestBase { ]); $node_type->save(); } + // @code // ['taxonomy_term', ['test_vocabulary' => 'test_vocabulary']], // Vocabulary::create(['name' => 'test_vocabulary']); + // @endcode // Setup vocabulary. Vocabulary::create([ 'vid' => 'test_vocabulary', @@ -63,7 +68,7 @@ class MetatagFieldInstanceTest extends MigrateSqlSourceTestBase { ])->save(); // Create a term and a comment. - $term = Term::create([ + Term::create([ 'vid' => 'test_vocabulary', 'name' => 'term', ])->save(); diff --git a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Unit/MetatagTrimmerTest.php b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Unit/MetatagTrimmerTest.php index 98ef3b2d4..ec56551d2 100644 --- a/frontend/drupal9/web/modules/contrib/metatag/tests/src/Unit/MetatagTrimmerTest.php +++ b/frontend/drupal9/web/modules/contrib/metatag/tests/src/Unit/MetatagTrimmerTest.php @@ -40,6 +40,8 @@ class MetatagTrimmerTest extends UnitTestCase { $this->assertEquals('Test', $trimResult3); $trimResult4 = $this->metatagTrimmer->trimBeforeValue('Test 123 123', 10); $this->assertEquals('Test 123', $trimResult4); + $trimResult5 = $this->metatagTrimmer->trimBeforeValue('Test 123 123', 20); + $this->assertEquals('Test 123 123', $trimResult5); } /** @@ -54,6 +56,8 @@ class MetatagTrimmerTest extends UnitTestCase { $this->assertEquals($trimResult3, 'Test'); $trimResult4 = $this->metatagTrimmer->trimAfterValue('Test 123 123', 10); $this->assertEquals('Test 123 123', $trimResult4); + $trimResult5 = $this->metatagTrimmer->trimAfterValue('Test 123 123', 20); + $this->assertEquals('Test 123 123', $trimResult5); } /**