mustache_templates-8.x-1.0-beta4/src/Element/Mustache.php

src/Element/Mustache.php
<?php

namespace Drupal\mustache\Element;

use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element\RenderElement;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\mustache\Event\BeforeTemplateRenderEvent;
use Drupal\mustache\Exception\MustacheException;
use Drupal\mustache\Exception\MustacheTemplateRenderPreventedException;
use Drupal\mustache\MustacheEvents;
use Drupal\mustache\MustachePhpLoader;
use Drupal\mustache\Render\IterableMarkup;
use Drupal\mustache\Render\Markup;

/**
 * Provides an element for rendering Mustache templates.
 *
 * @RenderElement("mustache")
 */
class Mustache extends RenderElement {

  /**
   * Known templates that are enabled or not enabled for DOM synchronization.
   *
   * @var array
   */
  public static $syncs = [];

  /**
   * Known templates that are not supposed to be included as partials.
   *
   * @var array
   */
  public static $excludePartials = [];

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#theme' => 'mustache',
      '#pre_render' => [
        [$class, 'generateContentMarkup'],
      ],
    ];
  }

  /**
   * Generates content markup for a Mustache template.
   *
   * @param array $element
   *   The element to render.
   *
   * @return array
   *   The element to render.
   *
   * @throws \Exception
   *   In case something went wrong on placeholder rendering,
   *   or when the template or data url could not be found.
   */
  public static function generateContentMarkup(array $element) {
    if (!isset($element['#cache'])) {
      // Caching will be bubbled up if necessary.
      $element['#cache'] = [];
    }

    /** @var \Drupal\mustache\MustacheTemplates $templates */
    $templates = \Drupal::service('mustache.templates');
    /** @var \Drupal\mustache\Summable\SummableScriptsInterface $summables */
    $summables = \Drupal::service('mustache.summables');

    // Load and encode the template content.
    $template_name = $element['#template'];
    if (isset($element['#inline']) && ($element['#inline'] !== FALSE)) {
      $template_content = $element['#inline'];
      $element_defaults = [];
      $element['#summable'] = FALSE;
      $use_inline_cache = TRUE;
    }
    else {
      $template_content = $templates->getContent($template_name);
      $element_defaults = $templates->getElementDefaults($template_name);
      $element['#inline'] = FALSE;
      $use_inline_cache = FALSE;
    }

    // Set default element values.
    $sync_defaults = isset($element_defaults['#sync']) ? $element_defaults['#sync'] : [];
    unset($element_defaults['#sync']);
    $element += $element_defaults;

    static::processTokens($element, $template_content);
    $template_encoded = trim(substr(json_encode($template_content, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), 1, -1));

    // Attach any retrieved information about the template.
    $element['#template'] = [
      'name' => $template_name,
      'content' => $template_content,
      'encoded' => $template_encoded,
      'summable' => isset($element['#summable']) ? (bool) $element['#summable'] : $summables->isEnabled(),
    ];

    if (!isset($element['#partials'])) {
      $element['#partials'] = [];
    }
    if (!isset($element['#inline_partials'])) {
      $element['#inline_partials'] = [];
    }
    $skip_partials = empty($element['#partials']) && empty($element['#inline_partials']) && !empty($element['#skip_partials']);
    $element['#skip_partials'] = $skip_partials;
    if ($skip_partials) {
      static::$excludePartials[$template_name] = TRUE;
    }

    // Whether to use a placeholder instead of rendering with Mustache.php.
    $use_placeholder = !empty($element['#placeholder']);
    // Whether exposure of server-side data is allowed.
    $expose_data = !empty($element['#expose']);

    // Begin to determine the data to handle with.
    $data = isset($element['#data']) ? $element['#data'] : [];
    $select = isset($element['#select']) ? $element['#select'] : NULL;
    if (is_string($select)) {
      $select = [$select];
    }

    // Obtain the Json data url, if given.
    if ($url = static::getUrlFromParam($data)) {
      $data = NULL;
    }

    // Give a last chance for still being able to use data directly instead of
    // fetching from the Json data url, if given.
    if (isset($element['#override_data'])) {
      $data = $element['#override_data'];
    }

    // Prepare form values, if given.
    if (!isset($element['#form'])) {
      $element['#form'] = [];
    }
    if (!empty($element['#form'])) {
      if ($element['#form'] instanceof FormStateInterface) {
        $form_state = $element['#form'];
        $element['#form'] = [];
        $form_array = &$form_state->getCompleteForm();
        if (!isset($form_array['#id'])) {
          $form_id = $form_state->getFormObject() ? $form_state->getFormObject()->getFormId() : Crypt::randomBytesBase64(4);
          $form_array['#id'] = Html::getUniqueId($form_id);
          $form_array['#attributes']['data-drupal-selector'] = Html::getId($form_id);
        }
        if (isset($form_array['#attributes']['data-drupal-selector'])) {
          $element['#form']['selector'] = '[data-drupal-selector="' . $form_array['#attributes']['data-drupal-selector'] . '"]';
        }
        $element['#form']['values'] = $form_state->getValues();
        unset($element['#form']['values']['form_build_id']);
      }
      elseif (!is_array($element['#form'])) {
        if (is_string($element['#form'])) {
          $element['#form'] = ['selector' => $element['#form']];
        }
        else {
          // A last attempt to get an array. If it fails, it should fail hard.
          $element['#form'] = (array) $element['#form'];
        }
      }
      if (isset($url) && !empty($element['#form']['values'])) {
        $url = static::rebuildUrl($url, $element['#form']['values']);
      }
      elseif (!isset($element['#form']['values'])) {
        $element['#form']['values'] = [];
      }
    }

    if (isset($url) && (!isset($data) || $element['#with_tokens']) && (!$use_placeholder || $expose_data)) {
      // We need some data, which now must be fetched from the url. We use our
      // own service here that may cache retrieved data for subsequent requests.
      /** @var \Drupal\mustache\MustacheHttp $http */
      $http = \Drupal::service('mustache.http');
      $bubbleable_metadata = new BubbleableMetadata();
      $received = $http->getData($url, $bubbleable_metadata);
      $element['#endpoint'] = $url;
      if ($received === FALSE) {
        // Something went wrong, thus make this render array uncacheable.
        $element['#cache']['max-age'] = 0;
        $element['#success'] = FALSE;
        if (!isset($data)) {
          // Return an empty element when there is nothing available.
          return static::emptyElement();
        }
      }
      $element['#success'] = TRUE;
      if ($data instanceof IterableMarkup) {
        if (empty($received) && !isset($data[0])) {
          // By setting an empty child element with a numeric key, the data
          // will be treated as non-associative array. Loops will then behave
          // like the list would be empty, and will not include any associative
          // keys, e.g. provided by Tokens.
          $data[0] = IterableMarkup::create([], NULL, $data);
        }
        foreach ($received as $r_k => $r_v) {
          $data[$r_k] = is_array($r_v) ? IterableMarkup::create($r_v, NULL, $data) : IterableMarkup::create([], $r_v, $data);
        }
      }
      elseif (is_array($data) || $data instanceof \ArrayAccess) {
        foreach ($received as $r_k => $r_v) {
          $data[$r_k] = $r_v;
        }
      }
      else {
        $data = $received;
      }
      $element_metadata = BubbleableMetadata::createFromRenderArray($element);
      $element_metadata->merge($bubbleable_metadata)->applyTo($element);
    }

    if (!empty($element['#sync'])) {
      $element['#use_sync'] = TRUE;
      static::$syncs[$template_name] = TRUE;
      $use_morph = FALSE;
      $use_form = FALSE;
      $use_object = FALSE;
      $sync_options = &$element['#sync'];
      $sync_options += $sync_defaults;

      if (!isset($sync_options['items'])) {
        $extract_keys = [
          'eval',
          'behaviors',
          'url',
          'data',
          'select',
          'increment',
          'delay',
          'period',
          'limit',
          'trigger',
          'adjacent',
          'morph',
          'form',
          'object',
        ];
        $sync_item = [];
        foreach ($extract_keys as $extract_key) {
          if (isset($sync_options[$extract_key])) {
            $sync_item[$extract_key] = $sync_options[$extract_key];
            unset($sync_options[$extract_key]);
          }
        }
        $sync_options['items'] = [$sync_item];
      }

      foreach ($sync_options['items'] as &$sync_item) {
        // Check for the Json data provider url, if given.
        $sync_url = NULL;
        if (!empty($sync_item['url'])) {
          $sync_url = static::getUrlFromParam($sync_item['url']);
        }
        elseif (!empty($sync_item['data']) && $sync_url = static::getUrlFromParam($sync_item['data'])) {
          $sync_item['data'] = $expose_data && $data ? $data : [];
        }
        elseif (isset($url)) {
          $sync_url = clone $url;
        }
        if (!isset($sync_item['data'])) {
          $sync_item['data'] = $expose_data && $data ? $data : [];
        }

        // Include nested selection, if specified.
        $sync_select = $select;
        if (!empty($sync_item['select'])) {
          $sync_select = $sync_item['select'];
          if (is_string($sync_select)) {
            $sync_select = [$sync_select];
          }
        }
        if (!empty($sync_select)) {
          $sync_item['select'] = json_encode($sync_select, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
        }

        // Include auto increment, if specified.
        if (isset($sync_item['increment']) && $sync_item['increment'] !== FALSE) {
          if (!is_array($sync_item['increment'])) {
            $sync_item['increment'] = [];
          }
          $sync_item['increment'] = json_encode($sync_item['increment']);
        }

        // Include (non-)execution of Drupal behaviors.
        if (isset($sync_item['behaviors'])) {
          $sync_item['behaviors'] = empty($sync_item['behaviors']) ? 'false' : 'true';
        }

        // Include and encode triggering elements, if specified.
        if (isset($sync_item['trigger'])) {
          foreach ($sync_item['trigger'] as &$trigger) {
            $trigger[1] = !empty($trigger[1]) && is_string($trigger[1]) ? $trigger[1] : 'load';
            $trigger[2] = !empty($trigger[2]) && is_int($trigger[2]) ? $trigger[2] : 1;
            if ($trigger[2] < 0) {
              // Always use -1 for triggering without any limits.
              $trigger[2] = -1;
            }
          }
        }
        if (!empty($sync_item['trigger'])) {
          $sync_item['trigger'] = json_encode($sync_item['trigger']);
        }

        // Validate and encode the adjacent option, if specified.
        if (!isset($sync_item['adjacent'])) {
          $sync_options['adjacent'] = NULL;
          $sync_item['adjacent'] = NULL;
        }
        elseif (!in_array($sync_item['adjacent'],
          ['beforebegin', 'afterbegin', 'beforeend', 'afterend'])) {
          throw new MustacheException(t('Invalid adjacent position :option given. See the Javascript documentation about Element.insertAdjacentHTML which position options are available.', [':option' => $sync_item['adjacent']]));
        }
        elseif (!isset($sync_options['adjacent'])) {
          $sync_options['adjacent'] = $sync_item['adjacent'];
        }
        if (!empty($sync_item['adjacent'])) {
          $sync_item['adjacent'] = json_encode($sync_item['adjacent']);
          unset($sync_item['morph']);
        }

        if (!empty($sync_item['morph'])) {
          $sync_item['morph'] = json_encode($sync_item['morph']);
          $use_morph = TRUE;
        }

        if (!isset($sync_item['form']) && !empty($element['#form'])) {
          if (isset($element['#form']['selector']) && empty($element['#form']['values'])) {
            $sync_item['form'] = $element['#form']['selector'];
          }
          elseif (isset($element['#form']['values'])) {
            $sync_item['form'] = [
              'selector' => isset($element['#form']['selector']) ? $element['#form']['selector'] : NULL,
              'el' => NULL,
              'values' => &$element['#form']['values'],
            ];
          }
        }
        if (!empty($sync_item['form'])) {
          $use_form = TRUE;
          if (isset($sync_url) && !empty($sync_item['form']['values'])) {
            $sync_url = static::rebuildUrl($sync_url, $sync_item['form']['values']);
          }
          $sync_item['form'] = json_encode($sync_item['form'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_FORCE_OBJECT);
        }
        if (!empty($sync_item['object'])) {
          $use_object = TRUE;
          $sync_item['object'] = json_encode($sync_item['object'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
        }
        if (isset($sync_item['max_age'])) {
          $sync_item['max_age'] = (int) $sync_item['max_age'];
          if ($sync_item['max_age'] < 0) {
            // Negative values do not make sense for max_age.
            unset($sync_item['max_age']);
          }
        }

        if (isset($sync_url)) {
          $sync_url->setAbsolute($sync_url->isExternal());
          $sync_item['url'] = json_encode($sync_url->toString(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_FORCE_OBJECT);
        }
        $sync_item['data'] = json_encode($sync_item['data'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_FORCE_OBJECT);
      }

      // Build and attach the structure for DOM content synchronization.
      $element_id = 'msync-' . Crypt::randomBytesBase64(4);
      $attributes = new Attribute(
        ['id' => $element_id, 'class' => ['mustache-sync', 'not-synced']]);
      $sync_options['attributes'] = $attributes;
      if (!array_key_exists('wrapper_tag', $sync_options)) {
        $sync_options['wrapper_tag'] = 'div';
      }
      if (!empty($sync_options['into'])) {
        $sync_options['wrapper_tag'] = NULL;
      }
      if (!isset($element['#attached']['library'])) {
        $element['#attached']['library'] = [];
      }
      $libraries = &$element['#attached']['library'];
      if ($use_morph && !in_array('mustache/morphdom', $libraries)) {
        $libraries[] = 'mustache/morphdom';
      }
      if ($use_form && !in_array('mustache/bind.form', $libraries)) {
        $libraries[] = 'mustache/bind.form';
      }
      if ($use_object && !in_array('mustache/bind.object', $libraries)) {
        $libraries[] = 'mustache/bind.object';
      }
      if ($element['#template']['summable']) {
        $library_name = $summables->getLibraryName($template_name);
        if (!in_array($library_name, $element['#attached']['library'])) {
          $element['#attached']['library'][] = $library_name;
        }
      }
      elseif (!in_array('mustache/sync.now', $element['#attached']['library'])) {
        $element['#attached']['library'][] = 'mustache/sync.now';
      }

      // Add further, custom defined attributes.
      if (isset($element['#attributes'])) {
        if (isset($element['#attributes']['class'])) {
          $attributes->addClass($element['#attributes']['class']);
          unset($element['#attributes']['class']);
        }
        foreach ($element['#attributes'] as $attr_name => $attr_value) {
          $attributes->setAttribute($attr_name, $attr_value);
        }
        unset($element['#attributes']);
      }

      if (!empty($element['#partials'])) {
        foreach ($element['#partials'] as $name) {
          $partial_defaults = $templates->getElementDefaults($name);
          $summable = isset($partial_defaults['#summable']) ? (bool) $partial_defaults['#summable'] : $summables->isEnabled();
          if ($summable && (empty($element_defaults['#partials']) || !in_array($name, $element_defaults['#partials']))) {
            $library_name = $summables->getLibraryName($name);
            if (!in_array($library_name, $element['#attached']['library'])) {
              array_unshift($element['#attached']['library'], $library_name);
            }
          }
          elseif (!$summable && !isset($element['#inline_partials'][$name])) {
            $element['#inline_partials'][$name] = [
              '#type' => 'mustache_template_js_inline',
              '#template' => $name,
            ];
          }
        }
      }
    }
    else {
      $element['#use_sync'] = FALSE;
      if (!isset(static::$syncs[$template_name])) {
        static::$syncs[$template_name] = FALSE;
      }
      $element['#sync'] = [];
    }

    if ($use_placeholder) {
      // Instead of rendering the content via Mustache.php,
      // render an arbitrary placeholder, by respecting its cache metadata.
      $placeholder_element = $element['#placeholder'];
      /** @var \Drupal\Core\Render\Renderer $renderer */
      $renderer = \Drupal::service('renderer');
      $rendered = $renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, &$placeholder_element) {
        return $renderer->render($placeholder_element);
      });
      $element_metadata = BubbleableMetadata::createFromRenderArray($element);
      $element_metadata
        ->merge(BubbleableMetadata::createFromRenderArray($placeholder_element))
        ->applyTo($element);
      $element['#content'] = Markup::create((string) $rendered);
    }
    else {
      // Render the content via Mustache.php.
      if (!empty($select) && !empty($data)) {
        $subset_exists = NULL;
        $data = NestedArray::getValue($data, $select, $subset_exists);
        if (!$subset_exists) {
          // Subset must be an array.
          $data = [];
        }
      }

      if (isset($element['#form']['values'])) {
        if (is_array($data)) {
          $data['form'] = &$element['#form']['values'];
        }
        elseif (is_object($data) && method_exists($data, 'offsetSetReference')) {
          $data->offsetSetReference('form', $element['#form']['values']);
        }
        else {
          $data['form'] = $element['#form']['values'];
        }
      }

      /** @var \Drupal\mustache\MustachePhpEngine $mustache */
      $mustache = \Drupal::service('mustache.engine');
      $partials_loader = $mustache->getPartialsLoader();

      if (!empty($element['#inline_partials']) && ($partials_loader instanceof \Mustache_Loader_MutableLoader)) {
        foreach ($element['#inline_partials'] as $name => $value) {
          if (is_string($value)) {
            $use_inline_cache = TRUE;
            $partials_loader->setTemplate($name, $value);
          }
          elseif (is_array($value)) {
            if (isset($value['#template'])) {
              $name = $value['#template'];
            }
            if (isset($value['#inline']) && $value['#inline'] !== FALSE) {
              $use_inline_cache = TRUE;
              $partials_loader->setTemplate($name, $value['#inline']);
            }
            elseif (!in_array($name, $element['#partials'])) {
              $element['#partials'][] = $name;
            }
          }
        }
      }

      /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
      $event_dispatcher = \Drupal::service('event_dispatcher');
      /** @var \Drupal\mustache\Magic\MagicFactory $magic_factory */
      $magic_factory = \Drupal::service('mustache.magic');

      try {
        foreach ($magic_factory->createInstances($data, $element) as $magic_key => $magic_value) {
          $data[$magic_key] = !isset($data[$magic_key]) || !is_array($data[$magic_key]) || !is_array($magic_value) ? $magic_value : NestedArray::mergeDeep($data[$magic_key], $magic_value);
        }
        if (!($data instanceof IterableMarkup) && is_array($data)) {
          // By using an iterable markup object, we enable top-level iterations
          // that will not include "empty" values by default.
          $data = IterableMarkup::create($data, NULL, NULL, ', ', FALSE, FALSE);
        }
        if ($use_inline_cache) {
          $template = $element['#inline'] === FALSE ? $mustache->loadTemplateByName($template_name) : NULL;
          foreach ($element['#partials'] as $name) {
            $mustache->loadTemplateByName($name);
          }
          $engine_cache = $mustache->getCache();
          /** @var \Drupal\mustache\MustachePhpInlineCache $inline_cache */
          $inline_cache = \Drupal::service('mustache.inline_php_cache');
          $inline_cache->setEngineCache($engine_cache);
          $mustache->setCache($inline_cache);
          if (isset($template)) {
            $before_render_event = new BeforeTemplateRenderEvent($template_name, $template_content, $template, $data);
            $event_dispatcher->dispatch($before_render_event, MustacheEvents::BEFORE_TEMPLATE_RENDER);
            $output = $template->render($data);
          }
          else {
            $loader = $mustache->getLoader();
            if ($loader instanceof \Mustache_Loader_MutableLoader) {
              $loader->setTemplate($template_name, $template_content);
              $template = $mustache->loadTemplateByName($template_name);
              $before_render_event = new BeforeTemplateRenderEvent($template_name, $template_content, $template, $data);
              $event_dispatcher->dispatch($before_render_event, MustacheEvents::BEFORE_TEMPLATE_RENDER);
              $output = $template->render($data);
            }
            else {
              $template = $mustache->loadTemplate($template_content);
              $before_render_event = new BeforeTemplateRenderEvent($template_name, $template_content, $template, $data);
              $event_dispatcher->dispatch($before_render_event, MustacheEvents::BEFORE_TEMPLATE_RENDER);
              $output = $template->render($data);
            }
          }
          $mustache->setCache($engine_cache);
        }
        else {
          $template = $mustache->loadTemplateByName($template_name);
          $before_render_event = new BeforeTemplateRenderEvent($template_name, $template_content, $template, $data);
          $event_dispatcher->dispatch($before_render_event, MustacheEvents::BEFORE_TEMPLATE_RENDER);
          $output = $template->render($data);
        }
        // Restore possibly "escaped" curly-brackets. This may happen if a
        // component wants to embed something with curly brackets, for example
        // an inline template containing Mustache variables. That component
        // might want to skip any rendering of Mustache.php though, and could
        // accomplish that by escaping the curly brackets with a backslash.
        $element['#content'] = Markup::create(str_replace('\{\{', '{{', $output));
      }
      catch (MustacheTemplateRenderPreventedException $e) {
        if ($message = $e->getMessage()) {
          \Drupal::service('logger.channel.mustache')->notice(t('A Mustache template was prevented from being rendered. Reason: @message', [
            '@message' => $message,
          ]));
        }
        if (!empty($element['#content'])) {
          return $element;
        }
        return static::emptyElement();
      }
      catch (\Exception $e) {
        if ($e instanceof \Mustache_Exception) {
          $time = (new \DateTime())->format('Y-m-d H:i:s e');
          $error_message = t('An error occurred when trying to evaluate a Mustache template. The error was: @message. Time: @time. Affected template: @template.', [
            '@message' => $e->getMessage(),
            '@time' => $time,
            '@template' => $template_name,
          ]);
          \Drupal::service('logger.channel.mustache')->error($error_message);
          if (\Drupal::requestStack() && $request = \Drupal::requestStack()->getCurrentRequest()) {
            if (\Drupal::currentUser()->hasPermission('view mustache debug messages')) {
              \Drupal::messenger()->addError($error_message);
            }
            elseif ($request->isMethod('POST')) {
              \Drupal::messenger()->addError(t('A Mustache template contains errors, please see the "mustache" logs for more details (@time).', ['@time' => $time]));
            }
          }
          return static::emptyElement();
        }
        else {
          throw $e;
        }
      }

      if ($element['#use_sync'] && !$skip_partials && ($partials_loader instanceof MustachePhpLoader)) {
        // In general, partials should be defined within 'default' values via
        // hook_mustache_templates(). This section is a last resort to extract
        // used partials during server-side rendering, which might not be known
        // from the info array yet. It should be noted that partials can be
        // loaded at runtime, and as a consequence this section is not
        // considered to include every possible partial, as it might be that a
        // partial is not included (e.g. when a certain condition was not met
        // for including a certain partial).
        foreach ($partials_loader->getLoadedRegistryTemplateNames() as $name) {
          if (($name === $template_name) || (isset(static::$syncs[$name]) && !static::$syncs[$name]) || !empty(static::$excludePartials[$name]) || in_array($name, $element['#partials'])) {
            continue;
          }
          $partial_defaults = $templates->getElementDefaults($name);
          $summable = isset($partial_defaults['#summable']) ? (bool) $partial_defaults['#summable'] : $summables->isEnabled();
          if ($summable) {
            $library_name = $summables->getLibraryName($name);
            if (!in_array($library_name, $element['#attached']['library'])) {
              array_unshift($element['#attached']['library'], $library_name);
            }
          }
          elseif (!isset($element['#inline_partials'][$name])) {
            $element['#inline_partials'][$name] = [
              '#type' => 'mustache_template_js_inline',
              '#template' => $name,
            ];
          }
        }
        foreach ($partials_loader->getLoadedNotRegistryTemplateNames() as $name) {
          if (isset($element['#inline_partials'][$name]) || (isset(static::$syncs[$name]) && !static::$syncs[$name]) || !empty(static::$excludePartials[$name])) {
            continue;
          }
          // On-the fly generated templates will only be loaded inline,
          // because it is not known if they may vary.
          $element['#inline_partials'][$name] = [
            '#type' => 'mustache_template_js_inline',
            '#template' => $name,
            '#inline' => $partials_loader->load($name),
          ];
        }
        $partials_loader->resetLoadedRegistryTemplateList();
        $partials_loader->resetLoadedNotRegistryTemplateList();
      }
    }

    foreach ($element['#inline_partials'] as $name => $value) {
      if (($name === $template_name) && (!$element['#summable'] || $element['#inline'] !== FALSE)) {
        // The scoped template will already be added as inline Javascript,
        // thus no need to include it also as partial.
        unset($element['#inline_partials'][$name]);
        continue;
      }
      // Server-side rendering is already done at this point, which means
      // that inline templates would be now used at client-side only (if
      // enabled). For that, the template content is being wrapped as inline
      // Javascript from here.
      if (is_string($value)) {
        $element['#inline_partials'][$name] = [
          '#type' => 'mustache_template_js_inline',
          '#template' => $name,
          '#inline' => $value,
        ];
      }
    }

    return $element;
  }

  /**
   * Returns an empty element which is not cacheable.
   *
   * This element would only be used at unusual circumstances.
   * Because it's not what the result should look like,
   * the caching is completely disabled for being able
   * to generate an expected result as soon as possible.
   *
   * @return array
   *   The empty element.
   */
  public static function emptyElement() {
    return [
      '#cache' => ['max-age' => 0],
      '#printed' => TRUE,
      '#markup' => Markup::create(''),
    ];
  }

  /**
   * Helper function to get the url from the given parameter.
   *
   * @param mixed $param
   *   The given parameter.
   *
   * @return \Drupal\Core\Url|null
   *   The url object, if found.
   */
  public static function getUrlFromParam($param) {
    if ($param instanceof Url) {
      return static::rebuildUrl($param);
    }
    elseif (is_string($param)) {
      try {
        return static::rebuildUrl(Url::fromUri($param));
      }
      catch (\InvalidArgumentException $e) {
        try {
          return static::rebuildUrl(Url::fromUserInput($param));
        }
        catch (\InvalidArgumentException $e) {
          return NULL;
        }
      }
    }
    return NULL;
  }

  /**
   * Processes tokens, if any, for the given template content.
   *
   * @param array &$element
   *   The current element build array.
   * @param string &$template_content
   *   The current template content.
   */
  public static function processTokens(array &$element, &$template_content) {
    if (!isset($element['#with_tokens']) || $element['#with_tokens'] === FALSE) {
      // Tokens are not enabled for this build, so abort.
      $element['#with_tokens'] = FALSE;
      return;
    }
    /** @var \Drupal\mustache\MustacheTokenProcessor $token_processor */
    $token_processor = \Drupal::service('mustache.token_processor');
    $token_processor->processTokens($element, $template_content);
  }

  /**
   * Helper function to rebuild the url with the given query parameters.
   *
   * @param \Drupal\Core\Url $url
   *   The url to rebuild.
   * @param array $new_query_params
   *   (Optional) Unescaped query params to use for rebuilding.
   *
   * @return \Drupal\Core\Url
   *   The rebuilt url object.
   */
  public static function rebuildUrl(Url $url, array $new_query_params = []) {
    if (!($query_params = $url->getOption('query'))) {
      $query_params = [];
    }
    $query_params = array_merge($query_params, $new_query_params);
    // Sort the query param keys, so that URLs using the same parameter
    // values are guaranteed to be recognized as the same URL. This would
    // then recognize same URLs despite of having a different param order.
    ksort($query_params, SORT_NATURAL);
    if ($query_params != $url->getOption('query')) {
      $url = clone $url;
      $url->setOption('query', $query_params);
    }
    return $url;
  }

}

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

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