SU-SWS/stanford_fields

View on GitHub
src/Service/FieldCache.php

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
<?php

namespace Drupal\stanford_fields\Service;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;

/**
 * Cache invalidator based on field values.
 *
 * @package Drupal\stanford_fields\Service
 */
class FieldCache implements FieldCacheInterface {

  /**
   * Drupal core field manager service.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $fieldManager;

  /**
   * Drupal core entity manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Drupal core state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * FieldCron constructor.
   *
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager
   *   Drupal core field manager service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Drupal core entity manager service.
   * @param \Drupal\Core\State\StateInterface $state
   *   Drupal core state service.
   */
  public function __construct(EntityFieldManagerInterface $field_manager, EntityTypeManagerInterface $entity_type_manager, StateInterface $state) {
    $this->fieldManager = $field_manager;
    $this->entityTypeManager = $entity_type_manager;
    $this->state = $state;
  }

  /**
   * {@inheritDoc}
   */
  public function invalidateDateFieldsCache() {
    $date_field_types = ['datetime', 'daterange', 'smartdate'];
    $cache_tags = [];

    // Loop through all fields to invalidate entities on date fields.
    foreach ($this->fieldManager->getFieldMap() as $entity_type => $fields) {
      foreach ($fields as $field_name => $field_info) {

        // Only invalidate desired date fields.
        if (in_array($field_info['type'], $date_field_types)) {
          $tags = $this->getExpiredDateCacheTags($entity_type, $field_name, $field_info['bundles']);
          $cache_tags = array_merge($cache_tags, $tags);
        }
      }
    }

    Cache::invalidateTags(array_unique($cache_tags));
    $this->state->set('stanford_fields.dates_cleared', time() - 5);
  }

  /**
   * Use an entity query to check for date fields that have recently passed.
   *
   * @param string $entity_type
   *   Entity type machine name.
   * @param string $field_name
   *   Field definition name.
   * @param array $bundles
   *   Array of entity bundle names.
   *
   * @return int[]
   *   Keyed array of entity ids.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getExpiredDateCacheTags($entity_type, $field_name, array $bundles = []) {
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
    $bundle_key = $this->entityTypeManager->getDefinition($entity_type)
      ->getKey('bundle');

    $field_storage = $this->entityTypeManager->getStorage('field_storage_config');
    $now = new DrupalDateTime();
    $last_ran = DrupalDateTime::createFromTimestamp($this->state->get('stanford_fields.dates_cleared', 0));

    /** @var \Drupal\field\FieldStorageConfigInterface $field_definition */
    $field_definition = $field_storage->load("$entity_type.$field_name");
    $field_date_format = $field_definition->getSetting('datetime_type') == 'date' ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;

    // Smartdate fields are formatted differently.
    if ($field_definition->getType() == 'smartdate') {
      $field_date_format = 'U';
    }

    // Query all entities for the given date field that is between the last ran
    // time and the current time.
    $start_query = $entity_storage->getQuery()
      ->accessCheck(FALSE)
      ->exists($field_name);
    $end_query = clone $start_query;
    $condition_group = $start_query->andConditionGroup()
      ->condition($field_name, $now->format($field_date_format), '<=')
      ->condition($field_name, $last_ran->format($field_date_format), '>=');
    $start_query->condition($condition_group);

    // Some entity types don't have bundles, so don't add the condition if not
    // applicable.
    if ($bundle_key) {
      $start_query->condition($bundle_key, $bundles, 'IN');
      $end_query->condition($bundle_key, $bundles, 'IN');
    }

    $field_properties = $field_definition->getPropertyNames();
    // If the field type has an end value, modify the end query's conditions to
    // check for those values.
    if (in_array('end_value', $field_properties)) {
      $condition_group = $end_query->andConditionGroup()
        ->condition("$field_name.end_value", $now->format($field_date_format), '<=')
        ->condition("$field_name.end_value", $last_ran->format($field_date_format), '>=');
    }
    $end_query->condition($condition_group);

    // Merge the start query ids with the end query ids.
    $entity_ids = array_merge($start_query->execute(), $end_query->execute());

    $tags = [];
    // If no entity ids were found, no tags should be invalidated.
    if (!empty($entity_ids)) {
      $entities = $entity_storage->loadMultiple($entity_ids);
      foreach ($entities as $entity) {
        $tags = array_merge($tags, $entity->getCacheTagsToInvalidate());
      }
    }
    return $tags;
  }

}