New Branch Structure
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Stylist\Http\Controllers;
|
||||
|
||||
use Illuminate\Routing\Controller;
|
||||
use Modules\Stylist\Services\ThemeAccessibilityValidator;
|
||||
use Modules\Stylist\Services\ThemeSettings;
|
||||
use Modules\Stylist\Theme\Stylist;
|
||||
use Modules\Stylist\Theme\UrlGenerator;
|
||||
|
||||
final class StylistController extends Controller
|
||||
{
|
||||
public function __invoke(Stylist $stylist, ThemeSettings $settings, ThemeAccessibilityValidator $accessibility, UrlGenerator $urls)
|
||||
{
|
||||
return view('stylist::index', [
|
||||
'theme' => $stylist->current(), 'themes' => $stylist->all(), 'variables' => $stylist->variables(),
|
||||
'settingsService' => $settings, 'accessibility' => $accessibility, 'urls' => $urls,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Stylist\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Modules\Stylist\Theme\Loader;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
final class ThemeAssetController extends Controller
|
||||
{
|
||||
private const ALLOWED_ROOTS = ['css', 'js', 'images', 'fonts', 'icons', 'assets'];
|
||||
|
||||
private const ALLOWED_EXTENSIONS = [
|
||||
'css', 'js', 'mjs', 'map', 'svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif',
|
||||
'ico', 'woff', 'woff2', 'ttf', 'otf', 'eot', 'json', 'xml', 'txt', 'webmanifest',
|
||||
];
|
||||
|
||||
public function __invoke(string $theme, string $path, Loader $loader): BinaryFileResponse|Response
|
||||
{
|
||||
abort_if(str_contains($path, "\0"), 404);
|
||||
|
||||
$normalised = str_replace('\\', '/', ltrim($path, '/'));
|
||||
$root = explode('/', $normalised, 2)[0] ?? '';
|
||||
$extension = strtolower(pathinfo($normalised, PATHINFO_EXTENSION));
|
||||
|
||||
abort_unless(in_array($root, self::ALLOWED_ROOTS, true), 404);
|
||||
abort_unless(in_array($extension, self::ALLOWED_EXTENSIONS, true), 404);
|
||||
|
||||
$loadedTheme = $loader->find($theme);
|
||||
$themeRoot = realpath($loadedTheme->path());
|
||||
abort_unless($themeRoot !== false, 404);
|
||||
|
||||
$asset = realpath($themeRoot.DIRECTORY_SEPARATOR.$normalised);
|
||||
|
||||
abort_unless(
|
||||
$asset !== false
|
||||
&& str_starts_with($asset, $themeRoot.DIRECTORY_SEPARATOR)
|
||||
&& is_file($asset),
|
||||
404
|
||||
);
|
||||
|
||||
$mime = match ($extension) {
|
||||
'css' => 'text/css; charset=UTF-8',
|
||||
'js', 'mjs' => 'application/javascript; charset=UTF-8',
|
||||
'svg' => 'image/svg+xml',
|
||||
'json', 'map', 'webmanifest' => 'application/json',
|
||||
'woff' => 'font/woff',
|
||||
'woff2' => 'font/woff2',
|
||||
'ttf' => 'font/ttf',
|
||||
'otf' => 'font/otf',
|
||||
default => mime_content_type($asset) ?: 'application/octet-stream',
|
||||
};
|
||||
|
||||
return response()->file($asset, [
|
||||
'Content-Type' => $mime,
|
||||
'Cache-Control' => app()->isProduction()
|
||||
? 'public, max-age=31536000, immutable'
|
||||
: 'no-cache, private',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'",
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Stylist\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Modules\Stylist\Services\ThemeAccessibilityValidator;
|
||||
use Modules\Stylist\Services\ThemeOptimizer;
|
||||
use Modules\Stylist\Services\ThemePackageManager;
|
||||
use Modules\Stylist\Services\ThemeSettings;
|
||||
use Modules\Stylist\Theme\Loader;
|
||||
|
||||
final class ThemeManagementController extends Controller
|
||||
{
|
||||
public function settings(Request $request, string $theme, Loader $loader, ThemeSettings $settings)
|
||||
{
|
||||
$item = $loader->find($theme);
|
||||
$settings->save(
|
||||
$item,
|
||||
(array) $request->input('settings', []),
|
||||
(string) $request->input('variant', 'light'),
|
||||
$request->string('appearance')->toString() ?: null,
|
||||
$request->string('accent')->toString() ?: null,
|
||||
);
|
||||
return back()->with('status', 'Theme settings saved.');
|
||||
}
|
||||
public function install(Request $request, ThemePackageManager $packages)
|
||||
{
|
||||
$request->validate(['theme' => ['required','file','mimes:zip','max:51200']]);
|
||||
$theme = $packages->install($request->file('theme'));
|
||||
return back()->with('status', $theme->name().' installed.');
|
||||
}
|
||||
public function uninstall(string $theme, Loader $loader, ThemePackageManager $packages)
|
||||
{
|
||||
$item = $loader->find($theme); $packages->uninstall($item);
|
||||
return back()->with('status', $item->name().' uninstalled.');
|
||||
}
|
||||
public function export(string $theme, Loader $loader, ThemePackageManager $packages)
|
||||
{
|
||||
$item = $loader->find($theme); $path = $packages->export($item);
|
||||
return response()->download($path)->deleteFileAfterSend();
|
||||
}
|
||||
public function optimize(string $theme, Loader $loader, ThemeOptimizer $optimizer)
|
||||
{
|
||||
$item = $loader->find($theme); $result = $optimizer->optimize($item);
|
||||
return back()->with('status', "Optimised {$result['css']} CSS, {$result['js']} JS and {$result['images']} images.");
|
||||
}
|
||||
public function validateTheme(string $theme, Loader $loader, ThemeAccessibilityValidator $validator)
|
||||
{
|
||||
return response()->json(['issues' => $validator->validate($loader->find($theme))]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user