Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
60 / 72
80.00% covered (warning)
80.00%
12 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Panel
83.33% covered (warning)
83.33%
60 / 72
80.00% covered (warning)
80.00%
12 / 15
43.34
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
 context
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
 panelPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 localeId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 stylesheets
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 scripts
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 moduleScripts
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 hasPanelBuild
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 publicPanelBuildDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 publicPanelDir
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 panelDevOrigin
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
7.10
 panelDevHost
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 logo
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 collections
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renderIcon
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Controller\Panel;
6
7use Celemas\Container\Container;
8use Celemas\Core\Request;
9use Cosray\Config;
10use Cosray\Contract\Icons;
11use Cosray\Locale;
12use Cosray\Navigation;
13
14use function Cosray\env;
15
16abstract class Panel
17{
18    protected string $panelDir;
19
20    public function __construct(
21        protected Config $config,
22        protected Container $container,
23        protected readonly Request $request,
24    ) {
25        $this->panelDir = __DIR__ . '/../../../panel';
26    }
27
28    protected function context(array $data = []): array
29    {
30        $panelPath = $this->panelPath();
31        $localeId = $this->localeId();
32
33        return array_merge([
34            'debug' => $this->config->debug(),
35            'env' => $this->config->env(),
36            'boosted' => $this->request->hasHeader('HX-Boosted'),
37            'htmx' => $this->request->hasHeader('HX-Request'),
38            'panelPath' => $panelPath,
39            'currentPath' => $this->request->uri()->getPath(),
40            'logo' => $this->logo(),
41            'localeId' => $localeId,
42            'config' => $this->config,
43            'renderIcon' => $this->renderIcon(...),
44            'stylesheets' => $this->stylesheets($panelPath),
45            'scripts' => $this->scripts($panelPath),
46            'moduleScripts' => $this->moduleScripts($panelPath),
47            'collections' => $this->collections(),
48        ], $data);
49    }
50
51    protected function panelPath(): string
52    {
53        return $this->config->panel->path;
54    }
55
56    protected function localeId(): string
57    {
58        $locale = $this->request->get('locale', null);
59
60        return $locale instanceof Locale ? $locale->id : 'en';
61    }
62
63    private function stylesheets(string $panelPath): array
64    {
65        $stylesheets = $this->config->panel->theme;
66
67        if ($this->config->env() !== 'development' && $this->hasPanelBuild()) {
68            $stylesheets[] = "{$panelPath}/build/panel.css";
69        }
70
71        return $stylesheets;
72    }
73
74    private function scripts(string $panelPath): array
75    {
76        return [
77            "{$panelPath}/assets/app/vendor/htmx.js",
78        ];
79    }
80
81    private function moduleScripts(string $panelPath): array
82    {
83        if ($this->config->env() === 'development') {
84            $origin = $this->panelDevOrigin();
85
86            return [
87                "{$origin}/@vite/client",
88                "{$origin}/src/panel.ts",
89            ];
90        }
91
92        return $this->hasPanelBuild() ? ["{$panelPath}/build/panel.js"] : [];
93    }
94
95    protected function hasPanelBuild(): bool
96    {
97        $build = $this->publicPanelBuildDir();
98
99        return is_file($build . '/panel.js') && is_file($build . '/panel.css');
100    }
101
102    protected function publicPanelBuildDir(): string
103    {
104        return $this->publicPanelDir() . '/build';
105    }
106
107    private function publicPanelDir(): string
108    {
109        $path = trim($this->panelPath(), '/');
110        $public = rtrim($this->config->path->public, '/\\');
111
112        return $path === '' ? $public : "{$public}/{$path}";
113    }
114
115    private function panelDevOrigin(): string
116    {
117        $origin = env('COSRAY_PANEL_DEV_ORIGIN', null);
118
119        if (is_string($origin) && trim($origin) !== '') {
120            return rtrim(trim($origin), '/');
121        }
122
123        $scheme = env('COSRAY_PANEL_DEV_SCHEME', 'http');
124        $scheme = is_string($scheme) && in_array($scheme, ['http', 'https'], true) ? $scheme : 'http';
125        $port = env('COSRAY_PANEL_DEV_PORT', '2001');
126        $port = is_scalar($port) && preg_match('/^[0-9]+$/', (string) $port) ? (string) $port : '2001';
127
128        return "{$scheme}://{$this->panelDevHost()}:{$port}";
129    }
130
131    private function panelDevHost(): string
132    {
133        $host = $this->request->uri()->getHost();
134
135        if ($host === '') {
136            $host = $this->request->header('Host');
137        }
138
139        $host = trim(explode(':', $host)[0] ?? '');
140
141        return preg_match('/^[A-Za-z0-9.-]+$/', $host) === 1 ? $host : 'localhost';
142    }
143
144    private function logo(): ?string
145    {
146        $logo = $this->config->panel->logo;
147
148        if ($logo === null) {
149            return null;
150        }
151
152        $logo = trim((string) $logo);
153
154        return $logo === '' ? null : $logo;
155    }
156
157    protected function collections(): array
158    {
159        /** @var Navigation $navigation */
160        $navigation = $this->container->get(Navigation::class);
161
162        return $navigation->items();
163    }
164
165    /** @param array{id: string, args?: array<array-key, mixed>}|null $icon */
166    private function renderIcon(?array $icon): string
167    {
168        if ($icon === null) {
169            return '';
170        }
171
172        $id = $icon['id'] ?? null;
173
174        if (!is_string($id) || trim($id) === '') {
175            return '';
176        }
177
178        $service = $this->container->get(Icons::class);
179
180        if (!$service instanceof Icons) {
181            return '';
182        }
183
184        $args = $icon['args'] ?? [];
185
186        return $service->icon(trim($id), is_array($args) ? $args : []);
187    }
188}