diff --git a/build.sh b/build.sh index f8470c6..81ebc70 100755 --- a/build.sh +++ b/build.sh @@ -54,9 +54,10 @@ cp -r "$SOURCE_DIR/resources" "$BUILD_DIR/" cp -r "$SOURCE_DIR/routes" "$BUILD_DIR/" cp -r "$SOURCE_DIR/vendor" "$BUILD_DIR/" -# Modules directory (example module for reference) +# Modules directory — example module is dev-only and excluded from releases if [ -d "$SOURCE_DIR/modules" ]; then cp -r "$SOURCE_DIR/modules" "$BUILD_DIR/" + rm -rf "$BUILD_DIR/modules/example" fi # Documentation and legal → build root (flat, alongside code) diff --git a/schneespur/.env.example b/schneespur/.env.example index 43f7442..208abd9 100644 --- a/schneespur/.env.example +++ b/schneespur/.env.example @@ -18,9 +18,10 @@ APP_MAINTENANCE_DRIVER=file BCRYPT_ROUNDS=12 LOG_CHANNEL=stack -LOG_STACK=single +LOG_STACK=daily LOG_DEPRECATIONS_CHANNEL=null -LOG_LEVEL=debug +LOG_LEVEL=warning +LOG_DAILY_DAYS=14 DB_CONNECTION=mysql DB_HOST=127.0.0.1 diff --git a/schneespur/VERSION b/schneespur/VERSION index ee90284..90a27f9 100644 --- a/schneespur/VERSION +++ b/schneespur/VERSION @@ -1 +1 @@ -1.0.4 +1.0.5 diff --git a/schneespur/app/Http/Controllers/Admin/DsgvoAdminController.php b/schneespur/app/Http/Controllers/Admin/DsgvoAdminController.php index 0dff2d5..417946d 100644 --- a/schneespur/app/Http/Controllers/Admin/DsgvoAdminController.php +++ b/schneespur/app/Http/Controllers/Admin/DsgvoAdminController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Controllers\DsgvoOnboardingController; use App\Models\DsgvoConfirmation; use App\Models\Setting; use Illuminate\Http\Request; @@ -18,7 +19,7 @@ class DsgvoAdminController extends Controller $version = (int) Setting::get('dsgvo_template_version', 1); if ($markdown === null) { - $markdown = view('dsgvo.default-template')->render(); + $markdown = view(DsgvoOnboardingController::resolveDefaultTemplateView())->render(); } $previewHtml = Str::markdown($this->replacePlaceholders($markdown), ['html_input' => 'strip']); @@ -82,25 +83,7 @@ class DsgvoAdminController extends Controller private function replacePlaceholders(string $text): string { - $companyName = Setting::get('company_name', ''); - $street = Setting::get('company_street', ''); - $zip = Setting::get('company_zip', ''); - $city = Setting::get('company_city', ''); - $email = Setting::get('company_email', ''); - $dpo = Setting::get('dpo_contact', ''); - $dpoEmail = Setting::get('dpo_email', ''); - - $address = trim("$street, $zip $city", ', '); - - $replacements = [ - '[Firmenname eintragen]' => $companyName ?: '[Firmenname eintragen]', - '[Adresse eintragen]' => $address ?: '[Adresse eintragen]', - '[E-Mail-Adresse eintragen]' => $email ?: '[E-Mail-Adresse eintragen]', - '[DPO-E-Mail-Adresse eintragen]' => $dpoEmail ?: '[DPO-E-Mail-Adresse eintragen]', - '[Datenschutzbeauftragter / Ansprechpartner eintragen]' => $dpo ?: '[Datenschutzbeauftragter / Ansprechpartner eintragen]', - ]; - - return str_replace(array_keys($replacements), array_values($replacements), $text); + return dsgvo_apply_company_placeholders($text); } public function showConfirmation(int $id): View diff --git a/schneespur/app/Http/Controllers/DsgvoOnboardingController.php b/schneespur/app/Http/Controllers/DsgvoOnboardingController.php index ab1726f..333b7db 100644 --- a/schneespur/app/Http/Controllers/DsgvoOnboardingController.php +++ b/schneespur/app/Http/Controllers/DsgvoOnboardingController.php @@ -66,32 +66,22 @@ class DsgvoOnboardingController extends Controller $version = (int) Setting::get('dsgvo_template_version', 1); if ($text === null) { - $text = view('dsgvo.default-template')->render(); + $text = view(self::resolveDefaultTemplateView())->render(); } return [$text, $version]; } + public static function resolveDefaultTemplateView(): string + { + $locale = app()->getLocale(); + $localized = "dsgvo.default-template-{$locale}"; + + return view()->exists($localized) ? $localized : 'dsgvo.default-template'; + } + private function replacePlaceholders(string $text): string { - $companyName = Setting::get('company_name', ''); - $street = Setting::get('company_street', ''); - $zip = Setting::get('company_zip', ''); - $city = Setting::get('company_city', ''); - $email = Setting::get('company_email', ''); - $dpo = Setting::get('dpo_contact', ''); - $dpoEmail = Setting::get('dpo_email', ''); - - $address = trim("$street, $zip $city", ', '); - - $replacements = [ - '[Firmenname eintragen]' => $companyName ?: '[Firmenname eintragen]', - '[Adresse eintragen]' => $address ?: '[Adresse eintragen]', - '[E-Mail-Adresse eintragen]' => $email ?: '[E-Mail-Adresse eintragen]', - '[DPO-E-Mail-Adresse eintragen]' => $dpoEmail ?: '[DPO-E-Mail-Adresse eintragen]', - '[Datenschutzbeauftragter / Ansprechpartner eintragen]' => $dpo ?: '[Datenschutzbeauftragter / Ansprechpartner eintragen]', - ]; - - return str_replace(array_keys($replacements), array_values($replacements), $text); + return dsgvo_apply_company_placeholders($text); } } diff --git a/schneespur/app/Http/Controllers/InstallerController.php b/schneespur/app/Http/Controllers/InstallerController.php index 97b012c..d16e0c3 100644 --- a/schneespur/app/Http/Controllers/InstallerController.php +++ b/schneespur/app/Http/Controllers/InstallerController.php @@ -129,7 +129,7 @@ class InstallerController extends Controller { if ($this->preflightChecker->hasCriticalFailures()) { return redirect()->route('install.preflight') - ->withErrors(['preflight' => 'Kritische Voraussetzungen nicht erfüllt.']); + ->withErrors(['preflight' => __('install.preflight_has_failures')]); } return redirect()->route('install.database'); diff --git a/schneespur/app/Providers/AppServiceProvider.php b/schneespur/app/Providers/AppServiceProvider.php index d27e4b8..1ff4a68 100644 --- a/schneespur/app/Providers/AppServiceProvider.php +++ b/schneespur/app/Providers/AppServiceProvider.php @@ -22,6 +22,7 @@ use App\Services\ModuleManager; use App\Services\RetentionService; use App\Services\SchneespurUpdater; use App\Services\SeasonService; +use App\Services\Translation\BrandedTranslator; use App\Services\Weather\BrightSkyProvider; use App\Services\Weather\MetNorwayProvider; use App\Services\Weather\OpenMeteoApiProvider; @@ -66,6 +67,17 @@ class AppServiceProvider extends ServiceProvider if (empty(config('app.key'))) { config(['app.key' => 'base64:'.base64_encode(random_bytes(32))]); } + + $this->app->extend('translator', function ($translator, $app) { + if ($translator instanceof BrandedTranslator) { + return $translator; + } + $branded = new BrandedTranslator($translator->getLoader(), $translator->getLocale()); + if ($fallback = $translator->getFallback()) { + $branded->setFallback($fallback); + } + return $branded; + }); } /** diff --git a/schneespur/app/Services/ModuleManager.php b/schneespur/app/Services/ModuleManager.php index ebebb03..57c90d4 100644 --- a/schneespur/app/Services/ModuleManager.php +++ b/schneespur/app/Services/ModuleManager.php @@ -56,7 +56,7 @@ class ModuleManager $this->modules[$slug] = $manifest; - Log::info('ModuleManager: module discovered', [ + Log::debug('ModuleManager: module discovered', [ 'slug' => $slug, 'version' => $manifest['version'] ?? 'unknown', ]); @@ -93,6 +93,13 @@ class ModuleManager continue; } + // Reference example module — opt-in only via env var. Ensures the + // bundled dev demo never auto-loads on customer installs even if the + // old folder is still present after upgrading from older releases. + if ($slug === 'example' && ! env('EXAMPLE_MODULE_ENABLED', false)) { + continue; + } + $namespace = $manifest['namespace'] ?? null; $srcPath = ($manifest['path'] ?? '') . '/src'; @@ -128,7 +135,7 @@ class ModuleManager $provider->register(); $provider->boot(); - Log::info('ModuleManager: module booted', [ + Log::debug('ModuleManager: module booted', [ 'slug' => $slug, 'version' => $manifest['version'] ?? 'unknown', ]); @@ -165,7 +172,7 @@ class ModuleManager $this->disabledModules[] = $slug; } - Log::info('ModuleManager: module disabled', ['slug' => $slug]); + Log::debug('ModuleManager: module disabled', ['slug' => $slug]); return true; } diff --git a/schneespur/app/Services/Translation/BrandedTranslator.php b/schneespur/app/Services/Translation/BrandedTranslator.php new file mode 100644 index 0000000..1e1679a --- /dev/null +++ b/schneespur/app/Services/Translation/BrandedTranslator.php @@ -0,0 +1,26 @@ +resolveAppName(); + } + + return parent::get($key, $replace, $locale, $fallback); + } + + private function resolveAppName(): string + { + try { + return brand(); + } catch (\Throwable) { + return (string) config('app.name', 'Schneespur'); + } + } +} diff --git a/schneespur/app/helpers.php b/schneespur/app/helpers.php index 8f963be..42e194b 100644 --- a/schneespur/app/helpers.php +++ b/schneespur/app/helpers.php @@ -23,3 +23,46 @@ function brand(): string default => 'Schneespur', }; } + +/** + * Replace company/DPO placeholders in DSGVO/GDPR template markdown. + * + * Knows both German and English placeholder strings so the same Settings + * values feed into either template language. Missing settings leave the + * placeholder visible so admins notice gaps. + */ +function dsgvo_apply_company_placeholders(string $text): string +{ + $companyName = Setting::get('company_name', ''); + $street = Setting::get('company_street', ''); + $zip = Setting::get('company_zip', ''); + $city = Setting::get('company_city', ''); + $email = Setting::get('company_email', ''); + $dpo = Setting::get('dpo_contact', ''); + $dpoEmail = Setting::get('dpo_email', ''); + + $address = trim("$street, $zip $city", ', '); + + $map = [ + // German placeholders (default-template.blade.php) + '[Firmenname eintragen]' => $companyName, + '[Adresse eintragen]' => $address, + '[E-Mail-Adresse eintragen]' => $email, + '[DPO-E-Mail-Adresse eintragen]' => $dpoEmail, + '[Datenschutzbeauftragter / Ansprechpartner eintragen]' => $dpo, + // English placeholders (default-template-en.blade.php) + '[Company name]' => $companyName, + '[Address]' => $address, + '[Email]' => $email, + '[DPO email]' => $dpoEmail, + '[Data Protection Officer / Contact]' => $dpo, + ]; + + foreach ($map as $token => $value) { + if ($value !== '') { + $text = str_replace($token, $value, $text); + } + } + + return $text; +} diff --git a/schneespur/config/logging.php b/schneespur/config/logging.php index b09cb25..fb30ed8 100644 --- a/schneespur/config/logging.php +++ b/schneespur/config/logging.php @@ -54,21 +54,21 @@ return [ 'stack' => [ 'driver' => 'stack', - 'channels' => explode(',', (string) env('LOG_STACK', 'single')), + 'channels' => explode(',', (string) env('LOG_STACK', 'daily')), 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), + 'level' => env('LOG_LEVEL', 'warning'), 'replace_placeholders' => true, ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), + 'level' => env('LOG_LEVEL', 'warning'), 'days' => env('LOG_DAILY_DAYS', 14), 'replace_placeholders' => true, ], diff --git a/schneespur/modules/example/module.json b/schneespur/modules/example/module.json index f35180d..1451911 100644 --- a/schneespur/modules/example/module.json +++ b/schneespur/modules/example/module.json @@ -3,7 +3,8 @@ "version": "1.0.0", "namespace": "Schneespur\\Module\\Example", "service_provider": "Schneespur\\Module\\Example\\ExampleServiceProvider", - "description": "Reference module demonstrating all extension points (nav, widget, event, settings, route).", + "description": "Reference module demonstrating all extension points (nav, widget, event, settings, route). Dev-only — opt in via EXAMPLE_MODULE_ENABLED=true in .env.", "min_schneespur_version": "1.0.0", - "requires_permissions": [] + "requires_permissions": [], + "default_enabled": false } diff --git a/schneespur/modules/example/src/ExampleServiceProvider.php b/schneespur/modules/example/src/ExampleServiceProvider.php index 3d769d1..7b23f6f 100644 --- a/schneespur/modules/example/src/ExampleServiceProvider.php +++ b/schneespur/modules/example/src/ExampleServiceProvider.php @@ -19,6 +19,10 @@ class ExampleServiceProvider extends ServiceProvider public function boot(): void { + if (! $this->shouldBoot()) { + return; + } + $this->loadViewsFrom(__DIR__ . '/../resources/views', 'example-module'); $this->registerNavigation(); @@ -28,6 +32,15 @@ class ExampleServiceProvider extends ServiceProvider $this->registerRoutes(); } + /** + * Reference module — only loads when explicitly enabled. + * Devs can enable for local exploration via .env: EXAMPLE_MODULE_ENABLED=true + */ + protected function shouldBoot(): bool + { + return (bool) env('EXAMPLE_MODULE_ENABLED', false); + } + protected function registerNavigation(): void { $nav = $this->app->make(NavigationRegistry::class); diff --git a/schneespur/resources/views/admin/jobs/manual/create.blade.php b/schneespur/resources/views/admin/jobs/manual/create.blade.php index 8318bc5..77a60e6 100644 --- a/schneespur/resources/views/admin/jobs/manual/create.blade.php +++ b/schneespur/resources/views/admin/jobs/manual/create.blade.php @@ -22,9 +22,9 @@ @endphp