Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.37% covered (success)
97.37%
37 / 38
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Scanner
97.37% covered (success)
97.37%
37 / 38
75.00% covered (warning)
75.00%
3 / 4
26
0.00% covered (danger)
0.00%
0 / 1
 scan
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 scanContent
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 walkContent
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
 walk
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
15.05
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Richtext;
6
7/**
8 * Collects the complete set of reference carriers from a richtext
9 * document: `image.uid`, `link.asset`, and `link.node`. Feeds the
10 * reference indexes (Phase 2) and the panel's asset lookup.
11 */
12final class Scanner
13{
14    /** @return array{assets: list<string>, nodes: list<string>} */
15    public static function scan(mixed $doc): array
16    {
17        $assets = [];
18        $nodes = [];
19        self::walk(is_array($doc) ? $doc : [], $assets, $nodes);
20
21        return [
22            'assets' => array_values(array_unique($assets)),
23            'nodes' => array_values(array_unique($nodes)),
24        ];
25    }
26
27    /**
28     * Scan a whole node content array: every envelope-marked richtext
29     * value (fields and blocks alike) contributes its references.
30     *
31     * @return array{assets: list<string>, nodes: list<string>}
32     */
33    public static function scanContent(mixed $content): array
34    {
35        $assets = [];
36        $nodes = [];
37        self::walkContent(is_array($content) ? $content : [], $assets, $nodes);
38
39        return [
40            'assets' => array_values(array_unique($assets)),
41            'nodes' => array_values(array_unique($nodes)),
42        ];
43    }
44
45    /**
46     * @param array<mixed> $data
47     * @param list<string> $assets
48     * @param list<string> $nodes
49     */
50    private static function walkContent(array $data, array &$assets, array &$nodes): void
51    {
52        if (($data['format'] ?? null) === Envelope::FORMAT && is_array($data['value'] ?? null)) {
53            foreach ($data['value'] as $doc) {
54                if (is_array($doc)) {
55                    self::walk($doc, $assets, $nodes);
56                }
57            }
58
59            return;
60        }
61
62        foreach ($data as $value) {
63            if (is_array($value)) {
64                self::walkContent($value, $assets, $nodes);
65            }
66        }
67    }
68
69    /**
70     * @param array<mixed> $node
71     * @param list<string> $assets
72     * @param list<string> $nodes
73     */
74    private static function walk(array $node, array &$assets, array &$nodes): void
75    {
76        if (($node['type'] ?? null) === 'image') {
77            $uid = $node['attrs']['uid'] ?? null;
78
79            if (is_string($uid) && $uid !== '') {
80                $assets[] = $uid;
81            }
82        }
83
84        foreach (is_array($node['marks'] ?? null) ? $node['marks'] : [] as $mark) {
85            if (!is_array($mark) || ($mark['type'] ?? null) !== 'link') {
86                continue;
87            }
88
89            $asset = $mark['attrs']['asset'] ?? null;
90            $target = $mark['attrs']['node'] ?? null;
91
92            if (is_string($asset) && $asset !== '') {
93                $assets[] = $asset;
94            }
95
96            if (is_string($target) && $target !== '') {
97                $nodes[] = $target;
98            }
99        }
100
101        foreach (is_array($node['content'] ?? null) ? $node['content'] : [] as $child) {
102            if (is_array($child)) {
103                self::walk($child, $assets, $nodes);
104            }
105        }
106    }
107}