From b6be07acde3e70d73e02f8f0e2390882923b651b Mon Sep 17 00:00:00 2001
From: petar_basic <petar_basic@3626336.no-reply.drupal.org>
Date: Thu, 21 Jan 2021 15:06:22 +0100
Subject: [PATCH] Issue #3185895 by petar_basic, fago: Add support for via
 parent invalidation strategy

---
 composer.json                    |  4 ++-
 src/Service/CacheInvalidator.php | 61 ++++++++++++++++++++++++++++----
 2 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/composer.json b/composer.json
index fb50539..6638e88 100644
--- a/composer.json
+++ b/composer.json
@@ -4,5 +4,7 @@
   "type": "drupal-module",
   "license": "GPL-2.0+",
   "minimum-stability": "dev",
-  "require": { }
+  "require": {
+    "drunomics/service-utils": "*",
+  }
 }
diff --git a/src/Service/CacheInvalidator.php b/src/Service/CacheInvalidator.php
index f524067..728bdea 100644
--- a/src/Service/CacheInvalidator.php
+++ b/src/Service/CacheInvalidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\cache_tools\Service;
 
+use drunomics\ServiceUtils\Core\Entity\EntityTypeManagerTrait;
 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
 use Drupal\Core\Entity\EntityPublishedInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -15,6 +16,8 @@ use Drupal\Core\Entity\FieldableEntityInterface;
  */
 class CacheInvalidator {
 
+  use EntityTypeManagerTrait;
+
   /**
    * The cache tag invalidator.
    *
@@ -137,17 +140,18 @@ class CacheInvalidator {
     $tag_prefix = $this->getPublishedEntityCacheTag($entity) . ':';
     foreach ($this->settings['invalidate'][$entity_type] as $cache_parameter) {
       $parts = explode(':', $cache_parameter);
-      if (count($parts) != 2 || $parts[0] != $bundle) {
+      if (count($parts) < 2 || $parts[0] != $bundle) {
         // This setting is not for the current bundle or not field-based.
         continue;
       }
       $field_name = $parts[1];
+      $invalidate_term_parents = isset($parts[2]) && $parts[2] === 'parents' ? TRUE : FALSE;
       if ($entity->hasField($field_name)) {
+        $field_definition = $entity->getFieldDefinition($field_name);
+        $settings = $field_definition->getSettings();
+        $target_type = empty($settings['target_type']) ? '' : $settings['target_type'];
         // The name of the value property, e.g. 'value' or 'target_id'.
-        $key = $entity
-          ->getFieldDefinition($field_name)
-          ->getFieldStorageDefinition()
-          ->getMainPropertyName();
+        $key = $field_definition->getFieldStorageDefinition()->getMainPropertyName();
         if (is_null($key)) {
           // The field has no main value property.
           continue;
@@ -162,7 +166,7 @@ class CacheInvalidator {
             // Add tag for the original field value.
             /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $field_items */
             foreach ($entity_compare->get($field_name)->getValue() as $value) {
-              $tags[] = $tag_prefix_field . $value[$key];
+              $tags = array_merge($tags, $this->generateTagsBasedOnInvalidationStrategy($target_type, $value[$key], $tag_prefix_field, $invalidate_term_parents));
             }
           }
         }
@@ -170,7 +174,7 @@ class CacheInvalidator {
           // Add tag for the new field value.
           /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $field_items */
           foreach ($entity->get($field_name)->getValue() as $value) {
-            $tags[] = $tag_prefix_field . $value[$key];
+            $tags = array_merge($tags, $this->generateTagsBasedOnInvalidationStrategy($target_type, $value[$key], $tag_prefix_field, $invalidate_term_parents));
           }
         }
       }
@@ -179,6 +183,49 @@ class CacheInvalidator {
     return $tags;
   }
 
+  /**
+   * Returns parent tids for a given tid, or $tid if no parent exists.
+   *
+   * @param int $tid
+   *   A taxonomy term id.
+   *
+   * @return array
+   *   Returns $tid if no parents are found, else parents tids.
+   */
+  public function taxonomyGetParents($tid) {
+    $ancestors = $this->getEntityTypeManager()->getStorage('taxonomy_term')->loadAllParents($tid);
+    return empty($ancestors) ? [$tid] : array_keys($ancestors);
+  }
+
+  /**
+   * Returns an array of cache tags to invalidate.
+   *
+   * @param string $target_type
+   *   Type of the referenced entity.
+   * @param mixed $entity_id
+   *   An entity id for which to generate tags.
+   * @param string $tag_prefix_field
+   *   Prefix for generated tags.
+   * @param bool $invalidate_term_parents
+   *  Flag to indicate invalidation strategy.
+   *  Only applicable for entities of type taxonomy_term.
+   *
+   * @return array
+   *   An array of cache tags to invalidate.
+   */
+  public function generateTagsBasedOnInvalidationStrategy($target_type, $entity_id, $tag_prefix_field, $invalidate_term_parents = FALSE) {
+    $tags = [];
+    if ($target_type === 'taxonomy_term' && $invalidate_term_parents) {
+      foreach ($this->taxonomyGetParents($entity_id) as $parentKey) {
+        $tags[] = $tag_prefix_field . $parentKey;
+      }
+    }
+    else {
+      $tags[] = $tag_prefix_field . $entity_id;
+    }
+    return $tags;
+  }
+
   /**
    * Invalidates published entity field-based cache tags.
    *
-- 
GitLab