Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.00% covered (warning)
68.00%
34 / 50
44.44% covered (danger)
44.44%
4 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Node
68.00% covered (warning)
68.00%
34 / 50
44.44% covered (danger)
44.44%
4 / 9
53.69
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
 path
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 unwrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 meta
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 title
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
7.12
 children
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 __get
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 __isset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __call
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Node;
6
7use Celemas\Core\Request;
8use Cosray\Cms;
9use Cosray\Context;
10use Cosray\Exception\RuntimeException;
11use Cosray\Field\FieldHydrator;
12use Cosray\Field\Text;
13use Cosray\Finder\Nodes;
14use Cosray\Locale;
15use Cosray\Node\Contract\Title;
16use Cosray\Value\Value;
17
18class Node
19{
20    public readonly Meta $meta;
21
22    public function __construct(
23        private readonly object $node,
24        private readonly array $fieldNames,
25        private readonly FieldHydrator $hydrator,
26        private readonly Types $types,
27        private readonly ?Request $request = null,
28        private readonly ?Context $context = null,
29        private readonly ?Cms $cms = null,
30        private readonly ?Factory $nodeFactory = null,
31    ) {
32        $this->meta = new Meta($this->node, $this->types);
33    }
34
35    /**
36     * Resolve the locale-aware URL path for this node.
37     *
38     * Uses the paths stored in Factory's WeakMap data,
39     * walking the locale fallback chain until a path is found.
40     */
41    public function path(?Locale $locale = null): string
42    {
43        $data = Factory::dataFor($this->node);
44        $paths = $data['paths'] ?? [];
45
46        if (!$locale && $this->request) {
47            $locale = $this->request->get('locale');
48        }
49
50        while ($locale) {
51            if (isset($paths[$locale->id])) {
52                return $paths[$locale->id];
53            }
54
55            $locale = $locale->fallback();
56        }
57
58        throw new RuntimeException('No url path found');
59    }
60
61    /**
62     * Return the inner node if the given object is a Node wrapper,
63     * otherwise return the object unchanged.
64     */
65    public static function unwrap(object $object): object
66    {
67        return $object instanceof self ? $object->node : $object;
68    }
69
70    public function meta(string $key, mixed $default = null): mixed
71    {
72        return $this->meta->get($key, $default);
73    }
74
75    public function title(): string
76    {
77        $inner = self::unwrap($this->node);
78
79        if ($inner instanceof Title) {
80            return $inner->title();
81        }
82
83        $titleField = $this->types->get($inner::class, 'titleField');
84
85        if (is_string($titleField) && $titleField !== '') {
86            $field = $this->hydrator->getField($inner, $titleField);
87
88            if (!$field instanceof Text) {
89                return '';
90            }
91
92            return $field->value()->unwrap() ?? '';
93        }
94
95        if (in_array('title', $this->fieldNames, true)) {
96            $field = $this->hydrator->getField($inner, 'title');
97
98            if (!$field instanceof Text) {
99                return '';
100            }
101
102            return $field->value()->unwrap() ?? '';
103        }
104
105        return '';
106    }
107
108    public function children(string $query = ''): Nodes
109    {
110        if ($this->context === null || $this->cms === null || $this->nodeFactory === null) {
111            throw new RuntimeException('children() is only available on finder-backed node proxies');
112        }
113
114        $children = new Nodes($this->context, $this->cms, $this->nodeFactory, $this->types)
115            ->published(null)
116            ->hidden(null)
117            ->childrenOf($this->meta->uid);
118
119        if (trim($query) !== '') {
120            $children->filter($query);
121        }
122
123        return $children;
124    }
125
126    public function __get(string $name): ?Value
127    {
128        if (in_array($name, $this->fieldNames, true)) {
129            $field = $this->hydrator->getField($this->node, $name);
130            $value = $field->value();
131
132            if ($value->isset()) {
133                return $value;
134            }
135
136            return null;
137        }
138
139        if (property_exists($this->node, $name)) {
140            return $this->node->{$name};
141        }
142
143        return null;
144    }
145
146    public function __isset(string $name): bool
147    {
148        if (in_array($name, $this->fieldNames, true)) {
149            $field = $this->hydrator->getField($this->node, $name);
150
151            return $field->value()->isset();
152        }
153
154        return isset($this->node->{$name});
155    }
156
157    public function __call(string $name, array $args): mixed
158    {
159        return $this->node->{$name}(...$args);
160    }
161}