mustache_templates-8.x-1.0-beta4/modules/mustache_magic/src/Plugin/mustache/Magic/Sync.php

modules/mustache_magic/src/Plugin/mustache/Magic/Sync.php
<?php

namespace Drupal\mustache_magic\Plugin\mustache\Magic;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Url;
use Drupal\mustache\Element\Mustache;
use Drupal\mustache\Helpers\MustacheRenderTemplate;
use Drupal\mustache\Plugin\MustacheMagic;
use Drupal\mustache\Render\IterableMarkup;
use Drupal\mustache\Render\Markup;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Enable automated DOM content synchronization by using a lambda.
 *
 * Usage:
 *
 * You can use the "magical" {{#sync.<options>}} variable to enable automated
 * DOM content synchronization. Examples:
 *
 * @code
 * One time immediate refresh: {{#sync.now}}{{node.title}}{{/sync.now}}
 * Unlimited refresh every 10 seconds: {{#sync.now.10.always}}...{{/sync.now.10.always}}
 * Up to 10 refreshes every second: {{#sync.now.1.10}}...{{/sync.now.1.10}}
 * Up to 10 refreshes every second, delayed by 5 seconds: {{#sync.5.1.10}}...{{/sync.5.1.10}}
 * Trigger refresh when clicking a DOM element having the CSS class ".button": {{#sync.trigger.button.click}}...{{/sync.trigger.button.click}}
 * @endcode
 *
 * @see https://git.drupalcode.org/project/mustache_templates/-/blob/2.0.x/README.md#36-magic-synchronization
 *
 * @MustacheMagic(
 *   id = "sync",
 *   label = @Translation("Synchronization"),
 *   description = @Translation("Use the <b>{{#sync.&lt;options&gt;}}</b> variable to enable automated DOM content synchronization. Examples: <ul><li>One time immediate refresh: {{#sync.now}}{{node.title}}{{/sync.now}}</li><li>Unlimited refresh every 10 seconds: {{#sync.10.always}}...{{/sync.10.always}}</li><li>Up to 10 refreshes every second: {{#sync.1.10}}...{{/sync.1.10}}</li><li>Up to 10 refreshes every second, delayed by 5 seconds: {{#sync.5.1.10}}...{{/sync.5.1.10}}</li><li>Trigger refresh when clicking a DOM element having the CSS class .button: {{#sync.trigger.button.click}}...{{/sync.trigger.button.click}}</li></ul>More examples can be found in the <a href='https://git.drupalcode.org/project/mustache_templates/-/blob/2.0.x/README.md#36-magic-synchronization' target='_blank' rel='noopener noreferrer'>README</a>.")
 * )
 */
class Sync extends MustacheMagic {

  /**
   * A sequence of user-defined synchronization settings.
   *
   * @var array
   */
  protected $keys = [];

  /**
   * Whether this object is a clone.
   *
   * @var bool
   */
  protected $cloned = FALSE;

  /**
   * The renderer.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The sync storage.
   *
   * @var \Drupal\mustache_magic\Storage\MustacheSyncStorage
   */
  protected $syncStorage;

  /**
   * The URL to use for server-side synchronization of user-defined templates.
   *
   * @var string
   */
  protected static $syncUrl = '/m/sync?';

  /**
   * A list of keywords to exclude when a certain scope is being handled.
   *
   * @var array
   */
  protected static $excludes = [
    'now',
    'always',
    'once',
    'span',
    'form',
    'trigger',
    'url',
    'proxy',
    'increment',
    'summable',
    'template',
    'into',
  ];

  /**
   * The client library for printing messages.
   *
   * @var string
   */
  public static $library = 'mustache_magic/magic.message';

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->renderer = $container->get('renderer');
    $instance->syncStorage = $container->get('mustache.sync_storage');
    return $instance;
  }

  /**
   * Implementation of the magic __isset() method.
   */
  public function __isset($name): bool {
    return !empty($name) || (!is_null($name) && is_scalar($name) && (trim((string) $name) !== ''));
  }

  /**
   * Implementation of the magic __get() method.
   */
  public function __get($name) {
    if (!$this->cloned) {
      $cloned = clone $this;
      $cloned->cloned = TRUE;
      return $cloned->__get($name);
    }
    $name = mb_strtolower(trim($name));
    if (ctype_digit(strval($name))) {
      $this->keys[] = (int) $name;
    }
    else {
      $this->keys[] = $name;
    }
    return $this;
  }

  /**
   * Implements the magic __invoke() method, used as higher-order section.
   */
  public function __invoke($template_content = NULL, $render = NULL) {
    if (!isset($template_content, $render)) {
      return;
    }

    if (isset(static::$library) && (empty($this->element['#attached']['library']) || !in_array(static::$library, $this->element['#attached']['library']))) {
      $this->element['#attached']['library'][] = static::$library;
    }

    $template_hash = hash('md4', $template_content);
    $element_sync = !empty($this->element['#use_sync']) && isset($this->element['#sync']['items']) ? reset($this->element['#sync']['items']) : NULL;
    $bubbleable_metadata = BubbleableMetadata::createFromRenderArray($this->element);

    $build = MustacheRenderTemplate::build($template_hash, $template_content);
    $sync_options = $build->withClientSynchronization()->morphing();
    $build_render_array = &$build->toRenderArray();

    $to_merge = [];
    $to_copys = [
      '#cache',
      '#partials',
      '#inline_partials',
    ];
    foreach ($to_copys as $to_copy) {
      if (!empty($this->element[$to_copy])) {
        $to_merge[$to_copy] = $this->element[$to_copy];
      }
    }
    if ($element_sync) {
      $to_merge['#sync'] = [];
      $to_copys = ['eval', 'behaviors'];
      foreach ($to_copys as $to_copy) {
        if (!empty($element_sync[$to_copy])) {
          $to_merge['#sync'][$to_copy] = $element_sync[$to_copy];
        }
      }
    }

    $force_proxy = FALSE;
    foreach ($this->keys as $i => $key) {
      if ($i === 0) {
        if (is_int($key) && $key > 0) {
          $sync_options->startsDelayed($key * 1000);
          continue;
        }
        elseif ($key === 'now') {
          continue;
        }
      }
      if ($key === 'always') {
        $sync_options->unlimited();
        continue;
      }
      if ($key === 'span') {
        $sync_options->withWrapperTag('span');
        continue;
      }
      if ($key === 'proxy') {
        $force_proxy = TRUE;
        continue;
      }
      if (isset($form_bind) && $form_bind === 0) {
        $form_bind = 1;
        if (!is_int($key) && !in_array($key, static::$excludes)) {
          $form_selector = $key;
          if ($form_selector !== 'form'
            && !in_array(mb_substr(
            $form_selector, 0, 1), ['#', '[', '*', ':', '.'])) {
            $form_selector = '.' . $form_selector;
          }
          continue;
        }
      }
      if ($key === 'form') {
        $form_bind = 0;
        $form_selector = 'form';
        continue;
      }
      if (isset($trigger) && $trigger < 3) {
        $trigger++;
        if ($trigger === 1) {
          $trigger_selector = $key;
          if (!in_array(mb_substr(
            $trigger_selector, 0, 1), ['#', '[', '*', ':', '.'])) {
            $trigger_selector = '.' . $trigger_selector;
          }
        }
        elseif ($trigger === 2) {
          $trigger_event = !is_int($key) && !in_array($key, static::$excludes) ? $key : 'click';
        }
        elseif ($trigger === 3) {
          if ($key == 'once') {
            $trigger_limit = 1;
          }
          elseif (is_int($key) && $key > 0) {
            $trigger_limit = $key;
          }
          else {
            $trigger_limit = -1;
          }
        }
        if (!in_array($key, static::$excludes)) {
          continue;
        }
      }
      if ($key === 'trigger' && !isset($trigger)) {
        $trigger = 0;
        $trigger_selector = '*';
        $trigger_event = 'click';
        $trigger_limit = -1;
        continue;
      }
      if (is_int($key)) {
        if (!isset($period)) {
          $period = $key * 1000;
          $sync_options->periodicallyRefreshesAt($period);
        }
        else {
          $sync_options->upToNTimes($key);
        }
        continue;
      }
      if (isset($url) && $url === '') {
        $url = str_replace('_dot_', '.', $key);
        if (substr($url, 0, 1) !== '/' && substr($url, 0, 4) !== 'http') {
          $url = '/' . $url;
        }
        continue;
      }
      if ($key === 'url') {
        $url = '';
        continue;
      }
      if (isset($increment) && $increment < 4) {
        $increment++;
        if ($increment === 1) {
          $increment_key = $key;
        }
        elseif ($increment === 2) {
          $increment_offset = $key;
        }
        elseif ($increment === 3) {
          if ($key == 'once') {
            $increment_limit = 1;
          }
          elseif (is_int($key) && $key > 0) {
            $increment_limit = $key;
          }
          else {
            $increment_limit = -1;
          }
        }
        elseif ($increment === 4) {
          $increment_step_size = $key;
        }
        if (!in_array($key, static::$excludes)) {
          continue;
        }
      }
      if ($key === 'increment' || $key === 'increments') {
        $increment = 0;
        $increment_key = 'page';
        $increment_offset = 0;
        $increment_limit = -1;
        $increment_step_size = 1;
        continue;
      }
      if ($key === 'summable' || $key === 'sumable') {
        $summable = \Drupal::service('mustache.summables')->isEnabled();
        continue;
      }
      if ($key === 'template') {
        $template_name = '';
        continue;
      }
      if (isset($template_name) && $template_name === '') {
        $template_name = !in_array($key, static::$excludes) ? $key : $template_hash;
        continue;
      }
      if ($key === 'into') {
        $into_selector = FALSE;
        continue;
      }
      if (isset($into_selector) && $into_selector === FALSE) {
        if (!in_array($key, static::$excludes)) {
          $into_selector = $key;
          if (
          !in_array(mb_substr($into_selector, 0, 1), ['#', '[', '*', ':', '.'])
          && !in_array(mb_substr($into_selector, 0, 4), ['head', 'body', 'meta'])) {
            $into_selector = '.' . $into_selector;
          }
          $sync_options->insertsInto($into_selector);
          // We assume that server-side rendering is not desired when using
          // a different element target other than the auto-generated wrapper.
          $build->withPlaceholder(['#markup' => '']);
          continue;
        }
      }
    }
    if (isset($form_bind, $form_selector)) {
      $sync_options->usingFormValues($form_selector);
    }
    if (isset($trigger)) {
      $sync_options->startsWhenElementWasTriggered($trigger_selector)
        ->atEvent($trigger_event)
        ->upToNTimes($trigger_limit);
    }
    if (isset($increment)) {
      $sync_options->increments()
        ->atParamKey($increment_key)
        ->startingAt($increment_offset)
        ->upToNTimes($increment_limit)
        ->withStepSize($increment_step_size);
    }

    if (empty($url) && $element_sync && !empty($element_sync['url'])) {
      $url = $element_sync['url'];
    }
    if (!empty($this->element['#data'])) {
      if (is_array($this->element['#data'])) {
        $data = $this->element['#data'];
      }
      elseif (empty($url) && (is_string($this->element['#data']) || $this->element['#data'] instanceof Url)) {
        $url = $this->element['#data'];
      }
    }
    if (!isset($data)) {
      if (isset($this->element['#override_data']) && is_array($this->element['#override_data'])) {
        $data = $this->element['#override_data'];
      }
      else {
        $data = [];
      }
    }
    foreach ($data as $d_k => $d_v) {
      if ($d_v instanceof IterableMarkup) {
        unset($data[$d_k]);
      }
    }

    $url_exists = !empty($url) && ($url = Mustache::getUrlFromParam($url));

    if (!$url_exists) {
      $build->withPlaceholder(['#markup' => Markup::create($render($template_content))]);
    }
    elseif (empty($data)) {
      $build->usingDataFromUrl($url);
    }
    else {
      $build->usingData($data);
      $sync_options->usingDataFromUrl($url);
    }

    if ($url_exists) {
      $url = clone $url;
      $url->setAbsolute($url->isExternal());
    }

    if (!$url_exists || $force_proxy) {
      $values = [
        'name' => $template_hash,
        'content' => $template_content,
        'merge' => $to_merge,
        'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
      ];

      $server_side_render = $build->toRenderArray();
      $server_side_render['#sync'] = [];

      if (!empty($this->element['#with_tokens'])) {
        $server_side_render['#with_tokens'] = $this->element['#with_tokens'];
        /** @var \Drupal\mustache\MustacheTokenProcessor $token_processor */
        $token_processor = \Drupal::service('mustache.token_processor');
        $tokenized = $token_processor->tokenizeTemplate($template_hash, $template_content);
        if (!empty($tokenized['tokens'])) {
          $values['token_options'] = !empty($this->element['#with_tokens']['options']) ? $this->element['#with_tokens']['options'] : [];
          $token_data = !empty($this->element['#with_tokens']['data']) ? $this->element['#with_tokens']['data'] : [];
          $token_processor->processData($token_data, $tokenized['tokens'], 'view', $bubbleable_metadata);
        }
      }

      $build->withPlaceholder($server_side_render);
      unset($build_render_array['#inline']);
      $build_render_array['#template'] = 'mustache_magic_sync';

      if ($force_proxy && $url_exists) {
        $values['url'] = $url->toString();
      }

      $datas = isset($token_data) ? [
        'data' => $data,
        'token_data' => $token_data,
      ] : ['data' => $data];
      foreach ($datas as $d_k => $d_v) {
        $values[$d_k] = [];
        foreach ($d_v as $t_key => $t_value) {
          if ($t_value instanceof EntityInterface) {
            if (!$t_value->isNew()) {
              $values[$d_k][$t_key] = [
                $t_value->getEntityTypeId(),
                $t_value->language()->getId(),
                $t_value->id(),
              ];
            }
            elseif ($uuid = $t_value->uuid()) {
              $values[$d_k][$t_key] = [$t_value->getEntityTypeId(), $uuid];
            }
          }
          elseif (is_scalar($t_value) || $t_value instanceof \JsonSerializable) {
            $values[$d_k][$t_key] = $t_value;
          }
          else {
            $values[$d_k][$t_key] = NULL;
          }
        }
      }

      $sync_hash = $this->syncStorage->generateHash($values);
      if (is_null($this->syncStorage->get($sync_hash))) {
        if ($sync_hash != $this->syncStorage->set($values)) {
          throw new \LogicException("The Mustache sync storage is behaving unexpected: Received a different hash for given values other than previously generated.");
        }
      }

      $sync_options
        ->usingDataFromUrl(static::$syncUrl . 'h=' . urlencode($sync_hash));
    }
    else {
      foreach ($to_merge as $m_key => $m_val) {
        if ($m_key === '#sync') {
          $sync_render_array = &$sync_options->toRenderArray();
          $sync_render_array = NestedArray::mergeDeep($sync_render_array, $to_merge['#sync']);
        }
        elseif (isset($build_render_array[$m_key]) && is_array($build_render_array[$m_key])) {
          $build_render_array[$m_key] = NestedArray::mergeDeep($build_render_array[$m_key], $m_val);
        }
        else {
          $build_render_array[$m_key] = $m_val;
        }
      }
    }

    if (isset($build_render_array['#inline']) && (isset($template_name) || !empty($summable))) {
      if (!isset($summable)) {
        $summable = \Drupal::service('mustache.summables')->isEnabled();
      }
      $template_name = $template_name ?? $template_hash;
      /** @var \Drupal\mustache_magic\Storage\MustacheTemplateStorage $template_storage */
      $template_storage = \Drupal::service('mustache.template_storage');
      $template_values = [
        'name' => $template_name,
        'content' => $template_content,
      ];
      if (!$summable) {
        $template_values['default']['#summable'] = FALSE;
      }
      $template_storage_hash = $template_storage->generateHash($template_values);
      $existing = $template_storage->get($template_storage_hash);
      if (is_null($existing) || ($template_storage->hashValues($existing) !== $template_storage->hashValues($template_values))) {
        if ($template_storage_hash != $template_storage->set($template_values)) {
          throw new \LogicException("The Mustache template storage is behaving unexpected: Received a different hash for given values other than previously generated.");
        }
        // Clearing all cached definitions is unfortunate, but required.
        mustache_cache_flush();
        if ($summable) {
          \Drupal::service('mustache.summables')->clearCaches();
        }
      }
      unset($build_render_array['#inline']);
      $build_render_array['#template'] = $template_name;
    }

    $renderer = $this->renderer;
    $rendered = $renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, &$build_render_array) {
      return $renderer->render($build_render_array);
    });
    $bubbleable_metadata
      ->merge(BubbleableMetadata::createFromRenderArray($this->element))
      ->merge(BubbleableMetadata::createFromRenderArray($build_render_array))
      ->applyTo($this->element);
    // We need to replace the opening curly brackets, so that Mustache.php's
    // lambda helper cannot chime in and would try to replace Mustache
    // variables within the inline template with current context values.
    return Markup::create(str_replace('{{', '\{\{', (string) $rendered));
  }

}

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

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