148 lines
4.4 KiB
PHP
148 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Modules\Stylist\Diagnostics;
|
|
|
|
use Illuminate\Filesystem\Filesystem;
|
|
use Modules\Stylist\Theme\Loader;
|
|
use Modules\Stylist\Theme\Theme;
|
|
|
|
final class ThemeInspector
|
|
{
|
|
public function __construct(
|
|
private readonly Filesystem $files,
|
|
private readonly Loader $loader,
|
|
) {}
|
|
|
|
/** @return array<int, array<string, mixed>> */
|
|
public function themes(): array
|
|
{
|
|
return $this->loader->all()->map(function (Theme $theme): array {
|
|
return [
|
|
'key' => $theme->key(),
|
|
'name' => $theme->name(),
|
|
'parent' => $theme->parent(),
|
|
'path' => $theme->path(),
|
|
'views' => count($this->viewFiles($theme)),
|
|
'entry_view' => $theme->entryView(),
|
|
];
|
|
})->values()->all();
|
|
}
|
|
|
|
/** @return array<string, array<int, array{theme:string,path:string}>> */
|
|
public function duplicateViews(): array
|
|
{
|
|
$ownership = [];
|
|
|
|
foreach ($this->loader->all() as $theme) {
|
|
foreach ($this->viewFiles($theme) as $relative => $absolute) {
|
|
$ownership[$relative][] = [
|
|
'theme' => $theme->key(),
|
|
'path' => $absolute,
|
|
];
|
|
}
|
|
}
|
|
|
|
return array_filter($ownership, fn (array $owners): bool => count($owners) > 1);
|
|
}
|
|
|
|
/** @return array<int, array{severity:string,theme:string,message:string}> */
|
|
public function issues(): array
|
|
{
|
|
$issues = [];
|
|
$themes = $this->loader->all();
|
|
|
|
foreach ($themes as $theme) {
|
|
$parent = $theme->parent();
|
|
|
|
if ($parent !== null && ! $themes->has($parent)) {
|
|
$issues[] = [
|
|
'severity' => 'error',
|
|
'theme' => $theme->key(),
|
|
'message' => "Parent theme [{$parent}] is not installed.",
|
|
];
|
|
}
|
|
|
|
if ($this->hasInheritanceCycle($theme, $themes->all())) {
|
|
$issues[] = [
|
|
'severity' => 'error',
|
|
'theme' => $theme->key(),
|
|
'message' => 'Theme inheritance contains a cycle.',
|
|
];
|
|
}
|
|
|
|
$entry = $theme->path().'/views/'.str_replace('.', '/', $theme->entryView()).'.blade.php';
|
|
if (! $this->files->exists($entry) && $parent === null) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'theme' => $theme->key(),
|
|
'message' => "Entry view [{$theme->entryView()}] does not exist in this theme.",
|
|
];
|
|
}
|
|
}
|
|
|
|
foreach ($this->duplicateViews() as $view => $owners) {
|
|
$issues[] = [
|
|
'severity' => 'error',
|
|
'theme' => implode(', ', array_column($owners, 'theme')),
|
|
'message' => "View [{$view}] has multiple owners.",
|
|
];
|
|
}
|
|
|
|
$default = (string) config('stylist.default_theme');
|
|
if ($default !== '' && ! $themes->has($default)) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'theme' => $default,
|
|
'message' => 'Configured default theme is not installed.',
|
|
];
|
|
}
|
|
|
|
return $issues;
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
private function viewFiles(Theme $theme): array
|
|
{
|
|
$root = $theme->path().'/views';
|
|
if (! $this->files->isDirectory($root)) {
|
|
return [];
|
|
}
|
|
|
|
$views = [];
|
|
foreach ($this->files->allFiles($root) as $file) {
|
|
if (! str_ends_with($file->getFilename(), '.blade.php')) {
|
|
continue;
|
|
}
|
|
|
|
$absolute = str_replace('\\', '/', $file->getPathname());
|
|
$relative = ltrim(str_replace(str_replace('\\', '/', $root), '', $absolute), '/');
|
|
$views[$relative] = $absolute;
|
|
}
|
|
|
|
ksort($views);
|
|
|
|
return $views;
|
|
}
|
|
|
|
/** @param array<string, Theme> $themes */
|
|
private function hasInheritanceCycle(Theme $theme, array $themes): bool
|
|
{
|
|
$seen = [];
|
|
$key = $theme->key();
|
|
|
|
while (isset($themes[$key])) {
|
|
if (isset($seen[$key])) {
|
|
return true;
|
|
}
|
|
|
|
$seen[$key] = true;
|
|
$key = $themes[$key]->parent() ?? '';
|
|
if ($key === '') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|