Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
61 / 61 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
| Index | |
100.00% |
61 / 61 |
|
100.00% |
4 / 4 |
16 | |
100.00% |
1 / 1 |
| index | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| cards | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
5 | |||
| recent | |
100.00% |
32 / 32 |
|
100.00% |
1 / 1 |
6 | |||
| typeLabels | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Cosray\Controller\Panel; |
| 6 | |
| 7 | use Celema\Core\Factory\Factory; |
| 8 | use Celema\Core\Response; |
| 9 | use Celema\Quma\Database; |
| 10 | use Cosray\Bootstrap; |
| 11 | use Cosray\Context; |
| 12 | use Cosray\Contract\DashboardCard; |
| 13 | use Cosray\Node\Types; |
| 14 | use Cosray\Panel\Dashboard; |
| 15 | use Cosray\Panel\NodeUrls; |
| 16 | use Cosray\Title\Resolver as TitleResolver; |
| 17 | use DateTimeImmutable; |
| 18 | use IntlDateFormatter; |
| 19 | use NumberFormatter; |
| 20 | |
| 21 | final class Index extends Panel |
| 22 | { |
| 23 | private const int RECENT_LIMIT = 6; |
| 24 | |
| 25 | protected const string AREA = 'dashboard'; |
| 26 | |
| 27 | /** @return array<string, mixed> */ |
| 28 | public function index( |
| 29 | Context $context, |
| 30 | Database $db, |
| 31 | Dashboard $dashboard, |
| 32 | Factory $factory, |
| 33 | Types $types, |
| 34 | ): array|Response { |
| 35 | if (!$this->config->panel->dashboard) { |
| 36 | return Response::create($factory)->redirect($this->homeUrl(), 303); |
| 37 | } |
| 38 | |
| 39 | return $this->context([ |
| 40 | 'cards' => $this->cards($context, $dashboard), |
| 41 | 'recent' => $this->recent($context, $db, $types), |
| 42 | ]); |
| 43 | } |
| 44 | |
| 45 | /** @return list<array{label: string, value: string, note: ?string, url: ?string}> */ |
| 46 | private function cards(Context $context, Dashboard $dashboard): array |
| 47 | { |
| 48 | $cards = []; |
| 49 | $formatter = new NumberFormatter($this->localeId(), NumberFormatter::DECIMAL); |
| 50 | $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 0); |
| 51 | $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 0); |
| 52 | |
| 53 | foreach ($dashboard->cards() as $definition) { |
| 54 | $provider = is_string($definition) ? $context->create($definition) : $definition; |
| 55 | assert($provider instanceof DashboardCard, 'The dashboard registry must contain card providers'); |
| 56 | $card = $provider->card(); |
| 57 | |
| 58 | if ($card === null) { |
| 59 | continue; |
| 60 | } |
| 61 | |
| 62 | $value = is_int($card->value) ? (string) $formatter->format($card->value) : $card->value; |
| 63 | $cards[] = [ |
| 64 | 'label' => $card->label, |
| 65 | 'value' => $value, |
| 66 | 'note' => $card->note, |
| 67 | 'url' => $card->url, |
| 68 | ]; |
| 69 | } |
| 70 | |
| 71 | return $cards; |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * @return list<array{ |
| 76 | * uid: string, |
| 77 | * url: string, |
| 78 | * title: string, |
| 79 | * type: string, |
| 80 | * changed: string, |
| 81 | * datetime: string, |
| 82 | * published: bool, |
| 83 | * status: string, |
| 84 | * }> |
| 85 | */ |
| 86 | private function recent(Context $context, Database $db, Types $types): array |
| 87 | { |
| 88 | $labels = $this->typeLabels($types); |
| 89 | $links = new NodeUrls($this->panelPath(), 'dashboard'); |
| 90 | $titles = new TitleResolver($types); |
| 91 | $date = new IntlDateFormatter( |
| 92 | $this->localeId(), |
| 93 | IntlDateFormatter::MEDIUM, |
| 94 | IntlDateFormatter::NONE, |
| 95 | $this->config->app->timezone->getName(), |
| 96 | ); |
| 97 | $recent = []; |
| 98 | |
| 99 | foreach ($db->dashboard->recent(['limit' => self::RECENT_LIMIT])->all() as $row) { |
| 100 | $uid = (string) ($row['uid'] ?? ''); |
| 101 | $map = $row['title'] ?? []; |
| 102 | |
| 103 | if (is_string($map)) { |
| 104 | $map = json_decode($map, true); |
| 105 | } |
| 106 | |
| 107 | $title = is_array($map) ? $titles->stored($map, $context->locale()) : null; |
| 108 | $changed = new DateTimeImmutable((string) ($row['changed'] ?? 'now')); |
| 109 | $formatted = $date->format($changed->getTimestamp()); |
| 110 | $published = (bool) ($row['published'] ?? false); |
| 111 | $type = (string) ($row['type'] ?? ''); |
| 112 | $recent[] = [ |
| 113 | 'uid' => $uid, |
| 114 | 'url' => $links->edit($uid), |
| 115 | 'title' => $title ?? $uid, |
| 116 | 'type' => $labels[$type] ?? $type, |
| 117 | 'changed' => $formatted === false ? '' : $formatted, |
| 118 | 'datetime' => $changed->format(DATE_ATOM), |
| 119 | 'published' => $published, |
| 120 | 'hasDraft' => (bool) ($row['has_draft'] ?? false), |
| 121 | 'status' => $published ? __('status:published') : __('status:unpublished'), |
| 122 | ]; |
| 123 | } |
| 124 | |
| 125 | return $recent; |
| 126 | } |
| 127 | |
| 128 | /** @return array<string, string> */ |
| 129 | private function typeLabels(Types $types): array |
| 130 | { |
| 131 | $registered = $this->container->tag(Bootstrap::NODE_TAG); |
| 132 | $labels = []; |
| 133 | |
| 134 | foreach ($registered->entries() as $handle) { |
| 135 | $class = $registered->entry($handle)->definition(); |
| 136 | assert(is_string($class) && class_exists($class), 'Registered node types must be existing classes'); |
| 137 | $labels[$handle] = __((string) $types->get($class, 'label', $handle)); |
| 138 | } |
| 139 | |
| 140 | return $labels; |
| 141 | } |
| 142 | } |