Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 80
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 / 49
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 13
812
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
0.00% covered (danger)
0.00%
0 / 1
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
0.00% covered (danger)
0.00%
0 / 1
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 / 8
0.00% covered (danger)
0.00%
0 / 4
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
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
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
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
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
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
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
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 / 7
0.00% covered (danger)
0.00%
0 / 9
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 / 17
0.00% covered (danger)
0.00%
0 / 48
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 / 8
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
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('Published')],
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 && $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}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

LogsPanel->build
56  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
57    $load = $builder->toArray();
58
59    if (!$load) {
60      return [];
64    $present = $load['present'];
65
66    if (!$present) {
67      return [];
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('Published')],
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) : [],
86      $save ? $this->printSaveAlert(\array_merge($load['past'], [$present], $load['future']), $save) : [],
85      $table,
85      $table,
LogsPanel->buildRow
184  private function buildRow(int $index, HistoryStep $step, ?HistoryStep $save): array {
185    $user = !empty($step->user) ? $this->entityTypeManager->getStorage('user')->load($step->user) : NULL;
185    $user = !empty($step->user) ? $this->entityTypeManager->getStorage('user')->load($step->user) : NULL;
185    $user = !empty($step->user) ? $this->entityTypeManager->getStorage('user')->load($step->user) : NULL;
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) ? '✅' : '',
191        ($save && $step->hash === $save->hash) ? '✅' : '',
191        ($save && $step->hash === $save->hash) ? '✅' : '',
191        ($save && $step->hash === $save->hash) ? '✅' : '',
191        ($save && $step->hash === $save->hash) ? '✅' : '',
192        $step->time ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->time) : NULL,
192        $step->time ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->time) : NULL,
188      'hash' => $step->hash,
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,
193        $user ? $user->getDisplayName() : NULL,
188      'hash' => $step->hash,
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;' : '',
196      'style' => ($index === 0) ? 'font-weight: bold;' : '',
196      'style' => ($index === 0) ? 'font-weight: bold;' : '',
196      'style' => ($index === 0) ? 'font-weight: bold;' : '',
LogsPanel->buildRows
154  protected function buildRows(array $past, ?HistoryStep $present, array $future, ?HistoryStep $save): array {
155    $rows = [];
156
157    foreach (\array_filter($past) as $index => $step) {
157    foreach (\array_filter($past) as $index => $step) {
157    foreach (\array_filter($past) as $index => $step) {
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) {
164    foreach (\array_filter($future) as $index => $step) {
164    foreach (\array_filter($future) as $index => $step) {
164    foreach (\array_filter($future) as $index => $step) {
165      $rows[] = $this->buildRow($index + 1, $step, $save);
166    }
167
168    return $rows;
LogsPanel->create
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;
LogsPanel->keyboardShortcuts
49      'o' => t('Show the logs'),
LogsPanel->onAttachToRoot
93  public function onAttachToRoot(string $builder_id, string $instance_id): array {
94    return $this->reloadWithGlobalData($builder_id);
LogsPanel->onAttachToSlot
100  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
101    return $this->reloadWithGlobalData($builder_id);
LogsPanel->onDelete
128  public function onDelete(string $builder_id, string $parent_id): array {
129    return $this->reloadWithGlobalData($builder_id);
LogsPanel->onHistoryChange
114  public function onHistoryChange(string $builder_id): array {
115    return $this->reloadWithGlobalData($builder_id);
LogsPanel->onMove
107  public function onMove(string $builder_id, string $instance_id): array {
108    return $this->reloadWithGlobalData($builder_id);
LogsPanel->onSave
135  public function onSave(string $builder_id): array {
136    return $this->reloadWithGlobalData($builder_id);
LogsPanel->onUpdate
121  public function onUpdate(string $builder_id, string $instance_id): array {
122    return $this->reloadWithGlobalData($builder_id);
LogsPanel->printSaveAlert
211  private function printSaveAlert(array $steps, HistoryStep $save): array {
212    $save_found = FALSE;
213
214    foreach ($steps as $step) {
214    foreach ($steps as $step) {
215      if ($step && $step->hash === $save->hash) {
215      if ($step && $step->hash === $save->hash) {
214    foreach ($steps as $step) {
215      if ($step && $step->hash === $save->hash) {
216        $save_found = TRUE;
214    foreach ($steps as $step) {
215      if ($step && $step->hash === $save->hash) {
216        $save_found = TRUE;
217
218        break;
219      }
220    }
221
222    if ($save_found) {
223      return [];
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),