mustache_templates-8.x-1.0-beta4/src/MustacheTokenIterate.php

src/MustacheTokenIterate.php
<?php

namespace Drupal\mustache;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\Utility\Token;
use Drupal\mustache\Render\IterableMarkup;

/**
 * Service for iterating through nested token data.
 */
class MustacheTokenIterate {

  /**
   * The token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * The token entity mapper.
   *
   * @var \Drupal\token\TokenEntityMapperInterface|null
   */
  protected $tokenEntityMapper;

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

  /**
   * The MustacheTokenIterate constructor.
   *
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(Token $token, EntityTypeManagerInterface $entity_type_manager) {
    $this->token = $token;
    $this->tokenEntityMapper = static::getTokenEntityMapper();
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Get the iterable target that is addressed by a token.
   *
   * @param string|array $token_name
   *   The token name without operation prefix (i.e. not prefixed with
   *   "iterate.<operation>.") that addresses the desired iterable target.
   *   Can also be passed as array if the token name already was split up
   *   into its parts.
   * @param array $data
   *   Provided token data.
   * @param array $options
   *   Provided token options.
   * @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
   *   The bubbleable metadata. This is passed to the token replacement
   *   implementations so that they can attach their metadata.
   * @param string|bool $check_access
   *   (Optional) The type of access check to perform on the addressed target.
   *   Set to FALSE if access check should be skipped.
   *
   * @return \Drupal\mustache\Render\IterableMarkup
   *   The iterable target, starting at root level.
   */
  public function getIterableTarget($token_name, array $data, array $options, BubbleableMetadata $bubbleable_metadata, $check_access = 'view') {
    $token_keys = is_string($token_name) ? explode(':', $token_name) : $token_name;
    $target = IterableMarkup::create();
    if (!empty($token_keys)) {
      $build = $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
      if ($build->exists()) {
        $build->parent = $target;
        $target[reset($token_keys)] = $build;
      }
    }
    return $target;
  }

  /**
   * Recusively builds up the iterable target data.
   *
   * @param array $data
   *   Provided token data.
   * @param array $options
   *   Provided token options.
   * @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
   *   The bubbleable metadata. This is passed to the token replacement
   *   implementations so that they can attach their metadata.
   * @param array $token_keys
   *   The token keys that address the desired target data.
   * @param string|bool $check_access
   *   (Optional) The type of access check to perform on the addressed target.
   *   Set to FALSE if access check should be skipped.
   *
   * @return \Drupal\mustache\Render\IterableMarkup
   *   The target data. May hold empty leaves if requested target was either
   *   not found, or if not an iterable data type, or if the current user has
   *   no permissions to access the data.
   */
  protected function buildRecursive(array $data, array $options, BubbleableMetadata $bubbleable_metadata, array $token_keys, $check_access = 'view') {
    $target = IterableMarkup::create();
    if (empty($data) && empty($token_keys)) {
      return $target;
    }

    $key = array_shift($token_keys);
    $property = reset($token_keys);

    if (!isset($data[$key])) {
      if (!empty($data)) {
        if (strpos($key, '_') !== FALSE) {
          $key_hyphened = str_replace('_', '-', $key);
          if (isset($data[$key_hyphened])) {
            array_unshift($token_keys, $key_hyphened);
            return $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
          }
        }
        elseif (strpos($key, '-') !== FALSE) {
          $key_underscore = str_replace('-', '_', $key);
          if (isset($data[$key_underscore])) {
            array_unshift($token_keys, $key_underscore);
            return $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
          }
        }
      }
      $data[$key] = NULL;
    }

    if (!isset($data['_token_keys'])) {
      $data['_token_keys'] = $token_keys;
    }

    $candidate = $data[$key];

    if ($candidate instanceof CacheableDependencyInterface) {
      $bubbleable_metadata->addCacheableDependency($candidate);
    }

    if ($check_access && $candidate instanceof AccessibleInterface) {
      /** @var \Drupal\Core\Access\AccessResultInterface $access_result */
      $access_result = $candidate->access($check_access, NULL, TRUE);
      $bubbleable_metadata->addCacheableDependency($access_result);
      if (!$access_result->isAllowed()) {
        return $target;
      }
    }

    if ($property === FALSE) {
      // Let us see whether a token replacement value exists for the given
      // target. If there is one, use it. If not, fall back to unprocessed
      // property values instead.
      $token_type = $key;
      $token_candidates = [];

      $entity = isset($data['_entity']) ? $data['_entity'] : NULL;
      if ($candidate instanceof TypedDataInterface) {
        $root = $candidate->getRoot()->getValue();
        if ($root instanceof CacheableDependencyInterface) {
          $bubbleable_metadata->addCacheableDependency($root);
        }
        if ($check_access && $root instanceof AccessibleInterface) {
          /** @var \Drupal\Core\Access\AccessResultInterface $access_result */
          $access_result = $root->access($check_access, NULL, TRUE);
          $bubbleable_metadata->addCacheableDependency($access_result);
          if (!$access_result->isAllowed()) {
            return $target;
          }
        }
        if ($root instanceof EntityInterface) {
          $entity = $root;
        }
        else {
          $token_type = $candidate->getRoot()->getDataDefinition()->getDataType();
          $data[$token_type] = $root;
        }
        // Use the data value itself as candidate.
        $data[$candidate->getDataDefinition()->getDataType()] = $candidate->getValue();
      }

      if ($entity) {
        $token_type = $this->getTokenTypeForEntity($entity);
        $data[$token_type] = $entity;
      }

      $token_candidate = '';
      if (isset($data['_field_name'])) {
        $token_candidate = implode(':', array_merge([$data['_field_name']], $data['_token_keys']));
      }
      elseif (!empty($data['_token_keys'])) {
        $token_candidate = implode(':', $data['_token_keys']);
      }
      if (!empty($token_candidate)) {
        $token_candidates[$token_candidate] = '[' . $token_type . ':' . $token_candidate . ']';
      }

      if ((count($token_candidates) === 2) && strlen(end($token_candidates)) > strlen(reset($token_candidates))) {
        // The token with the longer name should get first precedence.
        $token_candidates = array_reverse($token_candidates, TRUE);
      }

      if (!empty($token_candidates)) {
        $token_replacements = $this->token->generate($token_type, $token_candidates, $data, $options, $bubbleable_metadata);
        foreach ($token_candidates as $token) {
          if (isset($token_replacements[$token]) && (is_scalar($token_replacements[$token]) || (is_object($token_replacements[$token]) && method_exists($token_replacements[$token], '__toString')))) {
            $target->value = $token_replacements[$token];
            break;
          }
        }
      }

      if (empty($target->value)) {
        if ($candidate instanceof TypedDataInterface) {
          $property_path = $candidate->getPropertyPath();
          if (empty($property_path) && empty($data['_token_keys'])) {
            $target->value = $candidate->getString();
          }
          elseif (!empty($property_path)) {
            $parts = explode('.', $property_path);
            if ((!empty($data['_token_keys']) && $parts == $data['_token_keys']) || (isset($data['_field_name']) && $parts == array_merge([$data['_field_name']], $data['_token_keys']))) {
              $target->value = $candidate->getString();
            }
          }
        }
        elseif (empty($data['_token_keys']) && (is_scalar($candidate) || (is_object($candidate) && method_exists($candidate, '__toString')))) {
          $target->value = (string) $candidate;
        }
      }

      // Some iterations start on a higher level (for example, the first level
      // of a multidimensional array), and access would happen on subsets within
      // the scope of that iteration level. Therefore, load the children too.
      if (is_iterable($candidate) && empty($data['_halt_at_key_level'])) {
        foreach ($candidate as $c_key => $c_value) {
          $data = [
            '_entity' => $entity,
            $c_key => $c_value,
          ];
          if ($c_value instanceof TypedDataInterface) {
            $root = $c_value->getRoot()->getValue();
            $data['_entity'] = $root instanceof EntityInterface ? $root : $entity;
            $data['_token_keys'] = explode('.', $c_value->getPropertyPath());
          }
          $build = $this->buildRecursive($data, $options, $bubbleable_metadata, [$c_key], $check_access);
          if ($build->exists()) {
            $build->parent = $target;
            $target[$c_key] = $build;
          }
        }
      }

      return $target;
    }

    if ($candidate instanceof FieldableEntityInterface) {
      $data['_entity'] = $candidate;
      $data['_token_keys'] = $token_keys;
      // From here on, work with the entity wrapped as typed data object.
      $candidate = $candidate->getTypedData();
      // If any, unset the previously stored field name. Looking at an entity
      // at this point indicates that we either are at the beginning, or
      // we step down one level deeper, that "resets" our scope.
      unset($data['_field_name']);
    }
    if ($candidate instanceof FieldItemListInterface) {
      $data['_entity'] = $candidate->getEntity();
      $data['_field_name'] = $candidate->getFieldDefinition()->getName();
      $data['_token_keys'] = $token_keys;
    }
    if ($candidate instanceof FieldItemInterface) {
      $data['_entity'] = $candidate->getEntity();
      $data['_field_name'] = $candidate->getFieldDefinition()->getName();
      $data['_token_keys'] = array_merge([$key], $token_keys);
    }
    if ($candidate instanceof ComplexDataInterface) {
      $properties = $candidate->getProperties(TRUE);
      if (isset($properties[$property])) {
        unset($data[$key]);
        $data[$property] = $candidate->get($property);
        $build = $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
        if ($build->exists()) {
          $build->parent = $target;
          $target[$property] = $build;
          return $target;
        }
      }
      if ($candidate instanceof EntityReferenceItem) {
        $target_type = $candidate->getFieldDefinition()->getSetting('target_type');
        if ($property == $target_type) {
          $item_value = $candidate->getValue();
          if (isset($item_value['target_id']) || isset($item_value['entity'])) {
            unset($data[$key]);
            $data[$property] = !empty($item_value['entity']) ? $item_value['entity'] : $this->entityTypeManager->getStorage($target_type)->load($item_value['target_id']);
            $build = $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
            if ($build->exists()) {
              $build->parent = $target;
              $target[$property] = $build;
              return $target;
            }
          }
        }
      }
    }
    if (is_array($candidate) || $candidate instanceof ListInterface) {
      $target = $this->buildRecursive([
        '_token_keys' => [$key],
        '_halt_at_key_level' => TRUE,
        $key => $candidate,
      ], $options, $bubbleable_metadata, [$key], $check_access);
      $data['_token_keys'] = $token_keys;
      $fixed_delta = is_numeric($property) && (int) $property == $property;
      if ($fixed_delta) {
        array_shift($token_keys);
      }
      elseif ($candidate instanceof EntityReferenceFieldItemListInterface) {
        $target_type = $candidate->getFieldDefinition()->getSetting('target_type');
        if ($property == $target_type) {
          array_shift($token_keys);
          $candidate = $candidate->referencedEntities();
        }
      }
      foreach ($candidate as $delta_item => $item) {
        if (!$fixed_delta || $delta_item == $property) {
          array_unshift($token_keys, $delta_item);
          $data[$delta_item] = $item;
          $build = $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
          if ($build->exists()) {
            $build->parent = $target;
            $target[$delta_item] = $build;
          }
          array_shift($token_keys);
        }
      }
      return $target;
    }
    if (is_object($candidate)) {
      foreach (['get' . ucfirst($property), $property] as $method) {
        if (method_exists($candidate, $method)) {
          $data[$property] = call_user_func([$candidate, $method]);
          break;
        }
      }
      if ($candidate instanceof ConfigEntityInterface) {
        $property_values = [];
        if ($property === $candidate->getEntityTypeId()) {
          array_shift($token_keys);
        }
        $property_name = implode('.', $token_keys);
        if ($value = $candidate->get($property_name)) {
          NestedArray::setValue($property_values, $token_keys, $value, TRUE);
          $target[$property] = IterableMarkup::create($property_values, NULL, $target);
          return $target;
        }
      }
      if (!isset($data[$property]) && isset($candidate->$property)) {
        $data[$property] = $candidate->$property;
      }
      if (isset($data[$property])) {
        $build = $this->buildRecursive($data, $options, $bubbleable_metadata, $token_keys, $check_access);
        if ($build->exists()) {
          $build->parent = $target;
          $target[$property] = $build;
          return $target;
        }
      }
    }
    if (!isset($target[$property])) {
      $property_values = [];
      while (TRUE) {
        // Pass in the first key without any subsequent token keys, to indicate
        // that the token replacement is the last chance to get a value.
        $data['_token_keys'] = $token_keys;
        $build = $this->buildRecursive(['_halt_at_key_level' => TRUE] + $data, $options, $bubbleable_metadata, [$key], $check_access);
        array_shift($token_keys);
        if ($build->exists()) {
          NestedArray::setValue($property_values, $token_keys, $build, TRUE);
          $target[$property] = $property_values instanceof IterableMarkup ? $property_values : IterableMarkup::create($property_values, NULL, $target);
          break;
        }
        if (empty($token_keys)) {
          break;
        }
      }
    }

    return $target;
  }

  /**
   * Get the token entity mapper.
   *
   * @return \Drupal\token\TokenEntityMapperInterface|null
   *   The token type mapper, or NULL if not available.
   */
  protected static function getTokenEntityMapper() {
    return \Drupal::hasService('token.entity_mapper') ? \Drupal::service('token.entity_mapper') : NULL;
  }

  /**
   * Get the token type for the given entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   *
   * @return string
   *   The token type.
   */
  protected function getTokenTypeForEntity(EntityInterface $entity) {
    if ($this->tokenEntityMapper) {
      $token_type = $this->tokenEntityMapper->getTokenTypeForEntityType($entity->getEntityTypeId(), TRUE);
    }
    else {
      $definition = $this->entityTypeManager->getDefinition($entity->getEntityTypeId());
      $token_type = $definition->get('token_type') ?: $entity->getEntityTypeId();
    }
    return $token_type;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc