From d0b204659dd1cbef4fac4599efcdd16bd92ecfaa Mon Sep 17 00:00:00 2001 From: Thomas Fleming Date: Mon, 6 Apr 2026 17:21:52 -0400 Subject: [PATCH 1/2] Added cache tags to mapped org locations and contact us addresses, added tests --- .../src/ExistingSite/CacheTagCoverageTest.php | 123 ++++++++++++++++++ .../modules/custom/mayflower/mayflower.module | 7 +- .../modules/custom/mayflower/src/Helper.php | 3 +- .../mayflower/src/Prepare/Organisms.php | 20 ++- .../themes/custom/mass_theme/mass_theme.theme | 16 ++- 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 docroot/modules/custom/mass_caching/tests/src/ExistingSite/CacheTagCoverageTest.php diff --git a/docroot/modules/custom/mass_caching/tests/src/ExistingSite/CacheTagCoverageTest.php b/docroot/modules/custom/mass_caching/tests/src/ExistingSite/CacheTagCoverageTest.php new file mode 100644 index 0000000000..be58eb3cd6 --- /dev/null +++ b/docroot/modules/custom/mass_caching/tests/src/ExistingSite/CacheTagCoverageTest.php @@ -0,0 +1,123 @@ +requestDebugCachabilityHeaders($this->getSession()); + } + + /** + * Org location maps should bubble cache tags for referenced locations. + */ + public function testOrgLocationsBubbleReferencedLocationTags(): void { + $location = $this->createNode([ + 'type' => 'location', + 'title' => 'Cache Tag Test Location', + 'moderation_state' => 'published', + ]); + + $org_locations = Paragraph::create([ + 'type' => 'org_locations', + 'field_org_ref_locations' => [$location], + ]); + $org_locations->save(); + + $section = Paragraph::create([ + 'type' => 'org_section_long_form', + 'field_section_long_form_content' => [$org_locations], + ]); + $section->save(); + + $org_page = $this->createNode([ + 'type' => 'org_page', + 'title' => 'Cache Tag Test Org', + 'field_organization_sections' => [$section], + 'moderation_state' => 'published', + ]); + + $this->drupalGet($org_page->toUrl()->toString()); + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'node:' . $location->id()); + } + + /** + * Event headers should bubble cache tags for unique-address paragraphs. + */ + public function testEventUniqueAddressBubblesParagraphTags(): void { + $parent = $this->createNode([ + 'type' => 'org_page', + 'title' => 'Cache Tag Event Parent', + 'moderation_state' => 'published', + ]); + + $address = Paragraph::create([ + 'type' => 'address', + 'field_label' => 'Unique Event Address', + 'field_address_address' => [ + 'address_line1' => '1 Test Plaza', + 'locality' => 'Boston', + 'administrative_area' => 'MA', + 'postal_code' => '02108', + 'country_code' => 'US', + ], + ]); + $address->save(); + + $event = $this->createNode([ + 'type' => 'event', + 'title' => 'Cache Tag Test Event', + 'field_event_address_type' => 'unique', + 'field_event_ref_unique_address' => [$address], + 'field_event_date' => [ + 'value' => '2030-12-31T05:00:00', + 'end_value' => '2031-01-01T05:00:00', + ], + 'field_event_time' => '6AM - 5PM', + 'field_event_ref_parents' => [$parent], + 'field_organizations' => [$parent], + 'moderation_state' => 'published', + ]); + + $this->drupalGet($event->toUrl()->toString()); + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'paragraph:' . $address->id()); + } + + /** + * Related link lists should bubble tags for linked internal nodes. + */ + public function testRelatedLinkListBubblesLinkedNodeTags(): void { + $related = $this->createNode([ + 'type' => 'org_page', + 'title' => 'Cache Tag Related Org', + 'moderation_state' => 'published', + ]); + + $info_details = $this->createNode([ + 'type' => 'info_details', + 'title' => 'Cache Tag Info Details', + 'field_info_details_related' => [ + [ + 'uri' => 'entity:node/' . $related->id(), + 'title' => $related->label(), + ], + ], + 'moderation_state' => 'published', + ]); + + $this->drupalGet($info_details->toUrl()->toString()); + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'node:' . $related->id()); + } + +} diff --git a/docroot/modules/custom/mayflower/mayflower.module b/docroot/modules/custom/mayflower/mayflower.module index 78bbad674a..813cdbe03c 100644 --- a/docroot/modules/custom/mayflower/mayflower.module +++ b/docroot/modules/custom/mayflower/mayflower.module @@ -9,6 +9,7 @@ use Composer\InstalledVersions as ComposerInstalledVersions; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\block\Entity\Block; +use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\StringTranslation\ByteSizeMarkup; @@ -629,6 +630,7 @@ function mayflower_preprocess_paragraph__social_media(&$variables) { function mayflower_preprocess_paragraph__org_locations(&$variables) { $paragraph = $variables['paragraph']; $node = Helper::getParentNode($paragraph); + $cache_tags = []; if (Helper::isFieldPopulated($paragraph, 'field_org_ref_locations')) { $mappedLocations_options = [ @@ -652,13 +654,16 @@ function mayflower_preprocess_paragraph__org_locations(&$variables) { [ 'path' => '@organisms/by-author/mapped-locations.twig', 'data' => [ - 'mappedLocations' => Organisms::prepareMappedLocations($paragraph->field_org_ref_locations->referencedEntities(), $mappedLocations_options), + 'mappedLocations' => Organisms::prepareMappedLocations($paragraph->field_org_ref_locations->referencedEntities(), $mappedLocations_options, $cache_tags), ], ], ], ]; $variables['stackedRowSections'] = $sections; + if (!empty($cache_tags)) { + $variables['#cache']['tags'] = Cache::mergeTags($variables['#cache']['tags'] ?? [], $cache_tags); + } } } diff --git a/docroot/modules/custom/mayflower/src/Helper.php b/docroot/modules/custom/mayflower/src/Helper.php index 82b3978ed3..edb8513d8a 100644 --- a/docroot/modules/custom/mayflower/src/Helper.php +++ b/docroot/modules/custom/mayflower/src/Helper.php @@ -1148,12 +1148,13 @@ public static function buildPageHeaderOptionalContentsContactUs($entity, $field, * ], * ], ... ] */ - public static function buildPageHeaderOptionalContentsContactUsAddress($entity, $field, array $options = []) { + public static function buildPageHeaderOptionalContentsContactUsAddress($entity, $field, array $options = [], array &$cache_tags = []) { $optionalContentsContactUs = []; $contactUs = []; $contact_items = Helper::getReferencedEntitiesFromField($entity, $field); if (!empty($contact_items)) { foreach ($contact_items as $contact_item) { + $cache_tags = array_merge($cache_tags, $contact_item->getCacheTags()); $contactUs = Molecules::prepareAddress($contact_item, $options); } diff --git a/docroot/modules/custom/mayflower/src/Prepare/Organisms.php b/docroot/modules/custom/mayflower/src/Prepare/Organisms.php index acb9655e33..523be3223f 100755 --- a/docroot/modules/custom/mayflower/src/Prepare/Organisms.php +++ b/docroot/modules/custom/mayflower/src/Prepare/Organisms.php @@ -350,10 +350,24 @@ public static function prepareEventListing($entity, $field = '', array $options * ],... ] * ] */ - public static function prepareLinkList($entity, $field, array $options = []) { + public static function prepareLinkList($entity, $field, array $options = [], array &$cache_tags = []) { $linkList = []; + if (Helper::isEntityReferenceField($entity, $field)) { + foreach (Helper::getReferencedEntitiesFromField($entity, $field) as $referenced_entity) { + $cache_tags = array_merge($cache_tags, $referenced_entity->getCacheTags()); + } + } + else { + foreach ($entity->get($field) as $link) { + $linked_entity = Helper::entityFromUrl($link->getUrl()); + if ($linked_entity instanceof ContentEntityInterface) { + $cache_tags = array_merge($cache_tags, $linked_entity->getCacheTags()); + } + } + } + // Build description, if option is set. if (isset($options['description'])) { $description = [ @@ -995,6 +1009,7 @@ public static function prepareMappedLocations(array $locations, array $options, $contact_ids = []; foreach ($locations as $location) { + $cache_tags = array_merge($cache_tags, $location->getCacheTags()); foreach ($location->field_ref_contact_info_1 as $contactRef) { $contactId = $contactRef->target_id; $contact_ids[] = $contactId; @@ -1003,6 +1018,9 @@ public static function prepareMappedLocations(array $locations, array $options, } // Batch load contact entities all at once. $contact_entities = Node::loadMultiple($contact_ids); + foreach ($contact_entities as $contact_entity) { + $cache_tags = array_merge($cache_tags, $contact_entity->getCacheTags()); + } // Override the link to the map of locations if there is only a single // location listed and instead link directly to that single location's page. diff --git a/docroot/themes/custom/mass_theme/mass_theme.theme b/docroot/themes/custom/mass_theme/mass_theme.theme index 537208daf4..8c50c76bbe 100644 --- a/docroot/themes/custom/mass_theme/mass_theme.theme +++ b/docroot/themes/custom/mass_theme/mass_theme.theme @@ -1467,7 +1467,7 @@ function mass_theme_preprocess_node_topic_page(&$variables) { ], ]; - $variables['linkList'] = Organisms::prepareLinkList($node, 'field_topic_ref_related_topics', $linkList_options); + $variables['linkList'] = Organisms::prepareLinkList($node, 'field_topic_ref_related_topics', $linkList_options, $cache_tags); if (!empty($cache_tags)) { // Merge arrays of existing and new cache tags and removes duplicates. @@ -1509,7 +1509,7 @@ function mass_theme_preprocess_node_binder(&$variables) { ], 'stacked' => TRUE, ]; - $sideContent['linkList'] = Organisms::prepareLinkList($node, 'field_binder_related', $linkList_options); + $sideContent['linkList'] = Organisms::prepareLinkList($node, 'field_binder_related', $linkList_options, $cache_tags); $variables['sideContent'] = $sideContent; if (!empty($cache_tags)) { @@ -1573,7 +1573,7 @@ function mass_theme_preprocess_node_info_details(&$variables) { ], 'stacked' => TRUE, ]; - $sideContent['linkList'] = Organisms::prepareLinkList($node, 'field_info_details_related', $linkList_options); + $sideContent['linkList'] = Organisms::prepareLinkList($node, 'field_info_details_related', $linkList_options, $cache_tags); $variables['sideContent'] = $sideContent; } @@ -2989,7 +2989,7 @@ function mass_theme_preprocess_node_event(&$variables) { $optionalContents = array_merge($optionalContents, Helper::buildPageHeaderOptionalContentsContactUs($node, 'field_event_ref_contact', $headerContact_options, $cache_tags)); } if (Helper::isFieldPopulated($node, 'field_event_ref_unique_address') && ($address_type == 'unique')) { - $optionalContents = array_merge($optionalContents, Helper::buildPageHeaderOptionalContentsContactUsAddress($node, 'field_event_ref_unique_address')); + $optionalContents = array_merge($optionalContents, Helper::buildPageHeaderOptionalContentsContactUsAddress($node, 'field_event_ref_unique_address', [], $cache_tags)); } // Build call out time. @@ -3327,7 +3327,7 @@ function mass_theme_preprocess_node_service_page(&$variables) { $split_columns['columns'][]['items'][] = [ 'path' => '@organisms/by-author/link-list.twig', 'data' => [ - 'linkList' => Organisms::prepareLinkList($node, 'field_service_ref_services_6', $linkList_options), + 'linkList' => Organisms::prepareLinkList($node, 'field_service_ref_services_6', $linkList_options, $cache_tags), ], ]; } @@ -5456,6 +5456,7 @@ function mass_theme_preprocess_paragraph__related_content(&$variables) { */ function mass_theme_preprocess_paragraph__org_related_orgs(&$variables) { $paragraph = $variables['paragraph']; + $cache_tags = []; // Add related orgs stack row section. if (Helper::isFieldPopulated($paragraph, 'field_ref_orgs')) { @@ -5468,12 +5469,15 @@ function mass_theme_preprocess_paragraph__org_related_orgs(&$variables) { [ 'path' => '@organisms/by-author/link-list.twig', 'data' => [ - 'linkList' => Organisms::prepareLinkList($paragraph, 'field_ref_orgs', $linkList_options), + 'linkList' => Organisms::prepareLinkList($paragraph, 'field_ref_orgs', $linkList_options, $cache_tags), ], ], ], ]; $variables['stackedRowSections'] = $sections; + if (!empty($cache_tags)) { + $variables['#cache']['tags'] = Cache::mergeTags($variables['#cache']['tags'] ?? [], $cache_tags); + } } } From 51ef34dd0693fde0a58144877c89b34c55e9e8ba Mon Sep 17 00:00:00 2001 From: Thomas Fleming Date: Wed, 8 Apr 2026 13:07:36 -0400 Subject: [PATCH 2/2] Added changelog --- changelogs/DP-45669.yml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 changelogs/DP-45669.yml diff --git a/changelogs/DP-45669.yml b/changelogs/DP-45669.yml new file mode 100644 index 0000000000..c192d1d69c --- /dev/null +++ b/changelogs/DP-45669.yml @@ -0,0 +1,41 @@ +# +# Write your changelog entry here. Every pull request must have a changelog yml file. +# +# Change types: +# ############################################################################# +# You can use one of the following types: +# - Added: For new features. +# - Changed: For changes to existing functionality. +# - Deprecated: For soon-to-be removed features. +# - Removed: For removed features. +# - Fixed: For any bug fixes. +# - Security: In case of vulnerabilities. +# +# Format +# ############################################################################# +# The format is crucial. Please follow the examples below. For reference, the requirements are: +# - All 3 parts are required and you must include "Type", "description" and "issue". +# - "Type" must be left aligned and followed by a colon. +# - "description" must be indented with 2 spaces followed by a colon +# - "issue" must be indented with 4 spaces followed by a colon. +# - "issue" is for the Jira ticket number only e.g. DP-1234 +# - No extra spaces, indents, or blank lines are allowed. +# +# Example: +# ############################################################################# +# Fixed: +# - description: Fixes scrolling on edit pages in Safari. +# issue: DP-13314 +# +# You may add more than 1 description & issue for each type using the following format: +# Changed: +# - description: Automating the release branch. +# issue: DP-10166 +# - description: Second change item that needs a description. +# issue: DP-19875 +# - description: Third change item that needs a description along with an issue. +# issue: DP-19843 +# +Fixed: + - description: Fixed missing cache tags from org locations + issue: DP-45669