custom_elements-8.x-2.x-dev/src/CustomElement.php
src/CustomElement.php
<?php
namespace Drupal\custom_elements;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\DeprecationHelper;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Markup;
/**
* Custom element data model.
*/
class CustomElement implements RefinableCacheableDependencyInterface {
use RefinableCacheableDependencyTrait;
/**
* Whether Drupal's "field-" prefixes should be removed.
*
* This is a global settings and can be set early in the bootstrap, e.g. from
* settings.php.
*
* @var bool
*/
public static $removeFieldPrefix = TRUE;
/**
* List of no-end tags.
*
* @var array
*/
protected static $noEndTags = [
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr',
];
/**
* Custom element tag prefix.
*
* Used for prefixing a bunch of custom elements the same way.
*
* @var string
*/
protected $tagPrefix = '';
/**
* Custom element tag name.
*
* @var string
*/
protected $tag = 'div';
/**
* List of other attributes.
*
* @var array
*/
protected $attributes = [];
/**
* Array of slots, represented as array of nested custom elements.
*
* @var array[][]
* Array of slots, keyed slot name and entry index. Each entry is an array
* with the following keys:
* - key: Slot name
* - weight: Slot weight
* - content: Slot markup (MarkupInterface) or element (CustomElement)
* object.
*/
protected $slots = [];
/**
* Array of normalization styles.
*
* @var array[][]
* Array keyed by slot name and normalization style constant, with the value
* indicating whether it's enabled.
*
* @see self::NORMALIZE_AS_SINGLE_VALUE
*/
protected $slotNormalizationStyles = [];
/**
* Normalize assuming the slot is single-valued.
*/
const NORMALIZE_AS_SINGLE_VALUE = 'single';
/**
* Creates a new custom element.
*
* @param string $tag
* The element tag name.
*
* @return static
* The created custom element.
*/
public static function create($tag = 'div') {
// @phpstan-ignore-next-line
$element = new static();
$element->setTag($tag);
return $element;
}
/**
* Creates a custom element from the given render array.
*
* If no custom-element is pre-existing in the render array, the render
* array will be rendered and wrapped in a <drupal-markup> element.
*
* @param array $render
* The render array.
*
* @return \Drupal\custom_elements\CustomElement|mixed
* Returns the custom element data model.
*/
public static function createFromRenderArray(array $render) {
if (isset($render['#custom_element'])) {
$element = $render['#custom_element'];
if (isset($render['#cache'])) {
$element->addCacheableDependency(BubbleableMetadata::createFromRenderArray($render));
}
}
// If no custom-element is pre-existing, wrap the result in a drupal-markup
// tag.
else {
$element = static::create('drupal-markup');
$element->setSlotFromRenderArray('default', $render);
}
return $element;
}
/**
* Sets the slots of the element.
*
* @param array[] $slots
* The slots as returned by getSlots().
*/
public function setSlots(array $slots) {
$this->slots = $slots;
}
/**
* Gets the element slots, keyed by slot name and entry index.
*
* @return array[][]
* Array of slots, keyed slot name and entry index. Each entry is an array
* with the following keys:
* - key: Slot name
* - weight: Slot weight
* - content: Slot markup (MarkupInterface) or element (CustomElement)
* object.
*/
public function getSlots() {
return $this->slots;
}
/**
* Gets a sorted and flattened list of slots.
*
* @return \Drupal\custom_elements\CustomElement|\Drupal\Core\Render\Markup[]
* A numerically indexed and sorted array of slot arrays as defined
* by ::getSlot().
*/
public function getSortedSlots() {
$slots = $this->getSortedSlotsByName();
// Flatten the array.
$slot_entries = [];
foreach ($slots as $entries) {
foreach ($entries as $slot_entry) {
$slot_entries[] = $slot_entry;
}
}
return $slot_entries;
}
/**
* Gets a sorted list of slots, keyed by slot name.
*
* @return array[]
* A sorted array of slot entries as defined by ::getSlot(), keyed by slot
* name.
*/
public function getSortedSlotsByName() {
$slots = $this->getSlots();
foreach ($slots as &$entries) {
$i = 0;
$count = count($entries);
foreach ($entries as &$entry) {
$entry['weight'] = floor($entry['weight'] * 1000) + $i / $count;
$i++;
}
usort($entries, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
}
return $slots;
}
/**
* Gets a slot entry from the given slot key and index.
*
* NOTE: This method only gets a single entry for the given slot key, not the
* complete slot! Use ::getSortedSlotsByName() to get the complete slot.
*
* @param string $key
* Name of the slot to get.
* @param int $index
* (optional) The index of the slot entry to retrieve. Defaults to 0.
*
* @return array|null
* A single slot entry array with the following keys, or NULL if not found:
* - key: Slot name
* - weight: Slot weight
* - content: Slot markup (MarkupInterface) or element (CustomElement)
* object.
*
* @see static::getSortedSlotsByName())
*/
public function getSlot($key, $index = 0) {
$key = $this->fixSlotKey($key);
return $this->slots[$key][$index] ?? NULL;
}
/**
* Sets a slot from a render array.
*
* The render-array is rendered into markup and the slot is set with this
* markup. Falling-back to adding render arrays should be avoided as much as
* possible, instead content should be rendered into nested CustomElement
* objects.
*
* @param string $key
* Name of the slot to set value for.
* @param array $build
* The render array.
* @param string $tag
* (optional) The element tag to use for the slot.
* @param array $attributes
* (optional) Attributes to add to the slot tag.
* @param int|null $index
* (optional) The index of the slot entry to set. By default, if no value is
* given it's assumed that the slot is single-valued and the index 0 gets
* set.
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*/
public function setSlotFromRenderArray($key, array $build, string $tag = 'div', array $attributes = [], ?int $index = NULL, int $weight = 0) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
// @phpstan-ignore-next-line
$renderer = \Drupal::service('renderer');
$markup = DeprecationHelper::backwardsCompatibleCall(
currentVersion: \Drupal::VERSION,
deprecatedVersion: '10.3',
currentCallable: fn() => $renderer->renderInIsolation($build),
deprecatedCallable: fn() => $renderer->renderPlain($build),
);
// Add cache metadata as needed from the cache metadata attached to the
// render array.
$this->addCacheableDependency(BubbleableMetadata::createFromRenderArray($build));
return $this->setSlot($key, $markup, $tag, $attributes, $index, $weight);
}
/**
* Sets a value for the given slot.
*
* @param string $key
* Name of the slot to set value for.
* @param \Drupal\Component\Render\MarkupInterface|string $value
* Slot value or markup.
* @param string $tag
* (optional) The element tag to use for the slot.
* @param array $attributes
* (optional) Attributes to add to the slot tag.
* @param int|null $index
* (optional) The index of the slot entry to set. By default, if no value is
* given it's assumed that the slot is single-valued and the index 0 gets
* set. For adding a slot, use ::addSlot().
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*
* @see ::addSlot()
*/
public function setSlot(string $key, $value, string $tag = 'div', array $attributes = [], ?int $index = NULL, int $weight = 0) {
if (in_array($tag, static::$noEndTags) && !empty($value)) {
throw new \LogicException(sprintf('Tag %s is no-end tag and should not have a content.', $tag));
}
// We do not support passing render arrays directly anymore.
if (is_array($value)) {
@trigger_error('Setting slot value to an array is deprecated in custom_elements:8.x-2.0 (alpha13) and is removed from custom_elements:8.x-3.0. Provide a CustomElement, MarkupInterface or a markup string. Treating content as a render array. See https://www.drupal.org/project/custom_elements/issues/3144191', E_USER_DEPRECATED);
return $this->setSlotFromRenderArray($key, $value, $tag, $attributes, $index, $weight);
}
if (!isset($index)) {
$index = 0;
$this->setSlotNormalizationStyle($key, self::NORMALIZE_AS_SINGLE_VALUE);
}
$key = $this->fixSlotKey($key);
if ($value instanceof CustomElement) {
$this->setSlotFromCustomElement($key, $value, $index, $weight);
return $this;
}
if ($value && !$value instanceof MarkupInterface) {
$value = Markup::create((string) $value);
}
// If markup and attributes are given, we need to wrap the content in
// another custom element.
if ($value && ($attributes || ($tag != 'div' && $tag != 'span'))) {
$slot = CustomElement::create($tag)
->setAttributes($attributes)
->setSlot('default', $value);
}
// If just markup is given, we can use it directly as slot.
elseif ($value) {
$slot = $value;
}
// If no value is given, simply create a nested custom element.
else {
$slot = CustomElement::create($tag)
->setAttributes($attributes);
}
$this->slots[$key][$index] = [
'weight' => $weight,
'key' => $key,
'content' => $slot,
];
return $this;
}
/**
* Removes all entries for the given slot.
*
* @param string $key
* Name of the slot to remove.
*
* @return $this
*/
public function removeSlot($key) {
$key = $this->fixSlotKey($key);
unset($this->slots[$key]);
return $this;
}
/**
* Adds a value for the given slot by appending to any pre-existing ones.
*
* @param string $key
* Name of the slot to set value for.
* @param \Drupal\Core\Render\Markup|string $value
* Slot markup.
* @param string $tag
* (optional) The tag to use for the slot.
* @param array $attributes
* (optional) Attributes to add to the slot tag.
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*
* @see ::setSlot()
*/
public function addSlot($key, $value, $tag = 'div', array $attributes = [], $weight = 0) {
return $this->setSlot($key, $value, $tag, $attributes, $this->getIndexForNewSlotEntry($key), $weight);
}
/**
* Gets the index to use for a new slot entry.
*
* @param string $key
* The slot key.
*
* @return int
* Return index for new slot entry.
*/
private function getIndexForNewSlotEntry($key) {
// Fix the slot key before using it for indexing.
$key = $this->fixSlotKey($key);
// Determine last index.
if (isset($this->slots[$key])) {
// Determine array index by appending a new one. ::setSlot() will
// overwrite the array with the right slot array afterwards.
$this->slots[$key][] = [];
end($this->slots[$key]);
return key($this->slots[$key]);
}
else {
return 0;
}
}
/**
* Sets the slot with a single custom element on a certain index.
*
* Note: This is overwriting possibly already existing slots.
*
* This method avoids a wrapper div as necessary by the helper for multiple
* elements.
*
* @param string $key
* Name of the slot to set value for.
* @param \Drupal\custom_elements\CustomElement $nestedElement
* The nested custom element.
* @param int|null $index
* (optional) The index of the slot entry to set. By default, if no value is
* given it's assumed that the slot is single-valued and the index 0 gets
* set.
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*
* @see ::addSlotFromCustomElement()
*/
public function setSlotFromCustomElement(string $key, CustomElement $nestedElement, $index = NULL, $weight = 0) {
if (!isset($index)) {
$index = 0;
$this->setSlotNormalizationStyle($key, self::NORMALIZE_AS_SINGLE_VALUE);
}
$key = $this->fixSlotKey($key);
$this->slots[$key][$index] = [
'weight' => $weight,
'key' => $key,
'content' => $nestedElement,
];
return $this;
}
/**
* Sets a slot normalization style.
*
* @param string $key
* The slot name.
* @param string $style
* Normalization style. A single value is currently supported:
* Drupal\custom_elements\CustomElement::NORMALIZE_AS_SIMPLE_VALUE.
* @param bool $enable
* Whether to enable the style (default) or disable it.
*
* @return $this
*/
public function setSlotNormalizationStyle($key, $style, $enable = TRUE) {
$key = $this->fixSlotKey($key);
$this->slotNormalizationStyles[$key][$style] = $enable;
return $this;
}
/**
* Returns whether a slot normalization style is set.
*
* @param string $key
* The slot name.
* @param string $style
* Normalization style. A single value is currently supported:
* Drupal\custom_elements\CustomElement::NORMALIZE_AS_SIMPLE_VALUE.
*
* @return bool
* Whether the style is enabled.
*/
public function hasSlotNormalizationStyle($key, $style) {
return !empty($this->slotNormalizationStyles[$key][$style]);
}
/**
* Sets the slot with a single custom element.
*
* This method avoids a wrapper div as necessary by the helper for multiple
* elements.
*
* @param string $key
* Name of the slot to set value for.
* @param \Drupal\custom_elements\CustomElement $nestedElement
* The nested custom element.
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*/
public function addSlotFromCustomElement($key, CustomElement $nestedElement, $weight = 0) {
return $this->setSlotFromCustomElement($key, $nestedElement, $this->getIndexForNewSlotEntry($key), $weight);
}
/**
* Sets the slot content from multiple nested custom elements.
*
* Note: This is overwriting possibly already existing slots.
*
* @param string $key
* Name of the slot to set value for.
* @param \Drupal\custom_elements\CustomElement[] $nestedElements
* The (ordered) array of nested custom elements.
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*
* @see ::addSlotFromNestedElements()
*/
public function setSlotFromNestedElements($key, array $nestedElements, $weight = 0) {
$key = $this->fixSlotKey($key);
$this->slots[$key] = [];
if (empty($nestedElements)) {
return $this;
}
$this->addSlotFromNestedElements($key, $nestedElements, $weight);
return $this;
}
/**
* Adds multiple nested custom elements to the given slot.
*
* @param string $key
* Name of the slot to set value for.
* @param \Drupal\custom_elements\CustomElement[] $nestedElements
* The (ordered) array of nested custom elements to append.
* @param int $weight
* (optional) A weight for ordering output slots. Defaults to 0.
*
* @return $this
*
* @see ::setSlotFromNestedElements()
*/
public function addSlotFromNestedElements($key, array $nestedElements, $weight = 0) {
$key = $this->fixSlotKey($key);
$offset = $this->getIndexForNewSlotEntry($key);
foreach (array_values($nestedElements) as $delta => $nestedElement) {
$this->slots[$key][$offset + $delta] = [
'weight' => $weight,
'key' => $key,
'content' => $nestedElement,
];
}
return $this;
}
/**
* Fix slot key and remove prefix if set via static::$removeFieldPrefix.
*
* @param string $key
* The slot key.
*
* @return string
* The fixed slot key.
*/
public static function fixSlotKey($key) {
$key = str_replace('_', '-', $key);
if (static::$removeFieldPrefix && strpos($key, 'field-') === 0) {
$key = substr($key, strlen('field-'));
}
return $key;
}
/**
* Gets html tags which don't have an end-tag.
*
* @return string[]
* List of no-end tags.
*/
public static function getNoEndTags() {
return static::$noEndTags;
}
/**
* Sets the custom element from the given element.
*
* This overwrites the current custom element with content from the given
* element.
*
* @param \Drupal\custom_elements\CustomElement $element
* The element to set values from.
*/
public function setFromCustomElement(CustomElement $element) {
$this->setTag($element->getTag());
$this->setTagPrefix($element->getTagPrefix());
$this->setAttributes($element->getAttributes());
$this->setSlots($element->getSlots());
$this->slotNormalizationStyles = $element->slotNormalizationStyles;
$this->addCacheableDependency($element);
}
/**
* Gets the custom element tag name.
*
* @return string
* Custom element tag.
*/
public function getTag() {
return $this->tag;
}
/**
* Sets the custom element tag name.
*
* @param string $tag
* The element tag name.
*
* @return $this
*/
public function setTag($tag) {
$tag = str_replace('_', '-', $tag);
$this->tag = $tag;
return $this;
}
/**
* Gets value for given attribute.
*
* @param string $key
* Name of the attribute to get value for.
*
* @return string|null
* Value for a given attribute or NULL if unset.
*/
public function getAttribute($key) {
return $this->attributes[$key] ?? NULL;
}
/**
* Sets value for the given attribute.
*
* @param string $key
* Name of the attribute to set value for.
* @param string|null $value
* Attribute value or NULL to unset the attribute.
*
* @return $this
*/
public function setAttribute($key, $value = NULL) {
$key = str_replace('_', '-', $key);
if (static::$removeFieldPrefix && strpos($key, 'field-') === 0) {
$key = substr($key, strlen('field-'));
}
if (isset($value)) {
$this->attributes[$key] = $value;
}
else {
unset($this->attributes[$key]);
}
return $this;
}
/**
* Removes an attribute.
*
* @param string $key
* Attribute key.
*
* @return $this
*/
public function removeAttribute($key) {
$key = str_replace('_', '-', $key);
if (static::$removeFieldPrefix && strpos($key, 'field-') === 0) {
$key = substr($key, strlen('field-'));
}
unset($this->attributes[$key]);
return $this;
}
/**
* Gets all attributes.
*
* @return array
* All attributes.
*/
public function getAttributes() {
return $this->attributes;
}
/**
* Sets all attributes.
*
* @param array $attributes
* The attributes.
*
* @return $this
*/
public function setAttributes(array $attributes) {
$this->attributes = $attributes;
return $this;
}
/**
* Gets the tag prefix.
*
* @return string
* Tag prefix.
*/
public function getTagPrefix() {
return $this->tagPrefix;
}
/**
* Sets the tag prefix.
*
* Note that the tag prefix is separated by a dash from the tag.
*
* @param string $tagPrefix
* The tag prefix.
*
* @return $this
*/
public function setTagPrefix($tagPrefix) {
$tagPrefix = str_replace('_', '-', $tagPrefix);
$this->tagPrefix = $tagPrefix;
return $this;
}
/**
* Returns the tag including the prefix, separated by a dash.
*
* @return string
* The tag with prefix.
*/
public function getPrefixedTag() {
return $this->tagPrefix ? $this->tagPrefix . '-' . $this->tag : $this->tag;
}
/**
* Gets a render array for rendering the custom element into markup.
*
* The returned render array always includes a '#custom_element' reference
* to this CustomElement instance, enabling other code to detect and
* intelligently handle the custom element. This applies to all render
* variants.
*
* @param string|null $render_variant
* (optional) The rendering variant to use. One of:
* - NULL: Use configured default (respects default_render_variant setting)
* - 'markup': Custom Elements Markup rendering (traditional)
* - 'preview': Use preview provider (automatic selection)
* - 'preview:<provider_id>': Use specific preview provider
* (e.g., 'preview:markup', 'preview:nuxt')
*
* @return array
* Render array for the custom element. Always contains '#custom_element'
* property with reference to this CustomElement instance.
*/
public function toRenderArray(?string $render_variant = NULL) {
if ($uses_default = $render_variant === NULL) {
// @phpstan-ignore-next-line
$render_helper = \Drupal::service('custom_elements.render_helper');
$render_variant = $render_helper->getDefaultRenderVariant();
}
if ($render_variant === 'markup') {
// Traditional markup rendering via theme system.
$build = [
'#theme' => 'custom_element',
'#custom_element' => $this,
];
}
elseif ($render_variant === 'preview') {
// Auto-select preview provider.
$build = $this->preview();
}
elseif (strpos($render_variant, 'preview:') === 0) {
// Use specific preview provider.
$provider_id = substr($render_variant, strlen('preview:'));
$build = $this->preview($provider_id);
}
else {
throw new \InvalidArgumentException(sprintf('Invalid render variant: %s', $render_variant));
}
// When variant is using the default, add cache metadata.
// Note on caching: Cache metadata is handled as follows:
// - For API requests (when lupus_ce_renderer is installed): The
// lupus_ce_renderer_content_format cache context is added at the
// response level, ensuring proper cache variation.
// - For non-API requests: The render variant is determined by config,
// so we add config cache tags here to ensure proper cache invalidation.
if ($uses_default) {
$build['#cache']['tags'][] = 'config:custom_elements.settings';
}
return $build;
}
/**
* Generates a preview render array for the custom element.
*
* Previews are rendering the custom element in Drupal-UI context, e.g. in the
* administrative interface. This could be a simple markup preview, or a full
* client-side rendered preview using a JavaScript framework.
*
* @param string|null $provider_id
* (optional) The preview provider service ID. If not provided, the provider
* will be automatically selected based on the current request.
*
* @return array
* A render array for the preview. Always returns a render array since
* the markup provider is available as a fallback.
*
* @throws \InvalidArgumentException
* If the specified provider service does not exist.
*/
public function preview(?string $provider_id = NULL): array {
// @phpstan-ignore-next-line
$resolver = \Drupal::service('custom_elements.preview_resolver');
if ($provider_id !== NULL) {
// Use specific preview provider service.
$provider = $resolver->getProviderById($provider_id);
if (!$provider) {
throw new \InvalidArgumentException(sprintf('Preview provider "%s" does not exist.', $provider_id));
}
}
else {
// Auto-select provider based on request.
// @phpstan-ignore-next-line
$provider = $resolver->getProvider();
}
$preview = $provider->preview($this);
// Attach the custom element to the preview render array to enable other
// code like \Drupal\custom_elements\CustomElementsBlockRenderHelperTrait
// to detect the custom element and intelligently handle it.
$preview['#custom_element'] = $this;
return $preview;
}
/**
* Converts the custom element to an array representation.
*
* @param bool $preserve_keys
* (optional) Whether to preserve original key casing with underscores.
* Defaults to FALSE, which converts keys to camelCase for nicer JavaScript
* usage.
* @param \Drupal\Core\Render\BubbleableMetadata|null $cache_metadata
* (optional) Object to collect cache metadata from the element and its
* nested elements. If not provided, cache metadata will not be collected.
* @param bool $explicit
* (optional) Whether to use explicit structure with separate 'props' and
* 'slots' keys. Defaults to FALSE. When TRUE, attributes are in 'props'
* and slots in 'slots'. When FALSE (implicit format), attributes are
* mixed at the root level with slots.
*
* @return array
* Array representation of the custom element.
*
* @see \Drupal\custom_elements\CustomElementNormalizer
*/
public function toArray(bool $preserve_keys = FALSE, ?BubbleableMetadata $cache_metadata = NULL, bool $explicit = FALSE): array {
/** @var \Drupal\custom_elements\CustomElementNormalizer $normalizer */
// @phpstan-ignore-next-line
$normalizer = \Drupal::service('custom_elements.normalizer');
// Build context for the normalizer.
$context = $cache_metadata ? ['cache_metadata' => $cache_metadata] : [];
$context['key_casing'] = $preserve_keys ? 'ignore' : NULL;
$context['explicit'] = $explicit;
return $normalizer->normalize($this, NULL, $context);
}
/**
* Converts the custom element to a JSON-ready array.
*
* This method uses the normalizer's configured json_format setting
* (explicit vs implicit/legacy) rather than forcing a specific format.
*
* @param bool $preserve_keys
* (optional) Whether to preserve original key casing with underscores.
* Defaults to FALSE, which converts keys to camelCase.
* @param \Drupal\Core\Render\BubbleableMetadata|null $cache_metadata
* (optional) Object to collect cache metadata.
*
* @return array
* Array representation suitable for JSON encoding.
*/
public function toJson(bool $preserve_keys = FALSE, ?BubbleableMetadata $cache_metadata = NULL): array {
/** @var \Drupal\custom_elements\CustomElementNormalizer $normalizer */
// @phpstan-ignore-next-line
$normalizer = \Drupal::service('custom_elements.normalizer');
// Build context for the normalizer.
$context = $cache_metadata ? ['cache_metadata' => $cache_metadata] : [];
$context['key_casing'] = $preserve_keys ? 'ignore' : NULL;
// Don't set explicit - let normalizer use its configured default.
return $normalizer->normalize($this, NULL, $context);
}
/**
* Converts the custom element to HTML markup.
*
* This is a shortcut for rendering the element's render array to markup-style
* output of the custom element. The configured markup style is applied.
*
* @return \Drupal\Component\Render\MarkupInterface
* HTML markup of the custom element as a MarkupInterface object.
*
* @see template_preprocess_custom_element()
*/
public function toMarkup(): MarkupInterface {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
// @phpstan-ignore-next-line
$renderer = \Drupal::service('renderer');
// Always use 'markup' variant - this method's contract is to return markup.
$render_array = $this->toRenderArray('markup');
return $renderer->renderInIsolation($render_array);
}
}
