Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.91% covered (success)
96.91%
94 / 97
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Listing
96.91% covered (success)
96.91%
94 / 97
71.43% covered (warning)
71.43%
5 / 7
31
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 list
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
4
 childBlueprints
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
7.14
 rows
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 row
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
5
 hasChildrenMap
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
7
 resolveOrder
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Collection;
6
7use Cosray\Collection;
8use Cosray\Exception\RuntimeException;
9use Cosray\Node\Types;
10use Cosray\Node\Wrapper;
11
12/**
13 * List, row and hierarchy logic for collection pages.
14 *
15 * Kept out of the Collection base class so collections stay pure
16 * configuration + query objects.
17 */
18final class Listing
19{
20    public function __construct(
21        private readonly Collection $collection,
22        private readonly Types $types,
23    ) {}
24
25    public function list(
26        int $offset = 0,
27        int $limit = 50,
28        string $q = '',
29        string $sort = '',
30        string $dir = 'desc',
31        ?string $parent = null,
32    ): array {
33        $nodes = $this->collection->entries();
34
35        if ($this->collection->listMeta->showChildren) {
36            $parent = trim((string) $parent);
37
38            if ($parent === '') {
39                $nodes->roots();
40            } else {
41                $nodes->childrenOf($parent);
42            }
43        }
44
45        $q = trim($q);
46
47        if ($q !== '') {
48            $nodes->search($q, $this->collection->searchFields());
49        }
50
51        [$sort, $dir, $order] = $this->resolveOrder($sort, $dir);
52        $nodes->order(...$order);
53
54        $total = $nodes->count();
55        $nodes->offset($offset)->limit($limit);
56        $pageNodes = iterator_to_array($nodes);
57
58        return [
59            'total' => $total,
60            'offset' => $offset,
61            'limit' => $limit,
62            'q' => $q,
63            'sort' => $sort,
64            'dir' => $dir,
65            'nodes' => $this->rows($pageNodes),
66        ];
67    }
68
69    /** @return list<array{slug: string, name: string}> */
70    public function childBlueprints(Wrapper $node): array
71    {
72        $children = $node->meta->type->children;
73
74        if (!is_array($children) || $children === []) {
75            return [];
76        }
77
78        $result = [];
79
80        foreach ($children as $class) {
81            if (!is_string($class) || $class === '') {
82                throw new RuntimeException('The children schema must contain non-empty class names');
83            }
84
85            if (!$this->types->isNode($class)) {
86                throw new RuntimeException("Unknown child node class '{$class}' in #[Children(...)]");
87            }
88
89            $result[] = [
90                'slug' => (string) $this->types->get($class, 'handle'),
91                'name' => (string) $this->types->get($class, 'label'),
92            ];
93        }
94
95        return $result;
96    }
97
98    /**
99     * @param list<Wrapper> $nodes
100     */
101    private function rows(array $nodes): array
102    {
103        $result = [];
104        $hasChildren = $this->collection->listMeta->showChildren
105            ? $this->hasChildrenMap($nodes)
106            : [];
107
108        foreach ($nodes as $node) {
109            $result[] = $this->row($node, $hasChildren[$node->meta->uid] ?? false);
110        }
111
112        return $result;
113    }
114
115    private function row(Wrapper $node, bool $hasChildren): array
116    {
117        $columns = [];
118        $parent = $node->meta->get('parent');
119
120        if (!is_string($parent) || trim($parent) === '') {
121            $parent = null;
122        }
123
124        $childBlueprints = $this->collection->listMeta->showChildren
125            ? $this->childBlueprints($node)
126            : [];
127
128        foreach ($this->collection->columns() as $column) {
129            $columns[] = $column->get($node);
130        }
131
132        return [
133            'uid' => $node->meta->uid,
134            'published' => $node->meta->published,
135            'locked' => $node->meta->locked,
136            'hidden' => $node->meta->hidden,
137            'parent' => $parent,
138            'hasChildren' => $hasChildren,
139            'childBlueprints' => $childBlueprints,
140            'columns' => $columns,
141        ];
142    }
143
144    /**
145     * @param list<Wrapper> $nodes
146     * @return array<string, bool>
147     */
148    private function hasChildrenMap(array $nodes): array
149    {
150        if ($nodes === []) {
151            return [];
152        }
153
154        $uids = [];
155
156        foreach ($nodes as $node) {
157            $uids[] = $node->meta->uid;
158        }
159
160        $uids = array_values(array_unique($uids));
161        $list = implode(',', array_map(
162            static fn(string $uid): string => "'" . str_replace("'", "\\\\'", $uid) . "'",
163            $uids,
164        ));
165
166        if ($list === '') {
167            return [];
168        }
169
170        $children = $this->collection
171            ->cms
172            ->nodes("parent @ [{$list}]")
173            ->published(null)
174            ->hidden(null);
175        $result = [];
176
177        foreach ($children as $child) {
178            $parentUid = $child->meta->get('parent');
179
180            if (is_string($parentUid) && $parentUid !== '') {
181                $result[$parentUid] = true;
182            }
183        }
184
185        return $result;
186    }
187
188    private function resolveOrder(string $sort, string $dir): array
189    {
190        $sort = trim($sort);
191        $dir = strtolower(trim($dir));
192        $dir = in_array($dir, ['asc', 'desc'], true) ? $dir : strtolower($this->collection->defaultDir());
193        $sorts = $this->collection->sorts();
194        $sort = array_key_exists($sort, $sorts) ? $sort : $this->collection->defaultSort();
195        $field = $sorts[$sort] ?? 'changed';
196        $order = [sprintf('%s %s', $field, strtoupper($dir))];
197
198        if ($field !== 'uid') {
199            $order[] = 'uid ASC';
200        }
201
202        return [$sort, $dir, $order];
203    }
204}