Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.10% covered (success)
97.10%
67 / 69
90.91% covered (success)
90.91%
10 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Column
97.10% covered (success)
97.10%
67 / 69
90.91% covered (success)
90.91%
10 / 11
36
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
 new
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 defaults
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 bold
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 italic
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 badge
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 date
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 sort
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 kind
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 get
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 getValue
93.94% covered (success)
93.94%
31 / 33
0.00% covered (danger)
0.00%
0 / 1
18.07
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray;
6
7use Closure;
8use Cosray\Collection\Sort;
9use Cosray\Finder\SortField;
10use Cosray\Node\Factory;
11use Cosray\Node\Wrapper;
12
13final class Column
14{
15    private bool|Closure $bold = false;
16    private bool|Closure $italic = false;
17    private bool|Closure $badge = false;
18    private bool|Closure $date = false;
19    public private(set) ?Sort $sort = null;
20    private string $color = '';
21
22    public function __construct(
23        public readonly string $title,
24        public readonly string|Closure $field,
25    ) {}
26
27    public static function new(
28        string $title,
29        string|Closure $field,
30    ): self {
31        return new self($title, $field);
32    }
33
34    /**
35     * The listing columns of a collection without its own: the title, which
36     * is also the initial order, and the last change.
37     *
38     * @return list<self>
39     */
40    public static function defaults(): array
41    {
42        return [
43            self::new(__('collection:column-title'), 'title')->bold(true)->sort('title'),
44            self::new(__('collection:column-changed'), 'meta.changed')
45                ->date(true)
46                ->sort(
47                    'changed',
48                    direction: 'desc',
49                ),
50        ];
51    }
52
53    public function bold(bool|Closure $bold): self
54    {
55        $this->bold = $bold;
56
57        return $this;
58    }
59
60    public function italic(bool|Closure $italic): self
61    {
62        $this->italic = $italic;
63
64        return $this;
65    }
66
67    public function badge(bool|Closure $badge): self
68    {
69        $this->badge = $badge;
70
71        return $this;
72    }
73
74    public function date(bool|Closure $date): self
75    {
76        $this->date = $date;
77
78        return $this;
79    }
80
81    /**
82     * @param non-empty-list<string|SortField>|null $fields
83     * @param bool $default Whether the listing starts in this order; without
84     *                      a flagged column it starts in the first sortable one
85     */
86    public function sort(
87        ?string $key,
88        ?array $fields = null,
89        string $direction = 'asc',
90        bool $default = false,
91    ): self {
92        $this->sort = $key === null ? null : new Sort(trim($key), $fields, $direction, $default);
93
94        return $this;
95    }
96
97    /**
98     * What the column holds, for the listing to size its track by. A date needs
99     * room a number does not, and a badge sizes to its own text. Flags resolved
100     * per node cannot be answered for the column as a whole, so they count as
101     * text â€” the safe assumption, since text is what shrinks gracefully.
102     */
103    public function kind(): string
104    {
105        if ($this->date === true) {
106            return 'date';
107        }
108
109        if ($this->badge === true) {
110            return 'badge';
111        }
112
113        return 'text';
114    }
115
116    /** @return array<string, mixed> */
117    public function get(Wrapper $node): array
118    {
119        return [
120            'value' => is_string($this->field)
121                ? $this->getValue($node, $this->field)
122                : ($this->field)($node),
123            'bold' => is_bool($this->bold) ? $this->bold : ($this->bold)($node),
124            'italic' => is_bool($this->italic) ? $this->italic : ($this->italic)($node),
125            'badge' => is_bool($this->badge) ? $this->badge : ($this->badge)($node),
126            'date' => is_bool($this->date) ? $this->date : ($this->date)($node),
127            'color' => $this->color,
128        ];
129    }
130
131    private function getValue(Wrapper $node, string $field): mixed
132    {
133        $inner = Wrapper::unwrap($node);
134
135        switch ($field) {
136            case 'title':
137                return $node->label();
138            case 'meta.name':
139                return $node->meta->name;
140            case 'meta.uid':
141            case 'meta.published':
142            case 'meta.hidden':
143            case 'meta.locked':
144            case 'meta.created':
145            case 'meta.changed':
146            case 'meta.deleted':
147            case 'meta.content':
148            case 'meta.handle':
149                $key = explode('.', $field)[1];
150
151                return $node->meta->get($key);
152            case 'meta.class':
153                return $inner::class;
154            case 'meta.classname':
155                return basename(str_replace('\\', '/', $inner::class));
156            case 'meta.editor':
157                $editorData = (array) $node->meta->get('editor_data', []);
158                $editor =
159                    $editorData['name'] ?? $node->meta->get('editor_username') ?? $node->meta->get('editor_email');
160
161                return escape((string) $editor);
162            case 'meta.creator':
163                $creatorData = (array) $node->meta->get('creator_data', []);
164                $creator =
165                    $creatorData['name'] ?? $node->meta->get('creator_username') ?? $node->meta->get('creator_email');
166
167                return escape((string) $creator);
168            default:
169                $fieldObj = Factory::fieldFor($inner, $field);
170                $value = $fieldObj->value();
171
172                return $value->isset() ? (string) $value : null;
173        }
174    }
175}