mustache_templates-8.x-1.0-beta4/src/Plugin/mustache/Magic/Introspection.php

src/Plugin/mustache/Magic/Introspection.php
<?php

namespace Drupal\mustache\Plugin\mustache\Magic;

use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\Url;
use Drupal\mustache\Magic\LazyMagic;
use Drupal\mustache\Plugin\MustacheMagic;
use Drupal\mustache\Plugin\MustacheMagicInterface;
use Drupal\mustache\Render\IterableMarkup;
use Drupal\mustache\Render\Markup;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Introspect available resources (keys, partials) within Mustache templates.
 *
 * Usage:
 *
 * You can use the "magical" {{show.<resource>}} variable anywhere Mustache
 * template syntax is allowed. For example, the Mustache input filter plugin or
 * - when "mustache_token" is installed - any input that supports tokens.
 *
 * When that variable is used in a template, the contents will not be printed
 * out within the rendered template, but instead printed out as a message
 * on the currently opened page. Printed messages are only shown for users who
 * have the "view mustache debug messages" permission.
 *
 * @code
 * {{show.keys}}
 * {{show.partials}}
 * @endcode
 *
 * @MustacheMagic(
 *   id = "show",
 *   label = @Translation("Introspection"),
 *   description = @Translation("Use the <b>{{show.keys}}</b> variable to print out available data keys, and <b>{{show.partials}}</b> to print out a list of available reusable templates. Requires the <em>&quot;View Mustache debug messages&quot;</em> permission."),
 *   lambda = FALSE
 * )
 */
class Introspection extends MustacheMagic {

  /**
   * The module handler, if any.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface|null
   */
  protected $moduleHandler;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    if (isset($instance->messenger)) {
      $instance->moduleHandler = $container->get('module_handler');
    }
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    $trusted = parent::trustedCallbacks();
    $trusted[] = 'keys';
    $trusted[] = 'partials';
    return $trusted;
  }

  /**
   * Prints out a list of available data keys.
   *
   * @return string
   *   An empty string is returned, so that rendered template output is not
   *   affected when using a {{show}} variable.
   */
  public function keys() {
    if (!$this->messenger) {
      $this->logger->warning(sprintf("A {{show}} variable is set for debugging purposes in a Mustache template (%s). Remove that variable from the template when it is not needed anymore.", $this->element['#template']['name']));
      return $this->__toString();
    }

    $with_tokens = $this->withTokens();

    $keys = [];
    $data = $this->data;
    if ($data instanceof IterableMarkup) {
      $items = isset($data->items) ? $data->items : [];
      if (!empty($data->value) && (empty($items) || !isset($items['value']))) {
        $items['value'] = $data->value;
      }
      $data = $items;
    }
    if (is_iterable($data)) {
      foreach ($data as $key => $value) {
        if ($key === '') {
          continue;
        }
        $this->buildKeysInfo($keys, $value, $key);
      }
    }
    $endpoint = NULL;
    if (isset($this->element['#endpoint'])) {
      $endpoint = $this->element['#endpoint'] instanceof Url ? $this->element['#endpoint']->toString() : (string) $this->element['#endpoint'];
    }
    if (isset($endpoint, $this->element['#success'])) {
      $received = FALSE;
      if ($this->element['#success']) {
        /** @var \Drupal\mustache\MustacheHttp $http */
        $http = \Drupal::service('mustache.http');
        $received = $http->getData($this->element['#endpoint']);
      }
      if ($received === FALSE) {
        $this->messenger->addError(t('Could not fetch data from Json URL endpoint @url. Is that endpoint reachable?', ['@url' => $endpoint]));
      }
      elseif (!is_array($received)) {
        $this->messenger->addError(t('Unexpected data type received from Json endpoint @url. Mustache Templates always expect an array, got @type.', [
          '@url' => $endpoint,
          '@type' => gettype($received),
        ]));
      }
      else {
        $this->messenger->addMessage(t('Successfully received data (@type, containing @num items) from endpoint @url.', [
          '@url' => $endpoint,
          '@type' => gettype($received),
          '@num' => count($received),
        ]));
        $received_keys = [];
        foreach ($received as $r_k => $r_v) {
          $this->buildKeysInfo($received_keys, $r_v, $r_k);
        }
        $list = Markup::create('<ul><li>' . implode('</li><li>', $received_keys) . '</li></ul>');
        $this->messenger->addMessage(t('That endpoint returned the following data keys:<br />@list', ['@list' => $list]));
      }
    }
    if ($with_tokens) {
      $token_settings = &$this->element['#with_tokens'];
      $token_data = isset($token_settings['data']) ? $token_settings['data'] : [];
      /** @var \Drupal\mustache\MustacheTokenProcessor $token_processor */
      $token_processor = \Drupal::service('mustache.token_processor');
      $token_processor->processData($token_data);
      if (!empty($token_data)) {
        $token_info = \Drupal::token()->getInfo();
        foreach ($token_info['tokens'] as $type => $tokens) {
          if (isset($token_data[$type])) {
            $this->buildKeysInfo($keys, $token_data[$type], $type);
            foreach ($tokens as $key => $token) {
              $key = $type . '.' . str_replace(':', '.', $key);
              $keys[$key] = '<b>{{' . $key . '}}</b> <em>' . $token['name'] . '</em>';
            }
          }
        }
      }
    }
    foreach (array_keys($keys) as $key) {
      $key_parts = explode('.', $key);
      if (is_numeric(end($key_parts))) {
        // Encourage the usage concrete property names. This also lowers the
        // possibly huge amount of suggestions, at least for a little bit.
        unset($keys[$key]);
      }
    }
    if (!empty($keys)) {
      $list = Markup::create('<ul><li>' . implode('</li><li>', $keys) . '</li></ul>');
      if (isset($received_keys) && $with_tokens) {
        $this->messenger->addMessage(t('Further available:<br />@list', ['@list' => $list]));
      }
      else {
        $this->messenger->addMessage(t('All available data keys:<br />@list', ['@list' => $list]));
      }
      if ($this->moduleHandler->moduleExists('mustache_token')) {
        $this->messenger->addMessage(t('If you want to iterate through any of the above mentioned keys, you may prefix the key with "iterate.&lt;operation&gt;.*", for example {{iterate.loop.node.field_mytodolist}}. Available <b>&lt;operation&gt;</b> keys:<br /><ul><li>{{iterate.<b>loop</b>.*}}</li><li>{{iterate.<b>explode</b>.*}}</li><li>{{iterate.<b>first</b>.*}}</li><li>{{iterate.<b>last</b>.*}}</li><li>{{iterate.<b>count</b>.*}}</li><li>{{iterate.<b>empty</b>.*}}</li><li>{{iterate.<b>sum</b>.*}}</li><li>{{iterate.<b>min</b>.*}}</li><li>{{iterate.<b>max</b>.*}}</li><li>{{iterate.<b>avg</b>.*}}</li></ul>'));
      }
      $this->messenger->addMessage(t('It is sometimes possible to access previous data with {{previous.<b>*</b>}}. Also conditions are possible regards previous data, for example <em>{{#if.node.field_mytodolist.greaterthan.previous}}Even more to do...{{/if.node.field_mytodolist.greaterthan.previous}}</em>. This mechanic is not reliable though and should not be used for business critical data.'));
    }
    elseif (!$with_tokens) {
      $this->messenger->addWarning(t('No explicitly set data keys are available.'));
    }
    if ($with_tokens) {
      if ($this->moduleHandler->moduleExists('token') && $this->moduleHandler->moduleExists('help')) {
        $this->messenger->addMessage(t('<a href="/admin/help/token" target="_blank">Global tokens</a> may be available for you. Available tokens like "[site:name]" may be used as Mustache variables like "{{site.name}}".'));
      }
      else {
        $this->messenger->addMessage(t('Global tokens may be available for you. Available tokens like "[site:name]" may be used as Mustache variables like "{{site.name}}".'));
      }
    }
    $this->messenger->addMessage(t('When form binding is enabled, you may access submitted form values with {{form.<b>&lt;name&gt;</b>}}'));
    $this->messenger->addWarning(t('Above messages are printed because a {{show}} variable has been set in a Mustache template. Remove that variable from the template so that these messages will not show up anymore.'));

    return "";
  }

  /**
   * Prints out a list of reusable templates.
   *
   * @return string
   *   An empty string is returned, so that rendered template output is not
   *   affected when using a {{show}} variable.
   */
  public function partials() {
    if (!$this->messenger) {
      $this->logger->warning(sprintf("A {{show}} variable is set for debugging purposes in a Mustache template (%s). Remove that variable from the template when it is not needed anymore.", $this->element['#template']['name']));
      return $this->__toString();
    }

    /** @var \Drupal\mustache\MustacheTemplates */
    $templates = \Drupal::service('mustache.templates');
    $available = [];
    $all_templates = array_keys($templates->findAll());
    $num = count($all_templates);
    foreach ($all_templates as $name) {
      if ($num < 100) {
        $available[$name] = '<b>' . $name . '</b> :<br />' . trim(nl2br(htmlentities($templates->getContent($name))));
        $len = strlen($available[$name]);
        if ($len > 400) {
          $available[$name] = function_exists('text_summary') ? text_summary($available[$name], NULL, 400) : substr($available[$name], 0, 400);
          $available[$name] .= '<br/><em><b>... (+ ' . ($len - 400) . ' more characters)</b></em>';
        }
      }
      else {
        $available[$name] = '<b>' . $name . '</b>';
      }
    }
    $list = Markup::create('<ul><li>' . implode('</li><li>', $available) . '</li></ul>');
    if (empty($available)) {
      $this->messenger->addWarning(t('No reusable templates were found.'));
    }
    else {
      $this->messenger->addMessage(t('You can reuse templates as partials, for example <b>{{>template_name}}</b>.'));
      $this->messenger->addMessage(t('Available templates (<b>@num</b> total, names are shown first and are <b>bold</b>):<br/>@list', ['@num' => $num, '@list' => $list]));
    }
    $this->messenger->addWarning(t('Above messages are printed because a {{show}} variable has been set in a Mustache template. Remove that variable from the template so that these messages will not show up anymore.'));
    return "";
  }

  /**
   * Recursively builds up the keys info for a given value.
   *
   * @param array &$keys
   *   Already added keys.
   * @param mixed $data
   *   The currently scoped data value.
   * @param mixed $parent_key
   *   The parent key.
   * @param int $level
   *   (Optional) The current nesting level. This method aborts at level 3.
   * @param array $additional_info
   *   (Optional) Additional info.
   */
  protected function buildKeysInfo(array &$keys, $data, $parent_key, $level = 0, array $additional_info = []) {
    if ($level > 3) {
      return;
    }
    $level++;

    if ($data instanceof EntityReferenceFieldItemListInterface) {
      $target_type = $data->getFieldDefinition()->getSetting('target_type');
      $entities = $data->referencedEntities();
      $count = count($entities);
      $cardinality = (int) $data->getFieldDefinition()->getFieldStorageDefinition()->getCardinality();
      $name = $data->getName();
      foreach ($entities as $delta => $entity) {
        $additional_info = [];
        if ($cardinality !== 1) {
          $additional_info = [
            t('number of items within <em>@name</em>: <b>@num</b>', [
              '@num' => $count,
              '@name' => $name,
            ]),
          ];
        }
        $this->buildKeysInfo($keys, $entity, $parent_key . '.' . $target_type, $level, $additional_info);
        if ($cardinality !== 1) {
          $additional_info = [];
          if ($count > 1) {
            $additional_info = [t('available &lt;delta&gt; keys: <b>@keys</b>', ['@keys' => implode(',', array_keys($entities))])];
            $this->buildKeysInfo($keys, $entity, $parent_key . '.&lt;delta&gt;' . '.' . $target_type, $level, $additional_info);
          }
          else {
            $this->buildKeysInfo($keys, $entity, $parent_key . '.' . $delta . '.' . $target_type, $level, $additional_info);
          }
        }
      }
    }
    elseif ($data instanceof EntityReferenceItem) {
      $target_type = $data->getFieldDefinition()->getSetting('target_type');
      $item_value = $data->getValue();
      if (isset($item_value['target_id']) || isset($item_value['entity'])) {
        $entity = !empty($item_value['entity']) ? $item_value['entity'] : \Drupal::entityTypeManager()->getStorage($target_type)->load($item_value['target_id']);
        if ($entity) {
          $this->buildKeysInfo($keys, $entity, $parent_key . '.' . $target_type, $level);
        }
      }
    }
    if ($data instanceof ListInterface) {
      $count = $data->count();
      $cardinality = $data instanceof FieldItemListInterface ? $data->getFieldDefinition()->getFieldStorageDefinition()->getCardinality() : $count;
      $name = $data->getName();
      foreach ($data as $delta => $item) {
        $additional_info = [];
        if ($cardinality !== 1) {
          $additional_info = [
            t('number of items within <em>@name</em>: <b>@num</b>', [
              '@num' => $count,
              '@name' => $name,
            ]),
          ];
        }
        $this->buildKeysInfo($keys, $item, $parent_key, $level, $additional_info);
        if ($cardinality !== 1) {
          $additional_info = [];
          if ($count > 1) {
            $additional_info = [t('available <b>&lt;delta&gt;</b> keys: <b>@keys</b>', ['@keys' => implode(',', array_keys($data->getValue()))])];
            $this->buildKeysInfo($keys, $item, $parent_key . '.&lt;delta&gt;', $level, $additional_info);
          }
          else {
            $this->buildKeysInfo($keys, $item, $parent_key . '.' . $delta, $level, $additional_info);
          }
        }
        return;
      }
    }

    $is_iterable = is_iterable($data);
    if (!$is_iterable) {
      $data = [$parent_key => $data];
    }
    foreach ($data as $key => $value) {
      $key = $is_iterable ? implode('.', [$parent_key, $key]) : $key;
      if ($value instanceof IterableMarkup) {
        // Exclude "background noise" that comes from our beloved
        // MustacheTokenProcessor, which generates a stringable data tree.
        continue;
      }
      if (($value instanceof AccessibleInterface) && !$value->access('view')) {
        continue;
      }
      if ($value instanceof FieldableEntityInterface) {
        // From here on, work with the entity wrapped as typed data object.
        $value = $value->getTypedData();
      }
      if ($value instanceof TypedDataInterface) {
        $data_definition = $value->getDataDefinition();
        if (!($data_desc = (string) $data_definition->getLabel())) {
          $data_type = explode(':', $data_definition->getDataType());
          $data_desc = end($data_type);
        }
        $data_desc = !empty($data_desc) ? [$data_desc] : [];

        $keys[$key] = '<b>{{' . $key . '}}</b> <em>' . implode(', ', $data_desc) . '</em>';
      }
      elseif ($value instanceof ConfigEntityInterface) {
        // Some config objects don't give any insight about their properties,
        // thus work with the array representation from here instead.
        $keys[$key] = '<b>{{' . $key . '}}</b> <em>' . $value->getEntityType()->getLabel() . ' (' . t('not a scalar value') . ')</em>';
        $value = $value->toArray();
      }
      elseif ($value instanceof LazyMagic || $value instanceof MustacheMagicInterface) {
        $plugin = $value instanceof LazyMagic ? $value->plugin() : $value;
        $definition = $plugin->getPluginDefinition();
        $lambda = !empty($definition['lambda']) ? '#' : '';
        $keys[$key] = '<b>{{' . $lambda . $key . '}}</b> <em>' . $definition['label'] . ': ' . $definition['description'] . '</em>';
      }
      elseif (is_object($value)) {
        if (!method_exists($value, '__toString')) {
          $keys[$key] = '<b>{{' . $key . '}}</b> <em>' . gettype($value) . ' (' . t('not a scalar value') . ')</em>';
        }
        else {
          $keys[$key] = '<b>{{' . $key . '}}</b> <em>' . gettype($value) . '</em>';
        }
        $value_array = [];
        foreach (get_class_methods($value) as $method) {
          $reflection = new \ReflectionMethod($value, $method);
          if (!$reflection->getNumberOfRequiredParameters()) {
            $value_array[$method] = $value->$method();
          }
        }
        $value_array += array_merge(get_class_vars(get_class($value)), get_object_vars($value));
        $value = $value_array;
      }
      else {
        $keys[$key] = '<b>{{' . $key . '}}</b> <em>' . gettype($value) . '</em>';
      }
      if (!empty($additional_info)) {
        $keys[$key] .= ', ' . implode(', ', $additional_info);
      }
      if (is_iterable($value) || $value instanceof TypedDataInterface) {
        $this->buildKeysInfo($keys, $value, $key, $level, $additional_info);
      }
    }
  }

}

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

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