140 lines
4.2 KiB
PHP
140 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace Modules\Stylist\View;
|
|
|
|
use Illuminate\Contracts\View\Factory as ViewFactory;
|
|
use Illuminate\Contracts\View\View;
|
|
use Modules\Stylist\Theme\Exceptions\ThemeNotFoundException;
|
|
use Modules\Stylist\Theme\Loader;
|
|
use Modules\Stylist\Theme\Stylist;
|
|
use RuntimeException;
|
|
|
|
final class ThemeViewResolver
|
|
{
|
|
/** @var array<string, array<int, string>> */
|
|
private array $candidateCache = [];
|
|
|
|
public function __construct(
|
|
private readonly Stylist $stylist,
|
|
private readonly ViewFactory $views,
|
|
private readonly Loader $loader,
|
|
) {}
|
|
|
|
public function make(string $view, array $data = [], array $mergeData = []): View
|
|
{
|
|
return $this->views->first(
|
|
$this->candidates($view),
|
|
array_replace([
|
|
'themeSettings' => $this->stylist->settings(),
|
|
'themeVariant' => $this->stylist->variant(),
|
|
], $data),
|
|
$mergeData,
|
|
);
|
|
}
|
|
|
|
public function makeForTheme(string $theme, string $view, array $data = [], array $mergeData = []): View
|
|
{
|
|
return $this->views->first($this->candidatesForTheme($theme, $view), $data, $mergeData);
|
|
}
|
|
|
|
/** @return array<int, string> */
|
|
public function candidates(string $view): array
|
|
{
|
|
return $this->candidatesForTheme($this->stylist->current()->key(), $view);
|
|
}
|
|
|
|
/** @return array<int, string> */
|
|
/** @return array<int, string> */
|
|
public function themedCandidatesForTheme(string $theme, string $view): array
|
|
{
|
|
return array_values(array_filter(
|
|
$this->candidatesForTheme($theme, $view),
|
|
fn (string $candidate): bool => str_contains($candidate, '::'),
|
|
));
|
|
}
|
|
|
|
public function candidatesForTheme(string $theme, string $view): array
|
|
{
|
|
$view = ltrim($view, '.');
|
|
$cacheKey = $theme.'|'.$view;
|
|
|
|
if (isset($this->candidateCache[$cacheKey])) {
|
|
return $this->candidateCache[$cacheKey];
|
|
}
|
|
|
|
$keys = $this->inheritanceChain($theme);
|
|
$fallback = (string) config('stylist.default_theme', 'common');
|
|
|
|
if ($fallback !== '' && ! in_array($fallback, $keys, true)) {
|
|
try {
|
|
$keys = array_merge($keys, $this->inheritanceChain($fallback));
|
|
} catch (ThemeNotFoundException) {
|
|
// A missing configured fallback is diagnosed by stylist:doctor.
|
|
}
|
|
}
|
|
|
|
if (! in_array('common', $keys, true)) {
|
|
try {
|
|
$keys = array_merge($keys, $this->inheritanceChain('common'));
|
|
} catch (ThemeNotFoundException) {
|
|
// The module fallback below remains available.
|
|
}
|
|
}
|
|
|
|
$candidates = [];
|
|
foreach (array_values(array_unique($keys)) as $key) {
|
|
$this->registerNamespace($key);
|
|
$candidates[] = "stylist-theme-{$key}::{$view}";
|
|
$candidates[] = "stylist-module-theme-{$key}::{$view}";
|
|
}
|
|
|
|
$candidates[] = "stylist::{$view}";
|
|
$candidates[] = $view;
|
|
|
|
return $this->candidateCache[$cacheKey] = array_values(array_unique($candidates));
|
|
}
|
|
|
|
public function flush(): void
|
|
{
|
|
$this->candidateCache = [];
|
|
}
|
|
|
|
/** @return array<int, string> */
|
|
private function inheritanceChain(string $theme): array
|
|
{
|
|
$keys = [];
|
|
$seen = [];
|
|
$key = $theme;
|
|
|
|
while ($key !== '') {
|
|
if (isset($seen[$key])) {
|
|
throw new RuntimeException('Theme inheritance cycle detected at ['.$key.'].');
|
|
}
|
|
|
|
$seen[$key] = true;
|
|
$loaded = $this->loader->find($key);
|
|
$keys[] = $loaded->key();
|
|
$key = $loaded->parent() ?? '';
|
|
}
|
|
|
|
return $keys;
|
|
}
|
|
|
|
private function registerNamespace(string $theme): void
|
|
{
|
|
try {
|
|
$loaded = $this->loader->find($theme);
|
|
} catch (ThemeNotFoundException) {
|
|
return;
|
|
}
|
|
|
|
$viewsPath = $loaded->path().DIRECTORY_SEPARATOR.'views';
|
|
if (! is_dir($viewsPath)) {
|
|
return;
|
|
}
|
|
|
|
$this->views->addNamespace("stylist-theme-{$theme}", $viewsPath);
|
|
$this->views->addNamespace("stylist-module-theme-{$theme}", $viewsPath);
|
|
}
|
|
}
|