Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
LogsPanel
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 7
420
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
 onPublish
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 / 9
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\StringTranslation\TranslatableMarkup;
9use Drupal\display_builder\Attribute\Island;
10use Drupal\display_builder\DisplayBuilderHelpers;
11use Drupal\display_builder\InstanceInterface;
12use Drupal\display_builder\Island\IslandPluginBase;
13use Drupal\display_builder\Island\IslandReloadEventsTrait;
14use Drupal\display_builder\Island\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  use IslandReloadEventsTrait;
32
33  /**
34   * The date formatter.
35   */
36  protected DateFormatterInterface $dateFormatter;
37
38  /**
39   * {@inheritdoc}
40   */
41  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
42    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
43    $instance->dateFormatter = $container->get('date.formatter');
44
45    return $instance;
46  }
47
48  /**
49   * {@inheritdoc}
50   */
51  public static function keyboardShortcuts(): array {
52    return [
53      'key' => 'o',
54      'help' => t('Show the logs'),
55    ];
56  }
57
58  /**
59   * {@inheritdoc}
60   */
61  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
62    /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $present */
63    $present = $builder->get('present')->first();
64
65    if (!$present) {
66      return [];
67    }
68
69    /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $save */
70    $save = $builder->get('save')->first() ?? NULL;
71    $rows = $this->buildRows($builder);
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($builder->get('past')->getValue(), [$present->getValue()], $builder->get('future')->getValue()), $save) : [],
87    ];
88  }
89
90  /**
91   * {@inheritdoc}
92   */
93  public function onPublish(InstanceInterface $instance): array {
94    return $this->reloadWithGlobalData($instance);
95  }
96
97  /**
98   * Build rows for the logs table.
99   *
100   * @param \Drupal\display_builder\InstanceInterface $builder
101   *   A step with time and log message.
102   *
103   * @return array
104   *   A renderable array representing a table row.
105   */
106  protected function buildRows(InstanceInterface $builder): array {
107    $rows = [];
108    /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $save */
109    $save = $builder->get('save')->first();
110    $past = $builder->getPast();
111
112    foreach ($past as $index => $step) {
113      /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $step */
114      $rows[] = $this->buildRow(-\count($past) + $index, $step, $save);
115    }
116
117    // Present data.
118    $rows[] = $this->buildRow(0, $builder->getCurrent(), $save);
119
120    foreach ($builder->getFuture() as $index => $step) {
121      /** @var \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $step */
122      $rows[] = $this->buildRow($index + 1, $step, $save);
123    }
124
125    return $rows;
126  }
127
128  /**
129   * Build a single row for the logs table.
130   *
131   * @param int $index
132   *   The row index.
133   * @param \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $step
134   *   The step data containing time and log message.
135   * @param \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $save
136   *   Saved state.
137   *
138   * @return array
139   *   A renderable array representing a table row.
140   */
141  private function buildRow(int $index, HistoryStep $step, ?HistoryStep $save): array {
142    $user = !empty($step->getUser()) ? $this->entityTypeManager->getStorage('user')->load($step->getUser()) : NULL;
143
144    return [
145      'hash' => $step->getHash(),
146      'data' => [
147        (string) $index,
148        ($save && $step->getHash() === $save->getHash()) ? '✅' : '',
149        $step->getTime() ? DisplayBuilderHelpers::formatTime($this->dateFormatter, $step->getTime()) : NULL,
150        $user ? $user->getDisplayName() : NULL,
151        $step->getLog() ?? '',
152      ],
153      'style' => ($index === 0) ? 'font-weight: bold;' : '',
154    ];
155  }
156
157  /**
158   * Print an alert if the saved step is not in the history.
159   *
160   * @param array $steps
161   *   All steps: past, present and future.
162   * @param \Drupal\display_builder\Plugin\Field\FieldType\HistoryStep $save
163   *   Saved state.
164   *
165   * @return array
166   *   A renderable array.
167   */
168  private function printSaveAlert(array $steps, HistoryStep $save): array {
169    foreach ($steps as $step) {
170      if ($step && ($step['hash'] === $save->getHash())) {
171        return [];
172      }
173    }
174
175    $params = [
176      '%time' => DisplayBuilderHelpers::formatTime($this->dateFormatter, $save->getTime()),
177    ];
178
179    return [
180      '#type' => 'html_tag',
181      '#tag' => 'p',
182      '#value' => $this->t('Saved at %time but not visible in logs', $params),
183    ];
184  }
185
186}