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

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
59  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
60    /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $present */
61    $present = $builder->get('present')->first();
62
63    if (!$present) {
64      return [];
68    $save = $builder->get('save')->first() ?? NULL;
69    $rows = $this->buildRows($builder->get('past'), $present, $builder->get('future'), $save);
70    $table = [
71      '#theme' => 'table',
72      '#header' => [
73        ['data' => $this->t('Step')],
74        ['data' => $this->t('Published')],
75        ['data' => $this->t('Time')],
76        ['data' => $this->t('User')],
77        ['data' => $this->t('Message')],
78      ],
79      '#rows' => $rows,
80    ];
81
82    return [
83      $table,
84      $save ? $this->printSaveAlert(\array_merge($builder->get('past')->getValue(), [$present->getValue()], $builder->get('future')->getValue()), $save) : [],
84      $save ? $this->printSaveAlert(\array_merge($builder->get('past')->getValue(), [$present->getValue()], $builder->get('future')->getValue()), $save) : [],
83      $table,
83      $table,
84      $save ? $this->printSaveAlert(\array_merge($builder->get('past')->getValue(), [$present->getValue()], $builder->get('future')->getValue()), $save) : [],
85    ];
86  }
LogsPanel->buildRow
184  private function buildRow(int $index, HistoryStep $step, ?HistoryStep $save): array {
185    $user = !empty($step->getUser()) ? $this->entityTypeManager->getStorage('user')->load($step->getUser()) : NULL;
185    $user = !empty($step->getUser()) ? $this->entityTypeManager->getStorage('user')->load($step->getUser()) : NULL;
185    $user = !empty($step->getUser()) ? $this->entityTypeManager->getStorage('user')->load($step->getUser()) : NULL;
185    $user = !empty($step->getUser()) ? $this->entityTypeManager->getStorage('user')->load($step->getUser()) : NULL;
186
187    return [
188      'hash' => $step->getHash(),
189      'data' => [
190        (string) $index,
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
192        $step->getTime() ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->getTime()) : NULL,
192        $step->getTime() ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->getTime()) : NULL,
188      'hash' => $step->getHash(),
188      'hash' => $step->getHash(),
189      'data' => [
190        (string) $index,
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
192        $step->getTime() ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->getTime()) : NULL,
193        $user ? $user->getDisplayName() : NULL,
193        $user ? $user->getDisplayName() : NULL,
188      'hash' => $step->getHash(),
188      'hash' => $step->getHash(),
189      'data' => [
190        (string) $index,
191        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
192        $step->getTime() ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->getTime()) : NULL,
193        $user ? $user->getDisplayName() : NULL,
194        $step->getLog() ?? '',
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;' : '',
197    ];
198  }
LogsPanel->buildRows
152  protected function buildRows(FieldItemListInterface $past, ?HistoryStep $present, FieldItemListInterface $future, ?HistoryStep $save): array {
153    $rows = [];
154
155    foreach ($past as $index => $step) {
155    foreach ($past as $index => $step) {
155    foreach ($past as $index => $step) {
155    foreach ($past as $index => $step) {
156      /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $step */
157      $rows[] = $this->buildRow(-\count($past) + $index, $step, $save);
158    }
159
160    // Present data.
161    $rows[] = $this->buildRow(0, $present, $save);
162
163    foreach ($future as $index => $step) {
163    foreach ($future as $index => $step) {
163    foreach ($future as $index => $step) {
163    foreach ($future as $index => $step) {
164      /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $step */
165      $rows[] = $this->buildRow($index + 1, $step, $save);
166    }
167
168    return $rows;
169  }
LogsPanel->create
39  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
40    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
41    $instance->dateFormatter = $container->get('date.formatter');
42
43    return $instance;
44  }
LogsPanel->keyboardShortcuts
51      'key' => 'o',
52      'help' => t('Show the logs'),
53    ];
54  }
LogsPanel->onAttachToRoot
91  public function onAttachToRoot(string $builder_id, string $instance_id): array {
92    return $this->reloadWithGlobalData($builder_id);
93  }
LogsPanel->onAttachToSlot
98  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
99    return $this->reloadWithGlobalData($builder_id);
100  }
LogsPanel->onDelete
126  public function onDelete(string $builder_id, string $parent_id): array {
127    return $this->reloadWithGlobalData($builder_id);
128  }
LogsPanel->onHistoryChange
112  public function onHistoryChange(string $builder_id): array {
113    return $this->reloadWithGlobalData($builder_id);
114  }
LogsPanel->onMove
105  public function onMove(string $builder_id, string $instance_id): array {
106    return $this->reloadWithGlobalData($builder_id);
107  }
LogsPanel->onSave
133  public function onSave(string $builder_id): array {
134    return $this->reloadWithGlobalData($builder_id);
135  }
LogsPanel->onUpdate
119  public function onUpdate(string $builder_id, string $instance_id): array {
120    return $this->reloadWithGlobalData($builder_id);
121  }
LogsPanel->printSaveAlert
211  private function printSaveAlert(array $steps, HistoryStep $save): array {
212    foreach ($steps as $step) {
212    foreach ($steps as $step) {
213      if ($step && ($step['hash'] === $save->getHash())) {
213      if ($step && ($step['hash'] === $save->getHash())) {
213      if ($step && ($step['hash'] === $save->getHash())) {
214        return [];
212    foreach ($steps as $step) {
212    foreach ($steps as $step) {
213      if ($step && ($step['hash'] === $save->getHash())) {
214        return [];
215      }
216    }
217
218    $params = [
219      '%time' => DisplayBuilderHelpers::formatTime($this->dateFormatter, $save->getTime()),
220    ];
221
222    return [
223      '#type' => 'html_tag',
224      '#tag' => 'p',
225      '#value' => $this->t('Saved at %time but not visible in logs', $params),
226    ];
227  }