Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.85% covered (danger)
3.85%
1 / 26
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiControllerBase
3.85% covered (danger)
3.85%
1 / 26
33.33% covered (danger)
33.33%
1 / 3
38.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createEventWithEnabledIsland
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 saveSseData
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Controller;
6
7use Drupal\Component\Datetime\TimeInterface;
8use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
9use Drupal\Core\Controller\ControllerBase;
10use Drupal\Core\Render\RendererInterface;
11use Drupal\Core\TempStore\SharedTempStoreFactory;
12use Drupal\display_builder\Event\DisplayBuilderEvent;
13use Drupal\display_builder\Event\DisplayBuilderEvents;
14use Drupal\display_builder\InstanceInterface;
15use Drupal\display_builder\ProfileInterface;
16use Symfony\Component\DependencyInjection\Attribute\Autowire;
17use Symfony\Component\HttpFoundation\Session\SessionInterface;
18use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
19
20/**
21 * Returns responses for Display builder routes.
22 */
23abstract class ApiControllerBase extends ControllerBase {
24
25  public const string SSE_COLLECTION = 'display_builder_sse';
26
27  /**
28   * The list of DB events which triggers SSE refresh.
29   */
30  public const array SSE_EVENTS = [
31    DisplayBuilderEvents::ON_ATTACH_TO_ROOT,
32    DisplayBuilderEvents::ON_ATTACH_TO_SLOT,
33    DisplayBuilderEvents::ON_DELETE,
34    DisplayBuilderEvents::ON_HISTORY_CHANGE,
35    DisplayBuilderEvents::ON_MOVE,
36    DisplayBuilderEvents::ON_PRESET_SAVE,
37    DisplayBuilderEvents::ON_SAVE,
38    DisplayBuilderEvents::ON_UPDATE,
39  ];
40
41  /**
42   * The Display Builder instance triggering the action.
43   */
44  protected InstanceInterface $builder;
45
46  /**
47   * Plugin ID of the island triggering the HTMX event.
48   *
49   * If not NULL, the island will be skipped from the event dispatch. Useful to
50   * avoid swapping the content of an island which is already in the expected
51   * state. For examples, if we move an instance in Builder, Layers or Tree
52   * panels, if we change the settings in InstanceForm.
53   *
54   * @see \Drupal\display_builder\Event\DisplayBuilderEventsSubscriber::dispatchToIslands()
55   * @see \Drupal\display_builder\HtmxEvents
56   */
57  protected ?string $islandId = NULL;
58
59  /**
60   * The lazy loaded display builder.
61   */
62  protected ?ProfileInterface $displayBuilder = NULL;
63
64  public function __construct(
65    protected EventDispatcherInterface $eventDispatcher,
66    protected MemoryCacheInterface $memoryCache,
67    protected RendererInterface $renderer,
68    protected TimeInterface $time,
69    #[Autowire(service: 'tempstore.shared')] protected SharedTempStoreFactory $sharedTempStoreFactory,
70    protected SessionInterface $session,
71  ) {}
72
73  /**
74   * Creates a display builder event with enabled islands only.
75   *
76   * Use a cache to avoid loading all the builder configuration.
77   *
78   * @param string $event_id
79   *   The event ID.
80   * @param array|null $data
81   *   The data.
82   * @param string|null $node_id
83   *   Optional Instance entity ID.
84   * @param string|null $parent_id
85   *   Optional parent ID.
86   *
87   * @return \Drupal\display_builder\Event\DisplayBuilderEvent
88   *   The event.
89   */
90  protected function createEventWithEnabledIsland($event_id, $data, $node_id, $parent_id): DisplayBuilderEvent {
91    $builder_id = (string) $this->builder->id();
92    $key = \sprintf('db_%s_island_enable', $builder_id);
93    $island_configuration_key = \sprintf('db_%s_island_configuration', $builder_id);
94    $island_enabled = $this->memoryCache->get($key);
95    $island_configuration = $this->memoryCache->get($island_configuration_key);
96
97    if ($island_configuration === FALSE) {
98      $island_configuration = $this->builder->getProfile()->getIslandConfigurations();
99      $this->memoryCache->set($island_configuration_key, $island_configuration);
100    }
101    else {
102      $island_configuration = $island_configuration->data;
103    }
104
105    if ($island_enabled === FALSE) {
106      $island_enabled = $this->builder->getProfile()->getEnabledIslands();
107      $this->memoryCache->set($key, $island_enabled);
108    }
109    else {
110      $island_enabled = $island_enabled->data;
111    }
112
113    $event = new DisplayBuilderEvent($builder_id, $island_enabled, $island_configuration, $data, $node_id, $parent_id, $this->islandId);
114    $this->eventDispatcher->dispatch($event, $event_id);
115
116    return $event;
117  }
118
119  /**
120   * Save data for SSE.
121   *
122   * @param string $event_id
123   *   The event ID.
124   */
125  protected function saveSseData(string $event_id): void {
126    if (!\in_array($event_id, $this::SSE_EVENTS, TRUE)) {
127      return;
128    }
129
130    $state = [
131      'sessionId' => $this->session->getId(),
132      'timestamp' => $this->time->getRequestTime(),
133      // instanceId here is the ID of a display_builder_instance entity.
134      'instanceId' => (string) $this->builder->id(),
135    ];
136    $collection = $this->sharedTempStoreFactory->get($this::SSE_COLLECTION);
137    $collection->set(\sprintf('%s_latest', (string) $this->builder->id()), $state);
138  }
139
140}