added theme support

This commit is contained in:
Mark Gregory
2026-10-20 10:23:23 +01:00
parent 1d70a9c8cf
commit f0e31aad6b
5 changed files with 55 additions and 25 deletions
@@ -59,6 +59,9 @@ final class ThemeManagementController extends Controller
public function uninstall(string $theme, Loader $loader, ThemePackageManager $packages)
{
$item = $loader->find($theme);
abort_if(in_array($item->key(), ['sonardesk', 'sonardesk-admin'], true), 403, 'Bundled themes cannot be removed.');
$settings = app(SettingsRepository::class);
abort_if($settings->get($item->type().'_theme', null, 'site') === $item->key(), 409, 'Deactivate this theme before uninstalling.');
$packages->uninstall($item);
return back()->with('status', $item->name().' uninstalled.');
@@ -5,6 +5,7 @@ namespace Modules\Stylist\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;
use Modules\Stylist\Theme\Loader;
use Modules\Stylist\Theme\Stylist;
use Throwable;
@@ -12,20 +13,38 @@ final class ApplyActiveTheme
{
public function handle(Request $request, Closure $next)
{
$context = $request->is('admin', 'admin/*', 'stylist', 'stylist/*') ? 'backend' : 'frontend';
$context = $request->is('admin', 'admin/*', 'stylist', 'stylist/*')
|| $request->routeIs('admin.*', 'stylist.*') ? 'backend' : 'frontend';
try {
$loader = app(Loader::class);
$theme = app(Stylist::class)->current($context);
if ($theme->type() === $context) {
if ($theme->type() !== $context) {
$theme = $loader->find($context === 'backend' ? 'sonardesk-admin' : 'sonardesk');
}
$paths = [];
$seen = [];
while ($theme && ! isset($seen[$theme->key()])) {
$seen[$theme->key()] = true;
$path = $theme->path().'/views';
if (is_dir($path)) {
// Prepend so ordinary view('admin.index') and @extends('layouts.app')
// resolve theme files first, then original application files.
View::getFinder()->prependLocation($path);
$paths[] = $path;
}
$theme = $theme->parent() ? $loader->find($theme->parent()) : null;
}
$baseline = $loader->find($context === 'backend' ? 'sonardesk-admin' : 'sonardesk');
$paths[] = $baseline->path().'/views';
// Finder uses first match. Prepend in reverse order to preserve precedence.
foreach (array_reverse(array_unique($paths)) as $path) {
View::getFinder()->prependLocation($path);
}
} catch (Throwable $exception) {
report($exception); // Original application views remain available.
report($exception);
// Original Laravel views remain available for recovery.
}
return $next($request);
}
}
+18 -14
View File
@@ -1,15 +1,19 @@
# SonarDesk Stylist integration — 1.0.0
# SonarDesk — Stylist theme integration
- `resources/themes/sonardesk/views` contains the original frontend Blade templates.
- `resources/themes/sonardesk-admin/views` contains the original backend Blade templates.
- Both are fully editable independent view trees, with application view fallback.
- Active theme is selected by existing Stylist SettingsRepository (`frontend_theme` / `backend_theme`).
- The `ApplyActiveTheme` web middleware prepends the active theme view directory for each request.
- Public pages and authenticated customer pages use frontend; `/admin` and `/stylist` use backend.
- Controllers, routes, licensing, payments, APIs and mail templates remain unchanged.
- Theme management now requires AdminOnly permissions.
- Existing CSS/JS assets remain in place. Replacement themes may provide new layout asset links.
- Run `php artisan optimize:clear` and `php artisan migrate` after deployment.
- Check `/stylist` as an administrator, then verify public, account, admin, and checkout pages.
- If database already contains old `frontend_theme`/`backend_theme` values, activate the new bundled themes from Stylist.
- Theme ZIP installation security and the completeness of third-party theme templates require separate review before production use.
Stylist is the primary resolver for ordinary frontend and backend Laravel views. The active theme, its parents, the bundled baseline and finally `resources/views` are searched in that order.
- Frontend: `resources/themes/sonardesk/views`.
- Backend: `resources/themes/sonardesk-admin/views`.
- The bundled themes preserve existing markup, CSS, JavaScript, routes and business logic.
- `/admin` and `/stylist` use backend; other web pages (including authenticated customer pages) use frontend.
- AdminOnly middleware protects Stylist operations.
- Bundled and active themes cannot be uninstalled.
- Keep `resources/views`: email templates, fallback and compatibility views are required.
- New theme ZIPs must include all needed templates, layouts, assets and a valid theme.json. Missing views fall back to baseline.
- The middleware does not change asset URLs. Replacement themes must supply their own asset references and use the existing Stylist asset serving facilities.
## Local validation
Run `php artisan optimize:clear`, `php artisan migrate`, `php artisan stylist:doctor` (if registered) and `php artisan view:cache` using your configured PHP binary. Test public pages, authentication, checkout, admin, Stylist, and transactional email before deployment.
This package has not been executed against your ServBay environment. Do not deploy to production without smoke testing.
@@ -43,10 +43,10 @@ final class TenantThemeResolver
$request = request();
return $request->routeIs('home', 'blog.public.*', 'news.public.*', 'pages.public.*')
|| ! $request->user()
? 'frontend'
: 'backend';
return $request->is('admin', 'admin/*', 'stylist', 'stylist/*')
|| $request->routeIs('admin.*', 'stylist.*')
? 'backend'
: 'frontend';
}
public function variables(): array
+5 -1
View File
@@ -14,7 +14,11 @@ final class Stylist
{
$requested = $this->resolver->resolve($context);
try {
return $this->loader->find($requested);
$theme = $this->loader->find($requested);
if ($context !== null && $theme->type() !== $context) {
throw ThemeNotFoundException::forKey($requested);
}
return $theme;
} catch (ThemeNotFoundException) {
$fallback = $context === 'frontend'
? (string) config('stylist.default_frontend_theme', 'sonardesk')