diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 66ffb66..cd75660 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,9 +9,5 @@ class AppServiceProvider extends ServiceProvider { public function boot(): void { - Blade::anonymousComponentPath( - resource_path('views/components/layouts'), - 'layouts' - ); } } diff --git a/modules/Licensing/Providers/LicensingServiceProvider.php b/modules/Licensing/Providers/LicensingServiceProvider.php index c514a59..bb43592 100644 --- a/modules/Licensing/Providers/LicensingServiceProvider.php +++ b/modules/Licensing/Providers/LicensingServiceProvider.php @@ -41,6 +41,12 @@ class LicensingServiceProvider extends ServiceProvider */ public function boot() : void { + + \Illuminate\Support\Facades\Blade::anonymousComponentPath( + base_path('modules/Licensing/Resources/views/components/layouts'), + 'layouts' + ); + RateLimiter::for('login', fn (Request $request) => Limit::perMinute(5) ->by(strtolower((string) $request->input('email')).'|'.$request->ip())); diff --git a/modules/Licensing/Resources/views/components/layouts/app.blade.php b/modules/Licensing/Resources/views/components/layouts/app.blade.php new file mode 100644 index 0000000..8f13014 --- /dev/null +++ b/modules/Licensing/Resources/views/components/layouts/app.blade.php @@ -0,0 +1,9 @@ +@props(['title' => null]) + +@extends('layouts.app') + +@section('title', $title ?? config('app.name')) + +@section('content') + {{ $slot }} +@endsection diff --git a/resources/views/emails/license-key-text.blade.php b/modules/Licensing/Resources/views/emails/license-key-text.blade.php similarity index 100% rename from resources/views/emails/license-key-text.blade.php rename to modules/Licensing/Resources/views/emails/license-key-text.blade.php diff --git a/resources/views/emails/license-key.blade.php b/modules/Licensing/Resources/views/emails/license-key.blade.php similarity index 100% rename from resources/views/emails/license-key.blade.php rename to modules/Licensing/Resources/views/emails/license-key.blade.php diff --git a/modules/Licensing/Services/LicenseDeliveryService.php b/modules/Licensing/Services/LicenseDeliveryService.php index 053600d..285e0d3 100755 --- a/modules/Licensing/Services/LicenseDeliveryService.php +++ b/modules/Licensing/Services/LicenseDeliveryService.php @@ -41,7 +41,7 @@ final class LicenseDeliveryService 'downloadUrl' => route('release'), ]; - Mail::send(['html' => 'emails.license-key', 'text' => 'emails.license-key-text'], $data, function ($message) use ($recipient): void { + Mail::send(['html' => 'licensing::emails.license-key', 'text' => 'licensing::emails.license-key-text'], $data, function ($message) use ($recipient): void { $message->to($recipient)->subject('Your SonarDesk licence key'); }); diff --git a/modules/Stylist/Http/Controllers/ThemeManagementController.php.bak-20261020-103617 b/modules/Stylist/Http/Controllers/ThemeManagementController.php.bak-20261020-103617 deleted file mode 100644 index 3481218..0000000 --- a/modules/Stylist/Http/Controllers/ThemeManagementController.php.bak-20261020-103617 +++ /dev/null @@ -1,90 +0,0 @@ -find($theme); - abort_unless(in_array($item->type(), ['frontend', 'backend'], true), 422, 'Only frontend and backend themes can be activated.'); - - $settings->put($item->type().'_theme', $item->key(), 'site', 'string', true); - - return back()->with('status', $item->name().' is now the active '.$item->type().' theme.'); - } - - public function preview(string $theme, Loader $loader, ThemeViewResolver $views) - { - $item = $loader->find($theme); - - return $views->makeForTheme($item->key(), $item->entryView(), [ - 'themePreview' => true, - 'previewTheme' => $item, - ]); - } - - 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); - abort_if(in_array($item->key(), ['sonardesk', 'sonardesk-admin'], true), 403, 'Bundled themes cannot be removed.'); - $settings = app(SiteSettingsRepository::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.'); - } - - 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))]); - } -} diff --git a/modules/Stylist/Providers/StylistServiceProvider.php.bak-20261020-103220 b/modules/Stylist/Providers/StylistServiceProvider.php.bak-20261020-103220 deleted file mode 100644 index 2491f70..0000000 --- a/modules/Stylist/Providers/StylistServiceProvider.php.bak-20261020-103220 +++ /dev/null @@ -1,215 +0,0 @@ -app->register(RouteServiceProvider::class); - - $this->mergeConfigFrom(module_path(ModuleInfo::name(), 'Config/config.php'), ModuleInfo::nameLower()); - - $this->app->singleton('stylist', fn ($app) => $app->make(Stylist::class)); - $this->app->singleton('stylist.html', fn ($app) => $app->make(ThemeHtmlBuilder::class)); - $this->app->singleton('stylist.seo', fn ($app) => $app->make(ThemeSeoBuilder::class)); - } - - public function boot(): void - { - $this->registerViews(); - $this->registerThemeViews(); - $this->registerTranslations(); - $this->registerPublishing(); - $this->registerHelpers(); - $this->registerBlade(); - - if ($this->app->runningInConsole()) { - $this->commands([ - PublishAssetsCommand::class, - ValidateThemeCommand::class, - InspectThemesCommand::class, - FindDuplicateViewsCommand::class, - ThemeDoctorCommand::class, - CreateThemeCommand::class, - ]); - } - - $this->loadMigrationsFrom(module_path(ModuleInfo::name(), 'Database/Migrations')); - - View::composer('*', function ($view): void { - if (! $this->app->bound(Stylist::class)) { - return; - } - - $view->with('activeTheme', $this->app->make(Stylist::class)->current()); - }); - - - View::composer('stylist-theme-saasland::*', function ($view): void { - $theme = $this->app->make(Loader::class)->find('saasland'); - $settings = $this->app->make(ThemeSettings::class)->get($theme); - $siteSettings = $this->app->make(SiteSettingsRepository::class)->site(); - $navigation = $this->app->make(FrontendNavigation::class); - $homepage = $this->app->make(FrontendContent::class)->homepage($settings); - - $view->with([ - 'siteSettings' => $siteSettings, - 'siteTitle' => $siteSettings['site_name'], - 'siteDescription' => $siteSettings['site_description'], - 'logoUrl' => $siteSettings['logo_url'] ?: null, - 'facebookUrl' => $siteSettings['facebook_url'] ?: null, - 'xUrl' => $siteSettings['x_url'] ?: null, - 'linkedinUrl' => $siteSettings['linkedin_url'] ?: null, - 'primaryNavigation' => $navigation->primary(), - 'footerNavigation' => $navigation->footer(), - 'themeSettings' => $settings, - ...$homepage, - ]); - }); - } - - private function registerViews(): void - { - $sourcePath = module_path(ModuleInfo::name(), 'Resources/views'); - $viewPath = resource_path('views/modules/'.ModuleInfo::nameLower()); - - $this->publishes([$sourcePath => $viewPath], ['views', 'stylist-module-views']); - $this->loadViewsFrom([$viewPath, $sourcePath], ModuleInfo::nameLower()); - } - - private function registerThemeViews(): void - { - foreach ($this->themeRoots() as $root) { - if (! is_dir($root)) { - logger()->warning('Stylist theme root does not exist', [ - 'root' => $root, - ]); - - continue; - } - - $themeDirectories = glob($root.'/*', GLOB_ONLYDIR) ?: []; - - foreach ($themeDirectories as $themeDirectory) { - $themeViewsPath = $themeDirectory.'/views'; - - if (! is_dir($themeViewsPath)) { - continue; - } - - $themeKey = strtolower(basename($themeDirectory)); - - // Themes always live outside the module, normally in resources/themes. - // Register the canonical namespace and the legacy namespace as an alias - // to the same application-owned directory. The alias keeps existing - // routes and Blade templates working without reintroducing module themes. - $namespaces = [ - "stylist-theme-{$themeKey}", - "stylist-module-theme-{$themeKey}", - ]; - - $componentsPath = $themeViewsPath.'/components'; - - // The common theme is the application-wide owner of layouts, - // authentication, settings and shared components. Livewire 4 - // resolves its default full-page layout through layouts::app, - // so point that compatibility namespace at the common theme. - if ($themeKey === 'common') { - $layoutsPath = $componentsPath.'/layouts'; - - if (is_dir($layoutsPath)) { - View::addNamespace('layouts', $layoutsPath); - Blade::anonymousComponentPath($layoutsPath, 'layouts'); - } - - View::addNamespace('common', $themeViewsPath); - - if (is_dir($componentsPath)) { - Blade::anonymousComponentPath($componentsPath, 'common'); - } - } - - foreach ($namespaces as $namespace) { - View::addNamespace($namespace, $themeViewsPath); - - if (is_dir($componentsPath)) { - Blade::anonymousComponentPath($componentsPath, $namespace); - } - } - } - } - } - - private function normalisePath(string $path): string - { - return rtrim( - str_replace('\\', '/', realpath($path) ?: $path), - '/' - ); - } - - /** @return array */ - private function themeRoots(): array - { - return array_values(array_unique(array_filter([ - (string) config('stylist.themes_path'), - ]))); - } - - private function registerTranslations(): void - { - $langPath = resource_path('lang/modules/'.ModuleInfo::nameLower()); - $sourcePath = module_path(ModuleInfo::name(), 'Resources/lang'); - - $this->loadTranslationsFrom(is_dir($langPath) ? $langPath : $sourcePath, ModuleInfo::nameLower()); - } - - private function registerPublishing(): void - { - $this->publishes([ - module_path(ModuleInfo::name(), 'Config/config.php') => config_path('stylist.php'), - ], ['config', 'stylist-config']); - - } - - private function registerHelpers(): void - { - foreach (glob(module_path(ModuleInfo::name(), 'Helpers/*.php')) ?: [] as $filename) { - require_once $filename; - } - } - - private function registerBlade(): void - { - Blade::directive('themeVariables', fn () => 'variables(); ?>'); - Blade::directive('themeStylesheet', fn ($expression) => 'stylesheet('.($expression ?: 'null').'); ?>'); - Blade::directive('themeScript', fn ($expression) => 'script('.($expression ?: 'null').'); ?>'); - Blade::directive('themeSwitcher', fn () => 'switcher(); ?>'); - Blade::directive('themeSeo', fn ($expression) => 'render('.($expression ?: '[]').'); ?>'); - } -} diff --git a/modules/Stylist/Resources/views/index.blade.php.bak-20261020-102848 b/modules/Stylist/Resources/views/index.blade.php.bak-20261020-102848 deleted file mode 100644 index 8c4abf2..0000000 --- a/modules/Stylist/Resources/views/index.blade.php.bak-20261020-102848 +++ /dev/null @@ -1,86 +0,0 @@ -@extends('stylist::layouts.app') - -@section('content') -
- Administration -

Stylist

-

Install, configure, validate and export self-contained themes.

-
- - - @if (session('status')) -
{{ session('status') }}
- @endif - @if ($errors->any()) -
{{ $errors->first() }}
- @endif - -
- @csrf - - -
- -
- @foreach ($themes as $item) - @php($saved = $settingsService->get($item)) - @php($variant = $settingsService->variant($item)) - @php($issues = $accessibility->validate($item)) - @php($appearance = $settingsService->appearance($item)) - @php($accent = $settingsService->accent($item)) -
-
- @if ($item->preview() && $item->has($item->preview())) - {{ $item->name() }} theme preview - @else -
Add preview.webp to this theme
- @endif -
-
-

{{ $item->name() }}

v{{ $item->version() }}@if($item->author()) · {{ $item->author() }}@endif

@if(($item->type() === 'frontend' && $item->key() === $activeFrontendTheme) || ($item->type() === 'backend' && $item->key() === $activeBackendTheme))Active {{ ucfirst($item->type()) }}@endif
- @if($item->description())

{{ $item->description() }}

@endif - @if($item->parent())

Child of {{ $item->parent() }}

@endif -
- @if(in_array($item->type(), ['frontend', 'backend'], true)) -
@csrf
- Preview - @endif - Export ZIP -
@csrf
- @if(str_starts_with(realpath($item->path()) ?: '', realpath(config('stylist.themes_path')) ?: '/__never__')) -
@csrf @method('DELETE')
- @endif -
-
- Theme settings -
@csrf @method('PUT') - @if($item->variants()) - - @endif - @if($item->supports('dark_mode') || $item->appearances()) - - @endif - @if($item->accents()) -
Accent colour
@foreach($item->accents() as $name => $definition)@endforeach
- @endif - @foreach($item->settingsSchema() as $key => $definition) - @php($definition = is_array($definition) ? $definition : ['default' => $definition]) - - @endforeach - -
-
-
Accessibility: {{ count($issues) ? count($issues).' issue(s)' : 'passed static checks' }}
-
-
- @endforeach -
- -@endsection diff --git a/modules/Stylist/Resources/views/index.blade.php.bak-20261020-103220 b/modules/Stylist/Resources/views/index.blade.php.bak-20261020-103220 deleted file mode 100644 index c667fc3..0000000 --- a/modules/Stylist/Resources/views/index.blade.php.bak-20261020-103220 +++ /dev/null @@ -1,473 +0,0 @@ -@extends('layouts.app', ['title' => 'Stylist — SonarDesk Administration']) - -@section('content') - -
- -
-
- Administration / Appearance -

Stylist

-

- Manage frontend and administration themes independently. - Install, configure, preview and export complete themes. -

-
- - - Back to administration - -
- - @if(session('status')) -
{{ session('status') }}
- @endif - - @if($errors->any()) -
{{ $errors->first() }}
- @endif - -
-
- Installed themes - {{ count($themes) }} - Available designs -
- -
- Frontend theme - {{ $activeFrontendTheme }} - Public website -
- -
- Backend theme - {{ $activeBackendTheme }} - Administration interface -
-
- -
-
-
- Theme library -

Install a theme

-

Upload a self-contained theme package in ZIP format.

-
-
- -
- - @csrf - - - - -
-
- -
- -
-
- Appearance -

Installed themes

-

Frontend and backend themes can be activated independently.

-
-
- -
- - @foreach($themes as $item) - - @php - $saved = $settingsService->get($item); - $variant = $settingsService->variant($item); - $appearance = $settingsService->appearance($item); - $accent = $settingsService->accent($item); - $issues = $accessibility->validate($item); - - $isActive = - ($item->type() === 'frontend' && $item->key() === $activeFrontendTheme) - || ($item->type() === 'backend' && $item->key() === $activeBackendTheme); - - $isBundled = in_array( - $item->key(), - ['sonardesk', 'sonardesk-admin'], - true - ); - - $themeRoot = realpath(config('stylist.themes_path')); - $themePath = realpath($item->path()); - - $isInstalled = $themeRoot - && $themePath - && str_starts_with( - $themePath, - rtrim($themeRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR - ); - @endphp - -
- -
- - @if($item->preview() && $item->has($item->preview())) - - {{ $item->name() }} preview - - @else - -
- ◈ - {{ $item->name() }} - No preview image available -
- - @endif - -
- -
- -
- -
- - {{ ucfirst($item->type()) }} theme - - -

{{ $item->name() }}

- -

- Version {{ $item->version() }} - @if($item->author()) - · {{ $item->author() }} - @endif -

-
- - @if($isActive) - - Active - - @endif - -
- - @if($item->description()) -

- {{ $item->description() }} -

- @endif - - @if($item->parent()) -

- Based on {{ $item->parent() }} -

- @endif - -
- - @if(in_array($item->type(), ['frontend', 'backend'], true)) - - @unless($isActive) - -
- @csrf - - -
- - @endunless - - - Preview - - - @endif - - - Export ZIP - - -
- @csrf - - -
- -
- -
- - - Theme configuration - ⌄ - - -
- - @csrf - @method('PUT') - - @if($item->variants()) - - - - @endif - - @if($item->supports('dark_mode') || $item->appearances()) - - - - @endif - - @if($item->accents()) - -
- Accent colour - - @foreach($item->accents() as $name => $definition) - - - - @endforeach - -
- - @endif - - @foreach($item->settingsSchema() as $key => $definition) - - @php - $definition = is_array($definition) - ? $definition - : ['default' => $definition]; - - $fieldType = $definition['type'] ?? 'text'; - $fieldLabel = $definition['label'] ?? str($key)->headline(); - $fieldValue = $saved[$key] ?? ($definition['default'] ?? ''); - @endphp - -
- - - - @switch($fieldType) - - @case('color') - - - @break - - @case('textarea') - - - @break - - @case('checkbox') - - - - - @break - - @default - - - - @endswitch - -
- - @endforeach - - - -
- -
- - - -
- -
- - @endforeach - -
- -
- -
- - - -@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php deleted file mode 100644 index 0ebefd3..0000000 --- a/resources/views/layouts/app.blade.php +++ /dev/null @@ -1,530 +0,0 @@ - - - - - - - - @php - $seoSiteName = \App\Models\SiteSetting::valueFor('seo_site_name', 'SonarDesk'); - $seoDefaultTitle = \App\Models\SiteSetting::valueFor('seo_default_title', 'SonarDesk — Native SonarQube Monitoring for macOS'); - $seoDefaultDescription = \App\Models\SiteSetting::valueFor('seo_default_description', 'Native SonarQube monitoring for macOS. Track projects, quality gates, bugs, vulnerabilities and code smells from one professional desktop app.'); - $pageTitle = $title ?? $seoDefaultTitle; - $pageDescription = $description ?? $seoDefaultDescription; - $pageKeywords = $keywords ?? \App\Models\SiteSetting::valueFor('seo_default_keywords'); - $canonicalBase = rtrim(\App\Models\SiteSetting::valueFor('seo_canonical_base_url', config('app.url')), '/'); - $canonicalUrl = $canonical ?? ($canonicalBase . request()->getPathInfo()); - $socialImage = $ogImage ?? \App\Models\SiteSetting::valueFor('seo_default_og_image', url('/icons/og-sonardesk.png')); - $ogTitle = $ogTitle ?? $pageTitle; - $ogDescription = $ogDescription ?? $pageDescription; - $robotsIndex = $robotsIndex ?? true; - $robotsFollow = $robotsFollow ?? true; - $productionIndexing = app()->environment('production') && \App\Models\SiteSetting::booleanFor('seo_robots_enabled', true); - $robots = $productionIndexing ? (($robotsIndex ? 'index' : 'noindex').','.($robotsFollow ? 'follow' : 'nofollow').',max-image-preview:large,max-snippet:-1,max-video-preview:-1') : 'noindex,nofollow'; - $twitterSite = \App\Models\SiteSetting::valueFor('seo_twitter_site'); - $googleVerification = \App\Models\SiteSetting::valueFor('seo_google_site_verification'); - $organizationName = \App\Models\SiteSetting::valueFor('seo_organization_name', $seoSiteName); - $organizationLogo = \App\Models\SiteSetting::valueFor('seo_organization_logo', url('/icons/sonardesk-icon-512.png')); - $schema = [ - '@context' => 'https://schema.org', '@type' => 'SoftwareApplication', 'name' => $seoSiteName, - 'applicationCategory' => 'DeveloperApplication', 'operatingSystem' => 'macOS', 'url' => route('home'), - 'description' => $seoDefaultDescription, 'publisher' => ['@type' => 'Organization', 'name' => $organizationName, 'logo' => ['@type' => 'ImageObject', 'url' => $organizationLogo]], - ]; - @endphp - - {{ $pageTitle }} - - @if($pageKeywords)@endif - - - @if($googleVerification)@endif - - - - - - - - - - - - - - - - - - - - - - - - - @if($twitterSite)@endif - - - - - - - @stack('head') - - - - -@hasSection('fullbleed') - @yield('fullbleed') -@else -
- @if(session('status'))
{{ session('status') }}
@endif - @if($errors->any())
{{ $errors->first() }}
@endif - @yield('content') -
-@endif - - - - - - - - - - - - diff --git a/resources/views/seo/sitemap.blade.php b/resources/views/seo/sitemap.blade.php deleted file mode 100644 index 517eb1c..0000000 --- a/resources/views/seo/sitemap.blade.php +++ /dev/null @@ -1,18 +0,0 @@ -{!! '' !!} - -@foreach($static as $item) - - {{ route($item['route']) }} - {{ $item['frequency'] }} - {{ $item['priority'] }} - -@endforeach -@foreach($pages as $page) - - {{ route('cms.show', $page->slug) }} - {{ $page->updated_at->toAtomString() }} - {{ $page->sitemap_change_frequency }} - {{ number_format((float) $page->sitemap_priority, 1, '.', '') }} - -@endforeach -