eca-1.0.x-dev/modules/render/src/Hook/RenderHooks.php
modules/render/src/Hook/RenderHooks.php
<?php
namespace Drupal\eca_render\Hook;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\eca\Event\RenderEventInterface;
use Drupal\eca\Event\TriggerEvent;
use Symfony\Component\Serializer\Serializer;
/**
* Implements render hooks for the ECA Render submodule.
*/
class RenderHooks {
/**
* Constructs a new RenderHooks object.
*/
public function __construct(
protected TriggerEvent $triggerEvent,
protected Serializer $serializer,
protected LoggerChannelInterface $logger,
protected EntityTypeManagerInterface $entityTypeManager,
protected EntityTypeBundleInfoInterface $entityTypeBundleInfo,
) {}
/**
* Preprocesses for eca-serialized.html.twig.
*
* @param array &$variables
* The variables consisting of following keys:
* - data: The not-serialized data.
* - format: The used serialization format (e.g. "json").
* - method: The performed method, either "serialize" or "unserialize".
* - serialized: The already serialized data as string. When not given,
* the theme function will take care of serializing the given data.
* - wrap: Boolean indicating whether the serialized string should be
* wrapped by an HTML script tag.
*/
#[Hook('preprocess_eca_serialized')]
public function preprocessEcaSerialized(array &$variables = []): void {
if (!isset($variables['wrap'])) {
$variables['wrap'] = TRUE;
}
if (!isset($variables['method'])) {
$variables['method'] = 'serialize';
}
if (isset($variables['data'], $variables['format']) && !isset($variables['serialized'])) {
$variables['serialized'] = $this->serializer->serialize($variables['data'], $variables['format']);
}
if (!empty($variables['wrap'])) {
if (!isset($variables['attributes'])) {
$variables['attributes'] = new Attribute();
}
elseif (!($variables['attributes'] instanceof Attribute)) {
$variables['attributes'] = new Attribute($variables['attributes']);
}
$attributes = $variables['attributes'];
if (!isset($attributes['id'])) {
$attributes['id'] = Html::getUniqueId('eca-serialized');
$attributes->addClass('eca-serialized');
}
}
}
/**
* Implements hook_theme().
*/
#[Hook('theme')]
public function theme(): array {
return [
'eca_serialized' => [
'variables' => [
'serialized' => '',
'method' => '',
'format' => '',
'data' => NULL,
'wrap' => TRUE,
],
],
'eca_lazy' => [
'variables' => [
'name' => '',
'argument' => NULL,
],
],
];
}
/**
* Implements hook_entity_operation().
*/
#[Hook('entity_operation')]
public function entityOperation(EntityInterface $entity): array {
$build = [];
$event = $this->triggerEvent->dispatchFromPlugin('eca_render:entity_operations', $entity, $build);
if ($event instanceof RenderEventInterface) {
$build = &$event->getRenderArray();
}
$operations = [];
// Convert link element builds into operation link arrays.
$k = 0;
foreach ($build as $i => $link) {
if ($link instanceof EntityInterface) {
if (!($link->hasLinkTemplate('canonical'))) {
unset($build[$i]);
continue;
}
$link = [
'title' => $link->label(),
'url' => $link->toUrl('canonical'),
'weight' => (++$k * 10),
];
}
$weight = $link['weight'] ?? ($link['#weight'] ?? (++$k * 10));
if (isset($link['#title'], $link['#url'])) {
$link = [
'title' => $link['#title'],
'url' => $link['#url'],
];
}
if (isset($link['url']) && is_string($link['url'])) {
try {
$link['url'] = Url::fromUserInput($link['url']);
}
catch (\Exception) {
try {
$link['url'] = Url::fromUri($link['url']);
}
catch (\Exception) {
$this->logger->error("Failed to convert to URL when building up entity operation links.");
continue;
}
}
}
$link['weight'] = $weight;
if (isset($link['url']) && ($link['url'] instanceof Url)) {
$operations[$i] = $link;
}
}
return $operations;
}
/**
* Implements hook_contextual_links_alter().
*/
#[Hook('contextual_links_alter')]
public function contextualLinksAlter(array &$links, string $group, array $route_parameters): void {
$build = [];
$event = $this->triggerEvent->dispatchFromPlugin('eca_render:contextual_links', $links, $group, $route_parameters, $build, $this->entityTypeManager);
if ($event instanceof RenderEventInterface) {
$build = &$event->getRenderArray();
}
// Convert link element builds into contextual link arrays.
$k = 0;
foreach ($build as $i => $link) {
if ($link instanceof EntityInterface) {
if (!($link->hasLinkTemplate('canonical'))) {
unset($build[$i]);
continue;
}
$url = $link->toUrl('canonical');
$link = [
'title' => $link->label(),
'route_name' => $url->getRouteName(),
'route_parameters' => $url->getRouteParameters(),
'localized_options' => $url->getOptions(),
'weight' => (++$k * 10),
];
}
$weight = $link['weight'] ?? ($link['#weight'] ?? (++$k * 10));
if (isset($link['#title'], $link['#url'])) {
$link = [
'title' => $link['#title'],
'url' => $link['#url'],
];
}
if (isset($link['url']) && is_string($link['url'])) {
try {
$link['url'] = Url::fromUserInput($link['url']);
}
catch (\Exception) {
try {
$link['url'] = Url::fromUri($link['url']);
}
catch (\Exception) {
$this->logger->error("Failed to convert to URL when building up contextual links.");
continue;
}
}
}
if (isset($link['url'], $link['title']) && ($link['url'] instanceof Url)) {
/**
* @var \Drupal\Core\Url $url
*/
$url = $link['url'];
$link = [
'title' => $link['title'],
'route_name' => $url->getRouteName(),
'route_parameters' => $url->getRouteParameters(),
'localized_options' => $url->getOptions(),
'weight' => $weight,
];
$links[$i] = $link;
}
}
}
/**
* Implements hook_link_alter().
*/
#[Hook('link_alter')]
public function linkAlter(array &$variables): void {
$this->triggerEvent->dispatchFromPlugin('eca_render:alter_link', $variables);
}
/**
* Implements hook_menu_local_tasks_alter().
*/
#[Hook('menu_local_tasks_alter')]
public function menuLocalTasksAlter(array &$data, string $route_name, RefinableCacheableDependencyInterface &$cacheability): void {
$build = [];
$event = $this->triggerEvent->dispatchFromPlugin('eca_render:local_tasks', $route_name, $data, $build);
if ($event instanceof RenderEventInterface) {
$build = &$event->getRenderArray();
}
// Convert link element builds into contextual link arrays.
$k = 0;
foreach ($build as $i => $link) {
if ($link instanceof EntityInterface) {
if (!($link->hasLinkTemplate('canonical'))) {
unset($build[$i]);
continue;
}
$url = $link->toUrl('canonical');
$link = [
'#title' => $link->label(),
'#url' => $url,
'weight' => (++$k * 10),
];
}
if (isset($link['#title'], $link['#url'])) {
$weight = $link['weight'] ?? ($link['#weight'] ?? (++$k * 10));
$data['tabs'][0]['eca.local_task.' . $i] = [
'#theme' => 'menu_local_task',
'#link' => [
'title' => $link['#title'],
'url' => is_string($link['#url']) ? Url::fromUserInput($link['#url']) : $link['#url'],
'localized_options' => [],
],
'#access' => TRUE,
'#weight' => $weight,
];
}
}
}
/**
* Implements hook_views_data_alter().
*/
#[Hook('views_data_alter')]
public function viewsDataAlter(array &$data): void {
$data['views']['eca_render'] = [
'title' => t('ECA Views field'),
'help' => t("Custom render output, build up via ECA."),
'field' => [
'id' => 'eca_render',
],
];
}
/**
* Implements hook_entity_extra_field_info().
*/
#[Hook('entity_extra_field_info')]
public function entityExtraFieldInfo(): array {
$extra = [];
/**
* @var \Drupal\eca\Entity\Eca $eca
*/
foreach ($this->entityTypeManager->getStorage('eca')->loadMultiple() as $eca) {
foreach ($eca->get('events') ?? [] as $event) {
if (($event['plugin'] ?? NULL) !== 'eca_render:extra_field') {
continue;
}
$config = $event['configuration'] ?? [];
$entity_type_ids = $config['entity_type_id'] === '' ?
array_keys($this->entityTypeManager->getDefinitions()) :
explode(',', $config['entity_type_id']);
foreach ($entity_type_ids as $entity_type_id) {
$name = trim((string) ($config['extra_field_name'] ?? ''));
$label = trim((string) ($config['extra_field_label'] ?? $name));
$description = trim((string) ($config['extra_field_description'] ?? ''));
$display_type = $config['display_type'] ?? 'display';
$weight = (int) ($config['weight'] ?? 0);
$visible = (bool) ($config['visible'] ?? FALSE);
if ($name === '') {
continue;
}
$entity_type_id = trim($entity_type_id);
if (($entity_type_id === '') || !$this->entityTypeManager->hasDefinition($entity_type_id)) {
continue;
}
$bundle = trim($config['bundle'] ?? '');
$bundles_available = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
if ($bundle === '' || $bundle === '*') {
$bundles = $bundles_available;
}
else {
$bundles = explode(',', $bundle);
$bundles = array_intersect($bundles, $bundles_available);
}
foreach ($bundles as $bundle) {
$extra[$entity_type_id][$bundle][$display_type]['eca_render__' . $name] = [
'label' => $label,
'description' => $description,
'weight' => $weight,
'visible' => $visible,
];
}
}
}
}
return $extra;
}
/**
* Implements hook_entity_view().
*/
#[Hook('entity_view')]
public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, string $view_mode): void {
$event = $this->triggerEvent->dispatchFromPlugin('eca_render:entity', $entity, $build, $display, $view_mode);
if ($event instanceof RenderEventInterface) {
$build = &$event->getRenderArray();
}
if (!($entity instanceof FieldableEntityInterface)) {
return;
}
$this->triggerExtraField($build, $entity, $display, $view_mode, 'display');
}
/**
* Implements hook_form_alter().
*/
#[Hook('form_alter')]
public function formAlter(array &$form, FormStateInterface $form_state): void {
$form_object = $form_state->getFormObject();
if ($form_object instanceof ContentEntityFormInterface) {
$entity = $form_object->getEntity();
if (!($entity instanceof FieldableEntityInterface)) {
return;
}
$display = $form_object->getFormDisplay($form_state);
$this->triggerExtraField($form, $entity, $display, $display->getMode(), 'form');
}
}
/**
* Implements hook_breakpoints_alter().
*/
#[Hook('breakpoints_alter')]
public function breakpointsAlter(array &$definitions): void {
$this->triggerEvent->dispatchFromPlugin('eca_render:breakpoints_alter', $definitions);
}
/**
* Triggers the rendering of extra fields via ECA.
*
* @param array &$build
* The render array.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
* The entity display.
* @param string $view_mode
* The view mode.
* @param string $display_type
* The display type.
*/
protected function triggerExtraField(array &$build, EntityInterface $entity, EntityDisplayInterface $display, string $view_mode, string $display_type): void {
foreach ($display->getComponents() as $name => $options) {
if (strpos($name, 'eca_render__') !== 0) {
continue;
}
$extra_field_name = substr($name, 12);
$render_build = [];
$event = $this->triggerEvent->dispatchFromPlugin('eca_render:extra_field', $entity, $extra_field_name, $options, $render_build, $display, $view_mode, $display_type);
if ($event instanceof RenderEventInterface) {
$render_build = &$event->getRenderArray();
}
$build[$name] = $render_build;
if (!empty($build[$name]) && ($display_type === 'form') && !isset($build[$name]['#type'])) {
// Wrap as container so that the extra field can be grouped.
$build[$name]['#type'] = 'container';
}
if (!isset($build['#weight']) && isset($options['weight'])) {
$build['#weight'] = $options['weight'];
}
}
}
}
