Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
LogsPanel
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 13
756
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 keyboardShortcuts
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 build
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 onAttachToRoot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onAttachToSlot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onMove
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onHistoryChange
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onUpdate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onDelete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSave
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildRows
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 buildRow
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 printSaveAlert
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\Datetime\DateFormatterInterface;
8use Drupal\Core\StringTranslation\TranslatableMarkup;
9use Drupal\display_builder\Attribute\Island;
10use Drupal\display_builder\DisplayBuilderHelpers;
11use Drupal\display_builder\HistoryStep;
12use Drupal\display_builder\InstanceInterface;
13use Drupal\display_builder\IslandPluginBase;
14use Drupal\display_builder\IslandType;
15use Symfony\Component\DependencyInjection\ContainerInterface;
16
17/**
18 * Logs island plugin implementation.
19 */
20#[Island(
21  id: 'logs',
22  label: new TranslatableMarkup('Logs'),
23  description: new TranslatableMarkup('Logs based on changes history.'),
24  type: IslandType::View,
25  icon: 'list-columns-reverse',
26)]
27class LogsPanel extends IslandPluginBase {
28
29  /**
30   * The date formatter.
31   */
32  protected DateFormatterInterface $dateFormatter;
33
34  /**
35   * {@inheritdoc}
36   */
37  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
38    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
39    $instance->dateFormatter = $container->get('date.formatter');
40
41    return $instance;
42  }
43
44  /**
45   * {@inheritdoc}
46   */
47  public static function keyboardShortcuts(): array {
48    return [
49      'o' => t('Show the logs'),
50    ];
51  }
52
53  /**
54   * {@inheritdoc}
55   */
56  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
57    $load = $builder->toArray();
58
59    if (!$load) {
60      return [];
61    }
62
63    /** @var \Drupal\display_builder\HistoryStep $present */
64    $present = $load['present'];
65
66    if (!$present) {
67      return [];
68    }
69
70    $save = $load['save'] ?? NULL;
71    $rows = $this->buildRows($load['past'], $present, $load['future'], $save);
72    $table = [
73      '#theme' => 'table',
74      '#header' => [
75        ['data' => $this->t('Step')],
76        ['data' => $this->t('Saved')],
77        ['data' => $this->t('Time')],
78        ['data' => $this->t('User')],
79        ['data' => $this->t('Message')],
80      ],
81      '#rows' => $rows,
82    ];
83
84    return [
85      $table,
86      $save ? $this->printSaveAlert(\array_merge($load['past'], [$present], $load['future']), $save) : [],
87    ];
88  }
89
90  /**
91   * {@inheritdoc}
92   */
93  public function onAttachToRoot(string $builder_id, string $instance_id): array {
94    return $this->reloadWithGlobalData($builder_id);
95  }
96
97  /**
98   * {@inheritdoc}
99   */
100  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
101    return $this->reloadWithGlobalData($builder_id);
102  }
103
104  /**
105   * {@inheritdoc}
106   */
107  public function onMove(string $builder_id, string $instance_id): array {
108    return $this->reloadWithGlobalData($builder_id);
109  }
110
111  /**
112   * {@inheritdoc}
113   */
114  public function onHistoryChange(string $builder_id): array {
115    return $this->reloadWithGlobalData($builder_id);
116  }
117
118  /**
119   * {@inheritdoc}
120   */
121  public function onUpdate(string $builder_id, string $instance_id): array {
122    return $this->reloadWithGlobalData($builder_id);
123  }
124
125  /**
126   * {@inheritdoc}
127   */
128  public function onDelete(string $builder_id, string $parent_id): array {
129    return $this->reloadWithGlobalData($builder_id);
130  }
131
132  /**
133   * {@inheritdoc}
134   */
135  public function onSave(string $builder_id): array {
136    return $this->reloadWithGlobalData($builder_id);
137  }
138
139  /**
140   * Build rows for the logs table.
141   *
142   * @param \Drupal\display_builder\HistoryStep[] $past
143   *   Steps with time and log message.
144   * @param \Drupal\display_builder\HistoryStep $present
145   *   A step with time and log message.
146   * @param \Drupal\display_builder\HistoryStep[] $future
147   *   Steps with time and log message.
148   * @param \Drupal\display_builder\HistoryStep $save
149   *   Saved state.
150   *
151   * @return array
152   *   A renderable array representing a table row.
153   */
154  protected function buildRows(array $past, ?HistoryStep $present, array $future, ?HistoryStep $save): array {
155    $rows = [];
156
157    foreach (\array_filter($past) as $index => $step) {
158      $rows[] = $this->buildRow(-\count($past) + $index, $step, $save);
159    }
160
161    // Present data.
162    $rows[] = $this->buildRow(0, $present, $save);
163
164    foreach (\array_filter($future) as $index => $step) {
165      $rows[] = $this->buildRow($index + 1, $step, $save);
166    }
167
168    return $rows;
169  }
170
171  /**
172   * Build a single row for the logs table.
173   *
174   * @param int $index
175   *   The row index.
176   * @param \Drupal\display_builder\HistoryStep $step
177   *   The step data containing time and log message.
178   * @param \Drupal\display_builder\HistoryStep $save
179   *   Saved state.
180   *
181   * @return array
182   *   A renderable array representing a table row.
183   */
184  private function buildRow(int $index, HistoryStep $step, ?HistoryStep $save): array {
185    $user = !empty($step->user) ? $this->entityTypeManager->getStorage('user')->load($step->user) : NULL;
186
187    return [
188      'hash' => $step->hash,
189      'data' => [
190        (string) $index,
191        ($save && $step->hash === $save->hash) ? '✅' : '',
192        $step->time ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->time) : NULL,
193        $user ? $user->getDisplayName() : NULL,
194        $step->log ?? '',
195      ],
196      'style' => ($index === 0) ? 'font-weight: bold;' : '',
197    ];
198  }
199
200  /**
201   * Print an alert if the saved step is not in the history.
202   *
203   * @param \Drupal\display_builder\HistoryStep[] $steps
204   *   All steps: past, present and future.
205   * @param \Drupal\display_builder\HistoryStep $save
206   *   Saved state.
207   *
208   * @return array
209   *   A renderable array.
210   */
211  private function printSaveAlert(array $steps, HistoryStep $save): array {
212    $save_found = FALSE;
213
214    foreach ($steps as $step) {
215      if ($step->hash === $save->hash) {
216        $save_found = TRUE;
217
218        break;
219      }
220    }
221
222    if ($save_found) {
223      return [];
224    }
225    $params = [
226      '%time' => DisplayBuilderHelpers::formatTime($this->dateFormatter, $save->time),
227    ];
228
229    return [
230      '#type' => 'html_tag',
231      '#tag' => 'p',
232      '#value' => $this->t('Saved at %time but not visible in logs', $params),
233    ];
234  }
235
236}