Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Scanner
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
7 / 7
29
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 scan
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 fields
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
 rows
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
7
 collect
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 items
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
6
 unique
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\References;
6
7use Cosray\Field;
8use Cosray\Richtext;
9
10/**
11 * Collects every asset and node uid referenced by stored node content:
12 * media field items (`{uid}`), Reference field targets, and the richtext
13 * carriers (`image.uid`, `link.asset`, `link.node`). The rows of Blocks
14 * and Entries fields are recursed into. Feeds the reference indexes.
15 */
16final class Scanner
17{
18    private readonly Field\Index $index;
19
20    public function __construct(?Field\Index $index = null)
21    {
22        $this->index = $index ?? Field\Index::withDefaults();
23    }
24
25    /** @return array{assets: list<string>, nodes: list<string>} */
26    public function scan(mixed $content): array
27    {
28        $assets = [];
29        $nodes = [];
30
31        if (is_array($content)) {
32            $this->fields($content, $assets, $nodes);
33            $richtext = Richtext\Scanner::scanContent($content);
34            $assets = [...$assets, ...$richtext['assets']];
35            $nodes = [...$nodes, ...$richtext['nodes']];
36        }
37
38        return ['assets' => $this->unique($assets), 'nodes' => $this->unique($nodes)];
39    }
40
41    /**
42     * @param list<string> $assets
43     * @param list<string> $nodes
44     */
45    private function fields(array $content, array &$assets, array &$nodes): void
46    {
47        foreach ($content as $field) {
48            if (!is_array($field) || !is_string($field['type'] ?? null)) {
49                continue;
50            }
51
52            $type = $this->index->resolve($field['type']);
53
54            if ($type === null) {
55                continue;
56            }
57
58            if (is_a($type, Field\Blocks::class, true) || is_a($type, Field\Entries::class, true)) {
59                $this->rows($field['value'] ?? null, $assets, $nodes);
60            } elseif (is_a($type, Field\Reference::class, true)) {
61                $this->collect($field['value'] ?? null, $nodes);
62            } elseif (is_a($type, Field\File::class, true)) {
63                $this->collect($field['value'] ?? null, $assets);
64            }
65        }
66    }
67
68    /**
69     * Typed rows carry their sub-fields under `fields`, per locale list.
70     *
71     * @param list<string> $assets
72     * @param list<string> $nodes
73     */
74    private function rows(mixed $value, array &$assets, array &$nodes): void
75    {
76        foreach (is_array($value) ? $value : [] as $rows) {
77            foreach (is_array($rows) ? $rows : [] as $row) {
78                if (!is_array($row) || !is_array($row['fields'] ?? null)) {
79                    continue;
80                }
81
82                $this->fields($row['fields'], $assets, $nodes);
83            }
84        }
85    }
86
87    /**
88     * Collect uids from a locale-keyed map of `{uid}` lists into $out.
89     *
90     * @param list<string> $out
91     */
92    private function collect(mixed $value, array &$out): void
93    {
94        foreach (is_array($value) ? $value : [] as $items) {
95            $this->items($items, $out);
96        }
97    }
98
99    /** @param list<string> $out */
100    private function items(mixed $items, array &$out): void
101    {
102        foreach (is_array($items) ? $items : [] as $item) {
103            $uid = is_array($item) ? $item['uid'] ?? null : null;
104
105            if (is_string($uid) && $uid !== '') {
106                $out[] = $uid;
107            }
108        }
109    }
110
111    /**
112     * @param list<string> $uids
113     * @return list<string>
114     */
115    private function unique(array $uids): array
116    {
117        $uids = array_values(array_unique($uids));
118        sort($uids);
119
120        return $uids;
121    }
122}