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

src/MustacheTokenProcessor.php
<?php

namespace Drupal\mustache;

use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Cache\BackendChain;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\Utility\Token;
use Drupal\mustache\Plugin\MustacheMagicManager;
use Drupal\mustache\Render\IterableMarkup;

/**
 * Takes care of token processing within the render pipeline.
 *
 * @see \Drupal\mustache\Element\Mustache
 */
class MustacheTokenProcessor {

  /**
   * The cache bin to store tokenized template content.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $templateCache;

  /**
   * The cache backend that is an in-memory cache for Mustache data.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $dataMemoryCache;

  /**
   * The consistent cache backend for Mustache data.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $dataConsistentCache;

  /**
   * The chained cache backends for Mustache data.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $dataChainedCache;

  /**
   * The cache contexts manager.
   *
   * @var \Drupal\Core\Cache\Context\CacheContextsManager
   */
  protected $cacheContextsManager;

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

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

  /**
   * The context repository.
   *
   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
   */
  protected $contextRepository;

  /**
   * The manager of Mustache magic variable plugins.
   *
   * @var \Drupal\mustache\Plugin\MustacheMagicManager
   */
  protected $magicPluginManager;

  /**
   * Statically cached context IDs.
   *
   * @var string[]
   */
  protected static $contextIds;

  /**
   * The MustacheTokenProcessor constructor.
   *
   * @param \Drupal\Core\Cache\CacheBackendInterface $template_cache
   *   The cache bin to store tokenized template content.
   * @param \Drupal\Core\Cache\CacheBackendInterface $data_memory_cache
   *   The cache backend that is an in-memory cache for Mustache data.
   * @param \Drupal\Core\Cache\CacheBackendInterface $data_consistent_cache
   *   The consistent cache backend for Mustache data.
   * @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
   *   The cache contexts manager.
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
   *   The context repository.
   * @param \Drupal\mustache\Plugin\MustacheMagicManager $magic_plugin_manager
   *   The manager of Mustache magic variable plugins.
   */
  public function __construct(CacheBackendInterface $template_cache, CacheBackendInterface $data_memory_cache, CacheBackendInterface $data_consistent_cache, CacheContextsManager $cache_contexts_manager, Token $token, EntityTypeManagerInterface $entity_type_manager, ContextRepositoryInterface $context_repository, MustacheMagicManager $magic_plugin_manager) {
    $this->templateCache = $template_cache;
    $this->dataMemoryCache = $data_memory_cache;
    $this->dataConsistentCache = $data_consistent_cache;
    $this->dataChainedCache = (new BackendChain())
      ->appendBackend($data_memory_cache)
      ->appendBackend($data_consistent_cache);
    $this->cacheContextsManager = $cache_contexts_manager;
    $this->token = $token;
    $this->entityTypeManager = $entity_type_manager;
    $this->contextRepository = $context_repository;
    $this->magicPluginManager = $magic_plugin_manager;
  }

  /**
   * Processes tokens for the given template content.
   *
   * Before invoking this method, check before whether it is really needed.
   *
   * @param array &$element
   *   The current element build array.
   * @param string &$template_content
   *   The current template content.
   *
   * @\Drupal\mustache\Element\Mustache::processTokens().
   */
  public function processTokens(array &$element, &$template_content) {
    if (!isset($element['#data'])) {
      $element['#data'] = [];
    }
    if (!is_array($element['#with_tokens'])) {
      $element['#with_tokens'] = [];
    }

    $token_settings = &$element['#with_tokens'];
    $token_options = isset($token_settings['options']) ? $token_settings['options'] : [];
    $langcode = $token_options['langcode'] ?? \Drupal::languageManager()->getCurrentLanguage()->getId();

    // By default, empty or non-matched variables shall be cleared.
    if (!isset($token_options['clear'])) {
      $token_options['clear'] = TRUE;
    }

    $template_hash = hash('md4', $template_content);
    $tokenized = $this->tokenizeTemplate($template_hash, $template_content);
    if (empty($tokenized['tokens'])) {
      // No contained tokens were found, thus halt at this point.
      return;
    }
    $template_content = $tokenized['content'];
    $template_tokens = $tokenized['tokens'];
    $mustache_variables = $tokenized['variables'];

    if (!empty($element['#cache'])) {
      $bubbleable_metadata = BubbleableMetadata::createFromRenderArray($element);
    }
    else {
      $bubbleable_metadata = new BubbleableMetadata();
    }

    // Build up the token data.
    $token_data = isset($token_settings['data']) ? $token_settings['data'] : [];
    $this->processData($token_data, $template_tokens, 'view', $bubbleable_metadata, $langcode);

    // Determine cacheability of the Token data.
    $data_is_cacheable = empty(array_diff_key($template_tokens, $token_data));
    $data_cid = [];
    foreach ($token_data as $t_key => $t_value) {
      $data_cid[] = $t_key;
      $entity = NULL;
      if (is_object($t_value)) {
        if ($t_value instanceof EntityInterface) {
          $entity = $t_value;
        }
        elseif ($t_value instanceof \JsonSerializable) {
          $data_cid[] = json_encode($t_value->jsonSerialize());
        }
        elseif (method_exists($t_value, 'getEntity')) {
          $entity = $t_value->getEntity();
        }
        elseif (method_exists($t_value, '__toString')) {
          $data_cid[] = (string) $t_value;
        }
        elseif (method_exists($t_value, 'getString')) {
          $data_cid[] = $t_value->getString();
        }
        elseif (method_exists($t_value, 'getId')) {
          $data_cid[] = $t_value->getId();
        }
        else {
          $data_is_cacheable = FALSE;
          break;
        }
      }
      elseif (is_scalar($t_value)) {
        $data_cid[] = (string) $t_value;
      }
      else {
        $data_is_cacheable = FALSE;
        break;
      }
      if ($entity) {
        if ($entity->isNew()) {
          $data_is_cacheable = FALSE;
          break;
        }
        $data_cid[] = $entity->getEntityTypeId() . ':' . $entity->id();
      }
    }

    // Try to get previously generated data from cache.
    $previous_data = NULL;
    if ($data_is_cacheable) {
      if ($cache_contexts = $bubbleable_metadata->getCacheContexts()) {
        $context_cache_keys = $this->cacheContextsManager->convertTokensToKeys($cache_contexts);
        $data_cid = array_merge($data_cid, $context_cache_keys->getKeys());
      }
      $data_cid = 'mustache:token:' . $template_hash . ':' . hash('md4', implode(':', $data_cid));
      if ($cached = $this->dataChainedCache->get($data_cid, TRUE)) {
        if ($cached->valid) {
          $mustache_data = $cached->data;
          $this->attachTokenData($element, $mustache_data);
          return;
        }
        else {
          $previous_data = $cached->data;
        }
      }
    }

    // Generate iterable token replacement values.
    $replacements = [];
    foreach ($template_tokens as $type => $tokens) {
      if ($type === 'iterate') {
        $replacements += $this->token->generate($type, $tokens, $token_data, $token_options, $bubbleable_metadata);
      }
      else {
        foreach ($tokens as $token) {
          $target = static::getTokenIterate()->getIterableTarget(substr($token, 1, -1), $token_data, $token_options, $bubbleable_metadata);
          if ($token_options['clear'] || $target->exists()) {
            $replacements[$token] = $target;
          }
        }
      }
      if ($token_options['clear']) {
        $replacements += array_fill_keys($tokens, '');
      }
    }

    // No explicit escaping of token values is being done here, because
    // wrapped Mustache variables will be escaped by default, if not using
    // the triple {{{}}} parenthesis.
    // Optionally alter the list of replacement values.
    if (!empty($token_options['callback'])) {
      $function = $token_options['callback'];
      $function($replacements, $token_data, $token_options, $bubbleable_metadata);
    }

    $bubbleable_metadata->applyTo($element);

    // Merge the replacement values into one iterable data tree.
    $mustache_data = IterableMarkup::create();
    foreach ($replacements as $token => $replacement) {
      $var_pieces = explode('.', $mustache_variables[$token]);
      $token_pieces = explode(':', substr($token, 1, -1));
      // Special care taken for the "iterate" token.
      $is_iterate = reset($token_pieces) === 'iterate';

      $iterable = &$mustache_data;
      foreach ($token_pieces as $i => $token_piece) {
        $var_piece = $var_pieces[$i];
        if (!isset($iterable[$var_piece])) {
          $iterable[$var_piece] = IterableMarkup::create([], NULL, $iterable);
        }
        elseif (!($iterable[$var_piece] instanceof IterableMarkup)) {
          $iterable[$var_piece] = IterableMarkup::create([], $iterable[$var_piece], $iterable);
        }
        $iterable = &$iterable[$var_piece];
        if (($replacement instanceof \ArrayAccess) || is_array($replacement)) {
          $replacement = isset($replacement[$token_piece]) ? $replacement[$token_piece] : ($is_iterate ? $replacement : IterableMarkup::create([], NULL, $replacement));
          continue;
        }
        if (is_object($replacement)) {
          if (method_exists($replacement, $token_piece)) {
            $reflection = new \ReflectionMethod($replacement, $token_piece);
            if (!$reflection->getNumberOfRequiredParameters()) {
              $replacement = $replacement->$token_piece();
            }
          }
          elseif (isset($replacement->$token_piece)) {
            $replacement = $replacement->$token_piece;
          }
          continue;
        }
      }

      if ($replacement instanceof IterableMarkup) {
        if (!empty($replacement->string)) {
          $iterable->value = $replacement->string;
        }
        $iterable->items = array_merge($iterable->items, $replacement->items);
        $iterable->updatePositions();
      }
      elseif (is_iterable($replacement)) {
        foreach ($replacement as $key => $value) {
          // For being able to perform simple checks within the loop of
          // iterable data, like using {{#first}} and {{#last}} checks, any
          // value is being wrapped by an iterable object too.
          if (is_numeric($key)) {
            $iterable[] = IterableMarkup::create([], $value, $iterable);
          }
          else {
            $iterable[$key] = IterableMarkup::create([], $value, $iterable);
          }
        }
      }
      elseif (is_scalar($replacement) || (is_object($replacement) && method_exists($replacement, '__toString'))) {
        // When the replacement value represents a scalar, then the value of
        // the current iterable object is being set by the scalar itself.
        $iterable->value = (string) $replacement;
      }

      // Unset to not manipulate the last reference in the next loop.
      unset($iterable);
    }

    // The existence of a "previous" key within $mustache_data indicates the
    // attempt to access previous data as Mustache variable. We can safely skip
    // adding previous data here as we know it would not be needed.
    if (isset($previous_data, $mustache_data->getItems()['previous'])) {
      unset($mustache_data['previous']);
      if (isset($previous_data['previous']) || (($previous_data instanceof IterableMarkup) && isset($previous_data->getItems()['previous']))) {
        $prev_prev = $previous_data['previous'];
        // Unset the "previous" key from the previously fetched data, in order
        // to avoid unlimited growth.
        unset($previous_data['previous']);
        if (($previous_data instanceof IterableMarkup && ($previous_data->jsonSerialize() == $mustache_data->jsonSerialize())) || (!($previous_data instanceof IterableMarkup) && ($previous_data == $mustache_data))) {
          // Previously fetched data appears to be the same, thus jump once
          // more back to previous data of the previously fetched one.
          unset($prev_prev['previous']);
          $previous_data = $prev_prev;
        }
      }
      $mustache_data['previous'] = $previous_data;
    }

    if ($data_is_cacheable) {
      $bubbleable_metadata->addCacheTags(['mustache:token']);
      $this->dataChainedCache->set($data_cid, $mustache_data, $bubbleable_metadata->getCacheMaxAge(), $bubbleable_metadata->getCacheTags());
    }

    $this->attachTokenData($element, $mustache_data);
  }

  /**
   * Processes given token data by enriching with context and access filtering.
   *
   * @param array &$token_data
   *   The token data to process.
   * @param array|null $tokens
   *   (Optional) An array of tokens that are known to be used. Set to NULL if
   *   they are not known beforehand. This should be set whenever possible,
   *   otherwise cacheability may be not working properly.
   * @param string $check_access
   *   (Optional) The access check to perform. Set NULL to skip access filter.
   * @param \Drupal\Core\Render\BubbleableMetadata|null $bubbleable_metadata
   *   (Optional) The object to collect bubbleable metadata.
   * @param string|null $langcode
   *   (Optional) The langcode to use. Leave NULL to use the current language.
   */
  public function processData(array &$token_data, $tokens = NULL, $check_access = 'view', $bubbleable_metadata = NULL, $langcode = NULL) {
    if (isset($tokens) && empty($tokens)) {
      return;
    }

    if (!isset($bubbleable_metadata)) {
      $bubbleable_metadata = new BubbleableMetadata();
    }
    if (!isset($langcode)) {
      $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
    }

    /** @var \Drupal\token\TokenEntityMapperInterface $entity_token_type_mapper */
    $entity_token_type_mapper = \Drupal::hasService('token.entity_mapper') ? \Drupal::service('token.entity_mapper') : NULL;
    // Merge with static data, e.g. provided by preprocess hooks.
    if ($static_token_data = &drupal_static('mustache_tokens', [])) {
      foreach ($static_token_data as $type => $value) {
        if (isset($value) && !isset($token_data[$type])) {
          $token_data[$type] = $value;
        }
      }
    }

    $type_id = 'user';
    if (!isset($token_data[$type_id]) && (!isset($tokens) || isset($tokens[$type_id]))) {
      $token_data[$type_id] = $this->entityTypeManager->getStorage($type_id)->load(\Drupal::currentUser()->id());
    }

    // Merge the token data with any global context that provides a value.
    $context_repository = $this->contextRepository;
    $contexts = [];
    if (!isset(static::$contextIds)) {
      static::$contextIds = array_keys($context_repository->getAvailableContexts());
    }
    if (!empty(static::$contextIds)) {
      foreach ($context_repository->getRuntimeContexts(static::$contextIds) as $context) {
        $context_data_type = explode(':', $context->getContextDefinition()->getDataType());
        // For "entity:*" data types, we skip its prefixes and only respect
        // the last entry of the data type string (e.g. for "entity:node", we
        // want to only use "node").
        $type = end($context_data_type);
        $value = $context->hasContextValue() ? $context->getContextValue() : NULL;
        if ($value instanceof EntityInterface) {
          if ($entity_token_type_mapper) {
            $type = $entity_token_type_mapper->getTokenTypeForEntityType($value->getEntityTypeId(), TRUE);
          }
          else {
            $definition = $this->entityTypeManager->getDefinition($value->getEntityTypeId());
            $type = $definition->get('token_type') ?: $value->getEntityTypeId();
          }
        }
        $contexts[$type] = $context;
        if (isset($value) && !isset($token_data[$type]) && (!isset($tokens) || isset($tokens[$type]))) {
          $token_data[$type] = $value;
        }
      }
    }

    // If not yet given otherwise, get entities from route parameters.
    foreach (\Drupal::routeMatch()->getParameters() as $parameter) {
      if (!($parameter instanceof EntityInterface)) {
        continue;
      }
      $type_id = $parameter->getEntityTypeId();
      if (!isset($token_data[$type_id]) && (!isset($tokens) || isset($tokens[$type_id]))) {
        $token_data[$type_id] = $parameter;
      }
    }

    // Switch to translations if necessary and available.
    // Also re-map entities that may have a different token type defined.
    foreach ($token_data as $type => $value) {
      if (($value instanceof TranslatableInterface) && ($value->language()->getId() !== $langcode) && ($value->hasTranslation($langcode))) {
        $value = $value->getTranslation($langcode);
        $token_data[$type] = $value;
      }
      if (!($value instanceof EntityInterface)) {
        continue;
      }
      /** @var \Drupal\Core\Entity\EntityInterface $value */
      if ($entity_token_type_mapper) {
        $the_real_token_type = $entity_token_type_mapper->getTokenTypeForEntityType($value->getEntityTypeId(), TRUE);
      }
      else {
        $definition = $this->entityTypeManager->getDefinition($value->getEntityTypeId());
        $the_real_token_type = $definition->get('token_type') ?: $value->getEntityTypeId();
      }
      if (!isset($token_data[$the_real_token_type])) {
        if (isset($contexts[$type])) {
          $contexts[$the_real_token_type] = $contexts[$type];
        }
        $token_data[$the_real_token_type] = $value;
      }
    }

    // Filter out data that is not allowed to be viewed.
    // Also add cacheability metadata of data that is being used.
    foreach ($token_data as $type => $value) {
      if ($value instanceof CacheableDependencyInterface) {
        $bubbleable_metadata->addCacheableDependency($value);
      }
      if (isset($contexts[$type])) {
        $bubbleable_metadata->addCacheableDependency($contexts[$type]);
      }
      if ($check_access && $value instanceof AccessibleInterface) {
        /** @var \Drupal\Core\Access\AccessResultInterface $access_result */
        $access_result = $value->access($check_access, NULL, TRUE);
        $bubbleable_metadata->addCacheableDependency($access_result);
        if (!$access_result->isAllowed()) {
          unset($token_data[$type]);
          continue;
        }
      }
    }
  }

  /**
   * Tokenizes the given template content.
   *
   * @param string $template_name
   *   A unique name identifies the template content.
   * @param string $template_content
   *   The template content to tokenize.
   *
   * @return array
   *   Extracted tokenized results, keyed by 'content' that holds the tokenized
   *   template content, 'tokens' holds identified tokens, and 'variables' holds
   *   a mapping of tokens as keys to Mustache variables as values.
   */
  public function tokenizeTemplate($template_name, $template_content) {
    $template_cid = 'mustache:tokenized:' . $template_name;
    if ($cached = $this->templateCache->get($template_cid)) {
      return $cached->data;
    }
    else {
      if (strpos($template_content, 'iterate') !== FALSE) {
        // Special handling for iterate tokens, because IterableMarkup
        // implements methods and properties that could collide by having the
        // same name as the token name. Examples: "count", "first" and "last".
        $iterate_reflection = new \ReflectionClass(IterableMarkup::class);
        foreach ([':', '.'] as $accessor) {
          foreach ($iterate_reflection->getProperties() as $property) {
            $search = 'iterate' . $accessor . $property->getName();
            if (strpos($template_content, $search) !== FALSE) {
              $template_content = str_replace($search, 'iterate' . $accessor . '_' . $property->getName(), $template_content);
            }
          }
          foreach ($iterate_reflection->getMethods() as $method) {
            $search = 'iterate' . $accessor . $method->getName();
            if (strpos($template_content, $search) !== FALSE) {
              $template_content = str_replace($search, 'iterate' . $accessor . '_' . $method->getName(), $template_content);
            }
          }
        }
      }

      // Scan for regular tokens.
      $template_tokens = $this->token->scan($template_content);
      // Also include Mustache variables as tokens, but exclude magic variables.
      $magic_variables = [];
      foreach (array_keys($this->magicPluginManager->getDefinitions()) as $plugin_id) {
        $plugin_id_parts = explode(':', $plugin_id);
        if ($magic_variable = reset($plugin_id_parts)) {
          $magic_variables[$magic_variable] = 1;
        }
      }
      preg_match_all('/\{\{[^a-zA-Z\d\_\-\{\#\&\^]*([a-zA-Z\d\_\-\?\<\>\/\.]+)[^a-zA-Z\d\_\-\.\}]*\}\}/x', $template_content, $matches);
      if (!empty($matches[1])) {
        foreach ($matches[1] as $match) {
          $match = trim($match);
          $parts = explode('.', $match);
          $type = array_shift($parts);
          if (empty($type) || empty(reset($parts)) || isset($magic_variables[$type])) {
            // A token always consists of at least two parts: The token type as
            // first prefix, followed by a ":" and a name (e.g. node:title).
            // Therefore, we do not try to include variables that only consist
            // of one key (i.e. only include variables with a "." dot).
            continue;
          }
          if (!isset($token_data[$type])) {
            $type_hyphened = str_replace('_', '-', $type);
            if (isset($token_data[$type_hyphened])) {
              $template_content = str_replace($match, $type_hyphened . '.' . implode('.', $parts), $template_content);
              $type = $type_hyphened;
            }
          }
          $name = implode(':', $parts);
          array_unshift($parts, $type);
          $token = '[' . implode(':', $parts) . ']';
          $template_tokens[$type][$name] = $token;
        }
      }
      if (empty($template_tokens)) {
        $result = [
          'content' => $template_content,
          'tokens' => $template_tokens,
          'variables' => [],
        ];
        $this->templateCache->set($template_cid, $result);
        return $result;
      }
      unset($matches);

      // Transform tokens into Mustache variables.
      $mustache_variables = [];
      foreach ($template_tokens as $type => $tokens) {
        foreach ($tokens as $token) {
          $variable = str_replace(':', '.', substr($token, 1, -1));
          if (strpos($variable, '-') !== FALSE) {
            // Transform any hyphens into underscores, so that built up
            // data trees always match for given keys, even when hyphens and
            // underscores are being mixed up.
            $variable_underscore = str_replace('-', '_', $variable);
            $template_content = str_replace($variable, $variable_underscore, $template_content);
            $variable = $variable_underscore;
          }
          $mustache_variables[$token] = $variable;
        }
      }

      // Ensure that standalone tokens are being wrapped by Mustache brackets,
      // and replace all token versions by their Mustache variable counterparts.
      preg_match_all('/(\{\{)?[^a-zA-Z\d\_\-\{\#\&\^]*(\[[^\s\[\]:]+:[^\[\]]+\])[^a-zA-Z\d\_\-\?\.\}]*(\}\})?/x', $template_content, $matches);
      if (!empty($matches[2])) {
        foreach ($matches[2] as $i => $token) {
          if (!isset($mustache_variables[$token])) {
            continue;
          }
          $search = $matches[0][$i];
          $variable = $mustache_variables[$token];
          if ($matches[1][$i] !== '{{') {
            $variable = '{{' . $variable;
          }
          if ($matches[3][$i] !== '}}') {
            $variable .= '}}';
          }
          $replace = str_replace($token, $variable, $search);
          $template_content = str_replace($search, $replace, $template_content);
        }
      }

      $result = [
        'content' => $template_content,
        'tokens' => $template_tokens,
        'variables' => $mustache_variables,
      ];
      // Cache the tokenized results for the next request.
      $this->templateCache->set($template_cid, $result);
      return $result;
    }
  }

  /**
   * Get the memory cache for Mustache data.
   *
   * @return \Drupal\Core\Cache\CacheBackendInterface
   *   The memory cache.
   */
  public function getDataMemoryCache() {
    return $this->dataMemoryCache;
  }

  /**
   * Get the consistent cache for Mustache data.
   *
   * @return \Drupal\Core\Cache\CacheBackendInterface
   *   The consistent cache.
   */
  public function getDataConsistentCache() {
    return $this->dataConsistentCache;
  }

  /**
   * Get the chained cache for Mustache data.
   *
   * @return \Drupal\Core\Cache\CacheBackendInterface
   *   The chained cache.
   */
  public function getDataChainedCache() {
    return $this->dataChainedCache;
  }

  /**
   * Attaches the generated Token data to the render element.
   *
   * @param array &$element
   *   The current element build array.
   * @param mixed &$mustache_data
   *   The Token data to attach.
   */
  protected function attachTokenData(array &$element, &$mustache_data) {
    if (empty($element['#data'])) {
      $element['#data'] = $mustache_data;
    }
    elseif (is_iterable($element['#data'])) {
      foreach ($element['#data'] as $key => $value) {
        if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
          $mustache_data[$key] = $value;
        }
      }
      $element['#data'] = $mustache_data;
    }
    elseif (isset($element['#override_data']) && is_iterable($element['#override_data'])) {
      foreach ($element['#override_data'] as $key => $value) {
        if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
          $mustache_data[$key] = $value;
        }
      }
      $element['#override_data'] = $mustache_data;
    }
    else {
      $element['#override_data'] = $mustache_data;
    }
  }

  /**
   * Get the token iterate service.
   *
   * @return \Drupal\mustache\MustacheTokenIterate
   *   The token iterate service.
   */
  protected static function getTokenIterate() {
    return \Drupal::service('mustache.token_iterate');
  }

}

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

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