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 list<string> $assets
47     * @param list<string> $nodes
48     */
49    private static function walkContent(array $data, array &$assets, array &$nodes): void
50    {
51        if (($data['format'] ?? null) === Envelope::FORMAT && is_array($data['value'] ?? null)) {
52            foreach ($data['value'] as $doc) {
53                if (is_array($doc)) {
54                    self::walk($doc, $assets, $nodes);
55                }
56            }
57
58            return;
59        }
60
61        foreach ($data as $value) {
62            if (is_array($value)) {
63                self::walkContent($value, $assets, $nodes);
64            }
65        }
66    }
67
68    /**
69     * @param list<string> $assets
70     * @param list<string> $nodes
71     */
72    private static function walk(array $node, array &$assets, array &$nodes): void
73    {
74        if (($node['type'] ?? null) === 'image') {
75            $uid = $node['attrs']['uid'] ?? null;
76
77            if (is_string($uid) && $uid !== '') {
78                $assets[] = $uid;
79            }
80        }
81
82        foreach (is_array($node['marks'] ?? null) ? $node['marks'] : [] as $mark) {
83            if (!is_array($mark) || ($mark['type'] ?? null) !== 'link') {
84                continue;
85            }
86
87            $asset = $mark['attrs']['asset'] ?? null;
88            $target = $mark['attrs']['node'] ?? null;
89
90            if (is_string($asset) && $asset !== '') {
91                $assets[] = $asset;
92            }
93
94            if (is_string($target) && $target !== '') {
95                $nodes[] = $target;
96            }
97        }
98
99        foreach (is_array($node['content'] ?? null) ? $node['content'] : [] as $child) {
100            if (is_array($child)) {
101                self::walk($child, $assets, $nodes);
102            }
103        }
104    }
105}