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;
}
}
