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