Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 345
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
DisplayBuilderHelpers
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 345
0.00% covered (danger)
0.00%
0 / 7
1332
0.00% covered (danger)
0.00%
0 / 1
 isDisplayBuilderEntityType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 findAndReplaceInArray
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 309
0.00% covered (danger)
0.00%
0 / 1
240
 findArrayReplaceSource
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 getFixtureData
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 getFixtureDataFromExtension
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 formatLog
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
 formatTime
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder;
6
7use Drupal\Component\Serialization\Yaml;
8use Drupal\Core\Datetime\DateFormatterInterface;
9use Drupal\Core\Entity\EntityTypeInterface;
10use Drupal\Core\Entity\FieldableEntityInterface;
11use Drupal\Core\Render\Markup;
12use Drupal\Core\StringTranslation\TranslatableMarkup;
13
14/**
15 * Helpers related class for Display builder.
16 */
17class DisplayBuilderHelpers {
18
19  /**
20   * Determines if a given entity type is display builder relevant or not.
21   *
22   * @param \Drupal\Core\Entity\EntityTypeInterface $entityType
23   *   The entity type.
24   *
25   * @return bool
26   *   Whether this entity type is a display builder candidate or not.
27   */
28  public static function isDisplayBuilderEntityType(EntityTypeInterface $entityType): bool {
29    return $entityType->entityClassImplements(FieldableEntityInterface::class)
30      && $entityType->hasViewBuilderClass();
31  }
32
33  /**
34   * Recursively search and replace values in a multi-dimensional array.
35   *
36   * This function traverses an array and replaces elements based on a set of
37   * search criteria. It's optimized to perform multiple replacements in a
38   * single pass.
39   *
40   * @param array &$array
41   *   The array to search and replace in (passed by reference).
42   * @param array $replacements
43   *   An array of replacement rules. Each rule is an associative array with:
44   *   - 'search': An associative array with a single key-value pair to find.
45   *               Example: ['plugin_id' => 'system_messages_block'].
46   *   - 'new_value': The value to replace the matched element with.
47   */
48  public static function findAndReplaceInArray(array &$array, array $replacements): void {
49    foreach ($array as $key => &$value) {
50      if (!\is_array($value)) {
51        continue;
52      }
53
54      foreach ($replacements as $replacement) {
55        $search = $replacement['search'];
56        $class = $replacement['new_value_class'] ?? '';
57        $newValue = [
58          'source_id' => 'token',
59          'source' => [
60            'value' => '<div class="db-background db-preview-placeholder ' . $class . '">' . $replacement['new_value_title'] . '</div>',
61          ],
62        ];
63        $searchKey = \array_key_first($search);
64        $searchValue = $search[$searchKey] ?? NULL;
65
66        $match = FALSE;
67
68        // Match "source_id" directly on the child.
69        if ($searchKey === 'source_id' && isset($value['source_id']) && $value['source_id'] === $searchValue) {
70          $match = TRUE;
71        }
72        // Match "plugin_id" either directly on the child or inside its 'source'
73        // sub-array.
74        elseif ($searchKey === 'plugin_id') {
75          if ((isset($value['plugin_id']) && $value['plugin_id'] === $searchValue)
76            || (isset($value['source']) && \is_array($value['source']) && isset($value['source']['plugin_id']) && $value['source']['plugin_id'] === $searchValue)
77          ) {
78            $match = TRUE;
79          }
80        }
81
82        if ($match) {
83          $array[$key] = $newValue;
84
85          // Item replaced, continue to next item in the main array to avoid
86          // unnecessary recursion into the new value or other replacements.
87          continue 2;
88        }
89      }
90
91      // Recurse into deeper arrays if no replacement was made at this level.
92      self::findAndReplaceInArray($value, $replacements);
93    }
94    // Break the reference to the last iterated value.
95    unset($value);
96  }
97
98  /**
99   * Multi-array search and replace parent.
100   *
101   * @param array $array
102   *   The array to search in.
103   * @param array $search
104   *   The key value to replace.
105   * @param mixed $new_value
106   *   The new value to set.
107   */
108  public static function findArrayReplaceSource(array &$array, array $search, mixed $new_value): void {
109    foreach ($array as $key => $value) {
110      if (\is_array($value) && \is_array($array[$key])) {
111        self::findArrayReplaceSource($array[$key], $search, $new_value);
112      }
113      elseif ([$key => $value] === $search) {
114        $array['source'] = $new_value;
115      }
116    }
117  }
118
119  /**
120   * Load YAML data if found in fixtures folder.
121   *
122   * @param array $filepaths
123   *   The fixture file paths.
124   * @param bool $extension
125   *   (Optional) The filepath include extension. Default FALSE.
126   *
127   * @return array
128   *   The file content.
129   */
130  public static function getFixtureData(array $filepaths, bool $extension = FALSE): array {
131    foreach ($filepaths as $filepath) {
132      if (!$extension) {
133        $filepath = $filepath . '.yml';
134      }
135
136      if (!\file_exists($filepath)) {
137        continue;
138      }
139
140      $content = \file_get_contents($filepath);
141
142      if (!$content) {
143        continue;
144      }
145
146      return Yaml::decode($content);
147    }
148
149    return [];
150  }
151
152  /**
153   * Load YAML data from fixtures folder for current theme.
154   *
155   * @param string $name
156   *   The extension name.
157   * @param string|null $fixture_id
158   *   (Optional) The fixture file name.
159   *
160   * @return array
161   *   The file content.
162   */
163  public static function getFixtureDataFromExtension(string $name, ?string $fixture_id = NULL): array {
164    $path = NULL;
165
166    try {
167      $path = \Drupal::moduleHandler()->getModule($name)->getPath();
168    }
169    // @phpcs:ignore SlevomatCodingStandard.Exceptions.RequireNonCapturingCatch.NonCapturingCatchRequired
170    catch (\Throwable $th) {
171    }
172
173    if (!$path) {
174      try {
175        $path = \Drupal::service('theme_handler')->getTheme($name)->getPath();
176      }
177      // @phpcs:ignore SlevomatCodingStandard.Exceptions.RequireNonCapturingCatch.NonCapturingCatchRequired
178      catch (\Throwable $th) {
179      }
180    }
181
182    if (!$path) {
183      return [];
184    }
185
186    $filepath = \sprintf('%s/%s/fixtures/%s.yml', DRUPAL_ROOT, $path, $fixture_id);
187
188    if (!\file_exists($filepath)) {
189      return [];
190    }
191
192    return self::getFixtureData([$filepath], TRUE);
193  }
194
195  /**
196   * Format the log.
197   *
198   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $log
199   *   The log to format.
200   *
201   * @return array
202   *   The formatted log.
203   */
204  public static function formatLog(TranslatableMarkup $log): array {
205    return ['#markup' => Markup::create($log->render())];
206  }
207
208  /**
209   * Print the date for humans.
210   *
211   * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
212   *   The date formatter service.
213   * @param int $timestamp
214   *   The timestamp integer.
215   *
216   * @return string
217   *   The formatted date.
218   */
219  public static function formatTime(DateFormatterInterface $dateFormatter, int $timestamp): string {
220    $now = \time();
221
222    // Delta based on midnight today to not include day before.
223    $midnightToday = \strtotime('today');
224    $deltaToday = $now - $midnightToday;
225
226    $deltaEvent = $now - $timestamp;
227
228    if ($deltaEvent <= $deltaToday) {
229      return $dateFormatter->format($timestamp, 'custom', 'G:i');
230    }
231
232    return $dateFormatter->format($timestamp, 'short');
233  }
234
235}