From 97a7fdc36b354d231028d3e8b480b18e4d052685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sat, 27 May 2023 16:15:42 +0200 Subject: [PATCH] Fixes #1 Minicrud + Cookies viemo youtube + recaptcha + Matomo --- .env.example | 11 ++ README.md | 1 + app/Enums/InputType.php | 8 +- app/Http/Middleware/SecureHeaders.php | 17 +- .../Requests/Bo/UpdateMiniCrudRequest.php | 68 ++++++-- app/Http/Requests/Fo/.gitkeep | 0 app/Http/Requests/Fo/ContactRequest.php | 10 ++ app/Lib/Helpers/ToolboxHelper.php | 58 ++++++- app/Models/Concept.php | 13 +- app/Traits/Models/MiniCrudModel.php | 1 + config/cors.php | 4 +- config/services.php | 9 ++ package-lock.json | 12 +- package.json | 1 + resources/assets/images/cookie-icon.png | Bin 0 -> 4686 bytes resources/lang/de/cookie-notice.php | 48 ++++++ resources/lang/en/cookie-notice.php | 48 ++++++ resources/sass/fo/fo.scss | 19 +++ resources/sass/fo/utilities/_variables.scss | 15 +- resources/ts/fo.ts | 4 + resources/ts/fo/common.ts | 45 ++++++ resources/ts/index.d.ts | 2 + .../bo/modules/minicrud-form-inputs.blade.php | 8 +- resources/views/fo/layout-frame.blade.php | 4 +- resources/views/fo/layouts/footer.blade.php | 1 + .../views/fo/modules/video-frame.blade.php | 14 ++ .../views/fo/pages/concepts/bloc.blade.php | 60 ++++--- resources/views/fo/pages/contact.blade.php | 55 ++++++- resources/views/fo/pages/frame.blade.php | 16 ++ resources/views/fo/pages/home.blade.php | 11 +- .../standard.blade.php | 92 +++++++++++ .../wenndanntruc.blade.php | 80 +++++++++ .../views/fo/vendor/ldcweb-cookies.blade.php | 152 ++++++++++++++++++ routes/web-fo.php | 4 + 34 files changed, 823 insertions(+), 68 deletions(-) delete mode 100644 app/Http/Requests/Fo/.gitkeep create mode 100644 resources/assets/images/cookie-icon.png create mode 100644 resources/lang/de/cookie-notice.php create mode 100644 resources/lang/en/cookie-notice.php create mode 100644 resources/views/fo/modules/video-frame.blade.php create mode 100644 resources/views/fo/pages/frame.blade.php create mode 100644 resources/views/fo/vendor/cookie-notice-templates/standard.blade.php create mode 100644 resources/views/fo/vendor/cookie-notice-templates/wenndanntruc.blade.php create mode 100644 resources/views/fo/vendor/ldcweb-cookies.blade.php diff --git a/.env.example b/.env.example index 35936ecf..9a58106c 100644 --- a/.env.example +++ b/.env.example @@ -34,4 +34,15 @@ MAIL_FROM_NAME="${APP_NAME}" # Sentry SENTRY_LARAVEL_DSN= +# MATOMO +MATOMO_SITE_ID= + +# GOOGLE +# - ANALYTICS +ANALYTICS_GOOGLE_ID= +# - RECAPTCHA +GOOGLE_RECAPTCHA_PUBLIC=6LeQxHoUAAAAAGryIFfrnyvdqVm0KNBaIUCQDdf9 +GOOGLE_RECAPTCHA_PRIVATE=6LeQxHoUAAAAAOudkLY90DerM_ZtlaFfzTlJJvIo +GOOGLE_RECAPTCHA_VERSION=2 +# - MAPS GOOGLE_MAPS_KEY= diff --git a/README.md b/README.md index ffa98cfb..24b8f462 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ sentry:test - Add ldc/web-admin-secure-laravel & ldc/web-status-checker-laravel after bump laravel-10 - Api pour storefinder cassée rules.wo-gilt-was.de - passer le script en TS et le style pas en inline +- Meettre le bump cookie notice pour Matomo diff --git a/app/Enums/InputType.php b/app/Enums/InputType.php index 6d3dddbc..0b59d298 100644 --- a/app/Enums/InputType.php +++ b/app/Enums/InputType.php @@ -21,6 +21,10 @@ enum InputType: int case string = 11; case text = 12; case url = 13; - case videoVimeo = 14; - case videoYoutube = 15; + /** Vimeo only */ + case videoVimeo = 14; + /** Youtube only */ + case videoYoutube = 15; + /** Youtube or Vimeo. */ + case video = 16; } diff --git a/app/Http/Middleware/SecureHeaders.php b/app/Http/Middleware/SecureHeaders.php index ed0004fe..7dc15269 100644 --- a/app/Http/Middleware/SecureHeaders.php +++ b/app/Http/Middleware/SecureHeaders.php @@ -48,22 +48,24 @@ class SecureHeaders $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;'); $cspPolicies = [ - "default-src 'none';", "manifest-src 'self';", - "prefetch-src 'self' https://cloud.ccm19.de;", + "prefetch-src 'self';", // * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html "connect-src 'self' blob: " . \implode(' ', [ + 'https://player.vimeo.com', 'https://rules.wo-gilt-was.de', - 'https://cloud.ccm19.de', + // * MATOMO + 'https://yl.s-web.fr', 'https://region1.google-analytics.com', - 'https://maps.googleapis.com', + 'https://maps.googleapis.com' ]) . ';', "img-src * data: blob:;", - 'frame-src ' . implode(' ', [ + "frame-src 'self' " . implode(' ', [ 'https://www.youtube.com', 'https://www.google.com/recaptcha/', 'https://recaptcha.google.com/recaptcha/', - 'https://www.google.com/maps/' + 'https://www.google.com/maps/', + 'https://player.vimeo.com' ]) . ';', "font-src 'self' data: " . \implode(' ', [ 'https://fonts.gstatic.com', @@ -75,10 +77,8 @@ class SecureHeaders 'https://cdnjs.cloudflare.com', 'https://use.typekit.net', 'https://p.typekit.net', - 'https://cloud.ccm19.de', // * File Manager 'https://fonts.googleapis.com', - 'https://cdn.jsdelivr.net', 'https://use.fontawesome.com', 'https://fonts.googleapis.com' ]) . ';', @@ -89,7 +89,6 @@ class SecureHeaders // phpcs:ignore Generic.Files.LineLength.TooLong "script-src 'self' 'unsafe-eval' {$jsDev} " . \implode(' ', [ 'https: blob:', - 'https://cloud.ccm19.de', 'https://www.googletagmanager.com', 'https://cdnjs.cloudflare.com', "$scheme://www.google-analytics.com", diff --git a/app/Http/Requests/Bo/UpdateMiniCrudRequest.php b/app/Http/Requests/Bo/UpdateMiniCrudRequest.php index 969a6c7b..f845c96f 100644 --- a/app/Http/Requests/Bo/UpdateMiniCrudRequest.php +++ b/app/Http/Requests/Bo/UpdateMiniCrudRequest.php @@ -50,6 +50,18 @@ abstract class UpdateMiniCrudRequest extends FormRequest "data.$iptName.value" => $this->compileValidate('regex:%([^"&?\/\s]{11})%i', $input) ]); break; + case InputType::videoVimeo->value: + $rules = array_merge($rules, [ + "data.$iptName.value" => $this->compileValidate('regex:%([0-9]{8,10})%i', $input) + ]); + break; + case InputType::video->value: + $rules = array_merge($rules, [ + "data.$iptName.value" => $this->compileValidate([ + 'regex:%([0-9]{8,10}|[^"&?\/\s]{11})%i' + ], $input) + ]); + break; case InputType::string->value: $rules = array_merge($rules, [ "data.$iptName.value" => $this->compileValidate('required|string|min:3', $input) @@ -57,7 +69,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest break; case InputType::text->value: $mergeRules = $this->compileValidate('required|string|min:15', $input); - $mergeRules = collect(\explode('|', $mergeRules)) + $mergeRules = collect($mergeRules) ->map(function ($rule) { if (\strpos($rule, 'max:') !== false) { $max = \intval(str_replace('max:', '', $rule)); @@ -155,24 +167,54 @@ abstract class UpdateMiniCrudRequest extends FormRequest /** * Undocumented function * - * @param string $inputTypeValidate - * @param array $input - * @return string + * @param string|array $inputTypeValidate + * @param array $input + * @return array */ - public function compileValidate(string $inputTypeValidate, array $input): string + public function compileValidate(string|array $inputTypeValidate, array $input): array { - if (!isset($input['validate']) or !\is_string($input['validate'])) { - return $inputTypeValidate; + if (\is_string($inputTypeValidate)) { + $inputTypeValidate = explode('|', $inputTypeValidate); } - /** @var array */ - $inputValidate = explode('|', $input['validate']); - $out = explode('|', $inputTypeValidate); + /** @var array|string */ + $inputValidate = \collect($input)->get('validate', $input); + $inputValidate = \is_string($inputValidate) ? explode('|', $inputValidate) : $inputValidate; foreach ($inputValidate as $validate) { - if (!\in_array($validate, $out)) { - $out[] = $validate; + if (!\in_array($validate, $inputTypeValidate)) { + $inputTypeValidate[] = $validate; } } - return \implode('|', $out); + + $test = \collect($inputTypeValidate)->all(); + $test = \collect($inputTypeValidate)->sort(function ($valueA, $valueB) { + $valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2; + $valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2; + return $valueA - $valueB; + })->unique()->all(); + + return \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) { + $valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2; + $valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2; + return $valueA - $valueB; + })->all(); + } + + /** + * Does the rulle need to come first ? + * + * @param string $rule + * @return boolean + */ + private function ruleHasToComeFirst(string $rule): bool + { + $rule = \collect(\explode(':', $rule, 1))->filter()->first(null, ''); + return \collect([ + 'nullable', 'present', 'required', 'required_if', + 'required_unless', 'required_with', 'required_with_all', + 'required_without', 'required_without_all', 'required_with_all', + 'missing', 'missing_if', 'missing_unless', + 'missing_with', 'missing_with_all', + ])->first(fn (string $firstRule) => \strpos($rule, $firstRule) === 0) ? true : false; } /** diff --git a/app/Http/Requests/Fo/.gitkeep b/app/Http/Requests/Fo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Http/Requests/Fo/ContactRequest.php b/app/Http/Requests/Fo/ContactRequest.php index 92d72b2d..b8be5fef 100644 --- a/app/Http/Requests/Fo/ContactRequest.php +++ b/app/Http/Requests/Fo/ContactRequest.php @@ -2,7 +2,9 @@ namespace App\Http\Requests\Fo; +use App\Lib\Helpers\ToolboxHelper; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\ValidationException; class ContactRequest extends FormRequest { @@ -41,6 +43,14 @@ class ContactRequest extends FormRequest 'email' => 'required|email:rfc,spoof,dns,strict', 'message' => 'required|string|min:10', 'terms' => 'accepted', + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + 'g-recaptcha-response' => function (string $attribute, mixed $value, \Closure $fail) { + try { + ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version')); + } catch (ValidationException $e) { + $fail($e->getMessage()); + } + } ]; } } diff --git a/app/Lib/Helpers/ToolboxHelper.php b/app/Lib/Helpers/ToolboxHelper.php index fc56bcda..f6f9d376 100644 --- a/app/Lib/Helpers/ToolboxHelper.php +++ b/app/Lib/Helpers/ToolboxHelper.php @@ -2,10 +2,12 @@ namespace App\Lib\Helpers; +use Carbon\Carbon; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; @@ -35,6 +37,60 @@ class ToolboxHelper return $merged; } + /** + * Validate recaptcha + * + * @param \Illuminate\Http\Request $request + * @param boolean $isV3 + * @return void + */ + public static function validateRecaptcha(Request $request, bool $isV3 = true) + { + Validator::validate($request->all(), [ + 'g-recaptcha-response' => [ + 'required', 'string', + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed + function ($attribute, $value, $fail) use ($isV3) { + try { + $response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [ + 'secret' => config('services.google.recaptcha.private'), + 'response' => \strval($value), + 'remoteip' => app('clientIp') + ]); + if ($e = $response->toException()) { + throw $e; + } + if ($isV3) { + if ( + !$response->json('success') or + !$response->json('challenge_ts') or + Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or + $response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or + !$response->json('score') + ) { + throw new \RuntimeException(); + } + if (\floatval($response->json('score')) < 0.8) { + $fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha"); + } + return; + } + if ( + !$response->json('success') or + !$response->json('challenge_ts') or + Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or + $response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) + ) { + throw new \RuntimeException(); + } + } catch (\Exception $e) { + $fail('Erreur lors de la validation du formulaire veuillez réssayer'); + } //end try + } + ] + ], [], ['g-recaptcha-response' => 'reCaptcha']); + } + /** * Strip scheme (https) on oembed urls * Only embed allowed are for MP4 self hosted videos right now @@ -255,7 +311,7 @@ class ToolboxHelper ): Builder { $currentLocale = app()->currentLocale(); $locales = collect(config('app.locales')) - ->mapWithKeys(fn (string $locale) => [$locale => $locale]); + ->mapWithKeys(fn (string $locale) => [$locale => $locale]); if (!$locales->has($defaultLocale)) { // * Remove default locale. $locales->pull($defaultLocale); diff --git a/app/Models/Concept.php b/app/Models/Concept.php index ce0ffa06..2cebb530 100644 --- a/app/Models/Concept.php +++ b/app/Models/Concept.php @@ -229,11 +229,16 @@ class Concept extends Model 'validate' => "required_if:data.bloc_{$i}_has_video.value,false", ]; - $data["bloc_{$i}_video_youtube"] = [ - 'label' => "Bloc {$i} - Video", - 'inputType' => InputType::videoYoutube->value, + $data["bloc_{$i}_video"] = [ + 'label' => "Bloc {$i} - Video (Youtube or Vimeo)", + 'inputType' => InputType::video->value, 'value' => '', - 'validate' => "required_if:data.bloc_{$i}_has_video.value,true|nullable|regex:%([^\"&?\/\s]{11})%i", + // phpcs:ignore Generic.Files.LineLength.TooLong + 'validate' => [ + 'required_if:data.bloc_{$i}_has_video.value,true', + 'nullable', + 'regex:%([0-9]{8,10}|[^\"&?\/\s]{11})%i' + ], 'required' => false ]; diff --git a/app/Traits/Models/MiniCrudModel.php b/app/Traits/Models/MiniCrudModel.php index f0f3eb2c..09109f1f 100644 --- a/app/Traits/Models/MiniCrudModel.php +++ b/app/Traits/Models/MiniCrudModel.php @@ -161,6 +161,7 @@ trait MiniCrudModel case InputType::number->value: case InputType::videoVimeo->value: case InputType::videoYoutube->value: + case InputType::video->value: break; case InputType::string->value: case InputType::text->value: diff --git a/config/cors.php b/config/cors.php index 90d9e323..cbad99e8 100644 --- a/config/cors.php +++ b/config/cors.php @@ -15,13 +15,15 @@ return [ | */ - 'paths' => ['api/*', 'sanctum/csrf-cookie', '*/storefinder'], + // 'paths' => ['api/*', 'sanctum/csrf-cookie', '*/storefinder'], + 'paths' => ['*'], 'allowed_methods' => ['*'], 'allowed_origins' => ['*'], 'allowed_origins_patterns' => [ + 'https:\/\//player\.vimeo\.com', 'https:\/\/[a-z0-9\-\.]*\.' . \str_replace('.', '\.', 'wo-gilt-was.de') ], diff --git a/config/services.php b/config/services.php index 00cfe33c..f2eb2015 100644 --- a/config/services.php +++ b/config/services.php @@ -34,7 +34,16 @@ return [ 'google' => [ 'maps' => [ 'key' => env('GOOGLE_MAPS_KEY'), + ], + 'recaptcha' => [ + 'public' => env('GOOGLE_RECAPTCHA_PUBLIC'), + 'private' => env('GOOGLE_RECAPTCHA_PRIVATE'), + 'version' => env('GOOGLE_RECAPTCHA_VERSION', "3") === "3" ? true : false ] ], + 'matomo' => [ + 'site_id' => env('MATOMO_SITE_ID') + ], + ]; diff --git a/package-lock.json b/package-lock.json index 95f3b3f0..5850dadf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "srcs", + "name": "wenn-dann-mit-respekt", "lockfileVersion": 3, "requires": true, "packages": { @@ -11,6 +11,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.2", "@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/vue-fontawesome": "^3.0.1", + "@ldc-web/cookie-notice": "^3.0.3", "@rushstack/eslint-patch": "^1.1.4", "@sipec/vue3-tags-input": "^3.0.4", "@splidejs/splide": "^4.1.4", @@ -2348,6 +2349,15 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@ldc-web/cookie-notice": { + "version": "3.0.3", + "resolved": "https://lab.s-web.fr/api/v4/projects/50/packages/npm/@ldc-web/cookie-notice/-/@ldc-web/cookie-notice-3.0.3.tgz", + "integrity": "sha1-9zBm0372Tl+Q+p2FxG073/wHApQ=", + "dev": true, + "dependencies": { + "core-js": "^3.30.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 46cbd155..2ec985e6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.2", "@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/vue-fontawesome": "^3.0.1", + "@ldc-web/cookie-notice": "^3.0.3", "@rushstack/eslint-patch": "^1.1.4", "@sipec/vue3-tags-input": "^3.0.4", "@splidejs/splide": "^4.1.4", diff --git a/resources/assets/images/cookie-icon.png b/resources/assets/images/cookie-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..84e8867789a1ac45a0ea63003d11b87039bd9647 GIT binary patch literal 4686 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 Q9{>OV07*qoM6N<$f~_my-2eap literal 0 HcmV?d00001 diff --git a/resources/lang/de/cookie-notice.php b/resources/lang/de/cookie-notice.php new file mode 100644 index 00000000..503d15ad --- /dev/null +++ b/resources/lang/de/cookie-notice.php @@ -0,0 +1,48 @@ + 'Cookie-Einstellungen', + 'button_forbid' => 'Verbieten', + 'button_allow' => 'Zulassen', + 'button_forbid_all' => 'Alle ablehnen', + 'button_allow_all' => 'Alle akzeptieren', + 'button_customize' => 'Einstellungen', + 'button_back' => 'Rückkehr', + 'banner_title' => 'Die Website :site_name nutzt Cookies!', + 'banner_text_open' => 'Diese Website speichert Cookies auf Ihrem Computer. :site_name verwendet diese Informationen zur Verbesserung und Personalisierung Ihres Surferlebnisses sowie zur Analyse und Kennzahlen über unsere Besucher sowohl auf dieser Website als auch anderswo. Sie können auf „Akzeptieren“ klicken, wenn Sie der Verwendung dieser Cookies zustimmen.', + 'banner_text' => 'Wir verwenden Cookies, um das ordnungsgemäße Funktionieren unserer Website sicherzustellen und für analytische Zwecke. Indem Sie diese Website weiterhin durchsuchen oder durch Wenn Sie dieses Banner schließen, stimmen Sie der Verwendung dieser Cookies zu.', + 'banner_details' => 'Wenn Sie die Verwendung von Cookies ablehnen, werden Ihre Daten nicht erfasst. während Ihres Besuchs auf dieser Website. In Ihrem Browser wird ein Cookie verwendet, um Ihre Präferenzen nicht zu verfolgen. Auf einige Funktionen kann nicht zugegriffen werden. Sie können Ihre Meinung jederzeit ändern und Ihre Einwilligung ändern, indem Sie zu dieser Website zurückkehren.', + 'banner_details_link' => + 'Weitere Informationen zu den von uns verwendeten Cookies finden Sie in unserer Datenschutzerklärung.', + 'banner_details_link_title' => 'Datenschutz-Bestimmungen', + 'banner_more_details' => 'Mehr Erfahren...', + 'banner_less_details' => 'Weniger Details...', + 'Panel_title' => 'Panel zur Verwaltung von Cookies', + 'Panel_subtitle' => 'DerSchutz Ihrer Privatsphäre ist unsere Priorität', + 'Panel_details' => 'Die Funktionalitäten dieser Website basieren auf Diensten, die von Dritten angeboten werden. Wenn Sie zustimmen, werden diese Drittanbieter Cookies platzieren, die Ihnen die Anzeige ermöglichen auf der Website der von diesen Dritten gehosteten Inhalte. Durch diese Cookies sammeln diese Dritten und wird Ihre Browserdaten für eigene Zwecke gemäß verwenden zu deren Datenschutzerklärung (Link unten). Auf dieser Seite können Sie Ihre Einwilligung entweder global oder für jeden einzelnen Dienst erteilen oder widerrufen.', + + 'custom' => [ + 'frame' => 'Sie müssen :type-Cookies autorisieren, um dieses Element anzuzeigen', + 'recaptcha' => [ + 'name' => 'API - reCAPTACHA', + 'description' => 'Die Cookies, die über das Recaptcha auf den Formularen hinterlegt werden, sollen verhindern, dass diese durch Roboter übermittelt werden.', + 'warning' => 'Um das Formular nutzen zu können, müssen Sie Recaptcha-Cookies akzeptieren' + ], + 'matomo' => [ + 'name' => 'PUBLIKUMSMESSUNGEN - MATOMO', + 'description' => 'Zielgruppenanalysedienst.' + ], + 'youtube' => [ + 'name' => 'VIDEOS - Youtube', + 'description' => 'Über Video-Sharing-Dienste platzierte Cookies sollen es dem Benutzer ermöglichen, Multimedia-Inhalte direkt auf der Website anzuzeigen.', + 'labelLink' => 'Erfahren Sie mehr' + ], + 'vimeo' => [ + 'name' => 'VIDEOS - Vimeo', + 'description' => 'Über Video-Sharing-Dienste platzierte Cookies sollen es dem Benutzer ermöglichen, Multimedia-Inhalte direkt auf der Website anzuzeigen.', + 'labelLink' => 'Erfahren Sie mehr' + ], + ] +]; diff --git a/resources/lang/en/cookie-notice.php b/resources/lang/en/cookie-notice.php new file mode 100644 index 00000000..42e6aa79 --- /dev/null +++ b/resources/lang/en/cookie-notice.php @@ -0,0 +1,48 @@ + 'Customize cookies', + 'button_forbid' => 'Deny', + 'button_allow' => 'Accept', + 'button_forbid_all' => 'Deny all', + 'button_allow_all' => 'Accept all', + 'button_customize' => 'Customize', + 'button_back' => 'Back', + 'banner_title' => 'The website :site_name uses cookies!', + 'banner_text_open' => + 'This website stores cookies on your computer. :site_name uses this information to improve and personalize your browsing experience, as well as for analysis and metrics about our visitors both on this website and elsewhere. You can click on "Accept" if you consent to the use of these cookies.', + 'banner_text' => 'We use cookies to ensure the proper functioning of our site and for analytical purposes. By continuing to browse this site or by closing this banner, you consent to the use of these cookies.', + 'banner_details' => 'If you refuse the use of cookies, your information will not be tracked. during your visit to this site. A cookie will be used in your browser to remember not to track your preferences. Some features will not be accessible. You can change your mind and modify your consent at any time by returning to this site.', + 'banner_details_link' => 'To find out more about the cookies we use, see our privacy policy.', + 'banner_details_link_title' => 'Privacy Policy', + 'banner_more_details' => 'More details...', + 'banner_less_details' => 'Less details...', + 'Panel_title' => 'Cookie management panel', + 'Panel_subtitle' => 'Your privacy is our priority', + 'Panel_details' => 'The functionalities of this site rely on services offered by third parties. If you agree, these third parties will place cookies that will allow you to view on the site of the content hosted by these third parties. Through these cookies, these third parties will collect and will use your browsing data for their own purposes, in accordance with to their privacy policy (link below). This page allows you to give or withdraw your consent, either globally or on a service-by-service basis.', + + 'custom' => [ + 'frame' => 'You must autorize :type cookies to view this element', + 'recaptcha' => [ + 'name' => 'API - reCAPTACHA', + 'description' => 'The cookies that are deposited via the recaptcha on the forms are intended to prevent them from being submitted by robots.', + 'warning' => 'To use the form you must accept Recaptcha cookies' + ], + 'matomo' => [ + 'name' => 'AUDIENCE MEASUREMENTS - MATOMO', + 'description' => 'Audience analysis service.' + ], + 'youtube' => [ + 'name' => 'VIDEOS - Youtube', + 'description' => 'Cookies placed via video sharing services are intended to allow the user to view multimedia content directly on the site.', + 'labelLink' => 'Learn more' + ], + 'vimeo' => [ + 'name' => 'VIDEOS - Vimeo', + 'description' => 'Cookies placed via video sharing services are intended to allow the user to view multimedia content directly on the site.', + 'labelLink' => 'Learn more' + ], + ] +]; diff --git a/resources/sass/fo/fo.scss b/resources/sass/fo/fo.scss index 9ace1b82..9ca4b6e1 100644 --- a/resources/sass/fo/fo.scss +++ b/resources/sass/fo/fo.scss @@ -1,10 +1,29 @@ @charset "UTF-8"; +// * UTILITIES +@import "./utilities/variables"; + // * VENDOR @import "./vendor/tailwind"; @import "./vendor/ckEditorContent"; +@import "@ldc-web/cookie-notice"; // * BASE @import "./base/fonts"; @import "./base/common"; @import "./app"; + +.video.frame { + display: none; +} + +#cn-wrapper #cn-cookie-btn { + padding: 0; + background-color: transparent; + box-shadow: 0 0 0; + + img { + width: 100%; + filter: drop-shadow(0 0 0.75rem #888); + } +} diff --git a/resources/sass/fo/utilities/_variables.scss b/resources/sass/fo/utilities/_variables.scss index f6b3f769..d686a68e 100644 --- a/resources/sass/fo/utilities/_variables.scss +++ b/resources/sass/fo/utilities/_variables.scss @@ -6,7 +6,7 @@ $color-success: #198754; $color-danger: #dc3545; $color-info: #0dcaf0; $color-warning: #ffc107; -$color-primary: #0d6efd; +$color-primary: #b4be00; $color-secondary: #6c757d; // * BACKGROUNDS COLORS @@ -22,3 +22,16 @@ $color-default-texts: $color-dark; $font-default-family-base: "Imprima Regular", sans-serif; // - The default site font family. $font-default-size-base: 1rem; // Assumes the browser default, typically `16px` $line-default-height-base: 1.65; + +// * COOKIES +$ck-primary-color: $color-primary; +$ck-secondary-color: $color-secondary; +$ck-background-color: $color-white; +$ck-shadow-color: 0 0.1rem 1rem rgba($color-dark, 35%); +$ck-title-font: inherit; +$ck-title-line-height: 2.5rem; +$ck-title-size: 2rem; +$ck-text-font: inherit; +$ck-text-size: 1rem; +$ck-accept-color: $color-success; +$ck-deny-color: $color-danger; diff --git a/resources/ts/fo.ts b/resources/ts/fo.ts index ab3ee523..ffd51e1d 100644 --- a/resources/ts/fo.ts +++ b/resources/ts/fo.ts @@ -10,3 +10,7 @@ import "./utilities/fontawesome"; // CUSTOM import "./fo/common"; import "./fo/vue-fo"; + +// Cookies +import CookieNotice from "@ldc-web/cookie-notice"; +window.CookieNotice = CookieNotice; diff --git a/resources/ts/fo/common.ts b/resources/ts/fo/common.ts index 1cb4c9ba..8c4ce335 100644 --- a/resources/ts/fo/common.ts +++ b/resources/ts/fo/common.ts @@ -21,3 +21,48 @@ window.addEventListener("scroll", () => { document.querySelector("header")?.classList.remove("small"); } }); + +// * Video frames. +document.addEventListener("DOMContentLoaded", function () { + const videoFrameList = document.querySelectorAll( + ".video-frame" + ) as NodeListOf; + for (const videoLink of videoFrameList) { + if (!videoLink.dataset.type) { + throw new Error("Missing dataset type"); + } + if (!videoLink.dataset.frame) { + throw new Error("Missing dataset frame"); + } + // @ts-ignore + window.__CN_VIDEO = window.__CN_VIDEO ?? {}; + + const newIframe = document.createElement("iframe"); + newIframe.frameBorder = "0"; + newIframe.allow = "fullscreen"; + newIframe.width = "1920px"; + newIframe.height = "1080px"; + newIframe.setAttribute("allowfullscreen", "allowfullscreen"); + + // @ts-ignore + const instance = new FsLightbox(); + instance.props.sources = [newIframe]; + + setTimeout(() => { + videoLink.addEventListener("click", (e) => { + e.preventDefault(); + + // * This comes fslightbox lib + // @ts-ignore + instance.open(); + setTimeout(function () { + // @ts-ignore + newIframe.src = window.__CN_VIDEO[videoLink.dataset.type] + ? String(videoLink.href) + : String(videoLink.dataset.frame); + }, 50); + return false; + }); + }, 300); + } +}); diff --git a/resources/ts/index.d.ts b/resources/ts/index.d.ts index 4c614ed7..33320fa6 100644 --- a/resources/ts/index.d.ts +++ b/resources/ts/index.d.ts @@ -1,6 +1,7 @@ export {}; import { AxiosStatic } from "axios"; import type VueTagsInput from "@sipec/vue3-tags-input"; +import CookieNotice from "@ldc-web/cookie-notice"; // * Enum interface EnumInterface { @@ -74,6 +75,7 @@ declare global { axios: AxiosStatic; Swal: Function; vueDebug: boolean | undefined; + CookieNotice: typeof CookieNotice; __SYSTEM: { _asset: string; _locale: string; diff --git a/resources/views/bo/modules/minicrud-form-inputs.blade.php b/resources/views/bo/modules/minicrud-form-inputs.blade.php index 42830a0c..a521b25e 100644 --- a/resources/views/bo/modules/minicrud-form-inputs.blade.php +++ b/resources/views/bo/modules/minicrud-form-inputs.blade.php @@ -7,6 +7,8 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData; $input = json_decode(json_encode($input), true); @endphp @switch(intval($input['inputType'])) + @case(intval(App\Enums\InputType::video->value)) + @case(intval(App\Enums\InputType::videoVimeo->value)) @case(intval(App\Enums\InputType::videoYoutube->value))
@@ -21,7 +23,11 @@ $data = (isset($model->data)) ? $model->getEmptyData() : $conceptData; 'value' => old("data.{$k}.value", $model->data[$k]['value'] ?? null), 'required' => isset($input['required']) ? !empty($input['required']) : true, 'model' => $model ?? null, - 'helper' => __('video id eg: keZURbOo4-M') + 'helper' => match(intval($input['inputType'])) { + intval(App\Enums\InputType::videoYoutube->value) => __('video id eg: keZURbOo4-M'), + intval(App\Enums\InputType::videoVimeo->value) => __('video id eg: 74918975'), + intval(App\Enums\InputType::video->value) => __('video id eg: keZURbOo4-M or 74918975'), + } ]) @include('modules.input-error', [ 'inputName' => "data.{$k}.value" diff --git a/resources/views/fo/layout-frame.blade.php b/resources/views/fo/layout-frame.blade.php index 61edd789..eef14bf1 100644 --- a/resources/views/fo/layout-frame.blade.php +++ b/resources/views/fo/layout-frame.blade.php @@ -4,7 +4,6 @@ @include('fo.layouts.head') - @@ -17,11 +16,12 @@ @include('fo.layouts.footer') @include('fo.layouts.navigation') + + @include('fo.vendor/ldcweb-cookies') @stack('scripts-constants') @include('modules.script-constants') @stack('scripts') @vite(['resources/ts/fo.ts']) - +@endpush diff --git a/resources/views/fo/pages/frame.blade.php b/resources/views/fo/pages/frame.blade.php new file mode 100644 index 00000000..7be57e95 --- /dev/null +++ b/resources/views/fo/pages/frame.blade.php @@ -0,0 +1,16 @@ + + + + + + + {{ __('Autorisation nécessaire') }} + + +

+ {{ __('cookie-notice.custom.frame', [ + 'type' => $frame + ]) }} +

+ + diff --git a/resources/views/fo/pages/home.blade.php b/resources/views/fo/pages/home.blade.php index a06d3abf..7f81d31d 100644 --- a/resources/views/fo/pages/home.blade.php +++ b/resources/views/fo/pages/home.blade.php @@ -171,15 +171,14 @@ @endsection @push('scripts') - diff --git a/resources/views/fo/vendor/cookie-notice-templates/standard.blade.php b/resources/views/fo/vendor/cookie-notice-templates/standard.blade.php new file mode 100644 index 00000000..c691fa4a --- /dev/null +++ b/resources/views/fo/vendor/cookie-notice-templates/standard.blade.php @@ -0,0 +1,92 @@ + diff --git a/resources/views/fo/vendor/cookie-notice-templates/wenndanntruc.blade.php b/resources/views/fo/vendor/cookie-notice-templates/wenndanntruc.blade.php new file mode 100644 index 00000000..bc858092 --- /dev/null +++ b/resources/views/fo/vendor/cookie-notice-templates/wenndanntruc.blade.php @@ -0,0 +1,80 @@ + diff --git a/resources/views/fo/vendor/ldcweb-cookies.blade.php b/resources/views/fo/vendor/ldcweb-cookies.blade.php new file mode 100644 index 00000000..5f566a70 --- /dev/null +++ b/resources/views/fo/vendor/ldcweb-cookies.blade.php @@ -0,0 +1,152 @@ +@php + +$site_name = Str::ucfirst(config('app.name')); +$logo_picture_url = Vite::asset('resources/assets/images/Icons/logo-nature-respect.svg'); +$logo_alt_text = "Logo {$site_name}"; +$confidentiality_page_url = route('legals'); + +$strings = (object)[ + 'button_customize_cookies' => __('cookie-notice.button_customize_cookies'), + 'button_forbid' => __('cookie-notice.button_forbid'), + 'button_allow' => __('cookie-notice.button_allow'), + 'button_forbid_all' => Str::upper(__('cookie-notice.button_forbid_all')), + 'button_allow_all' => Str::upper(__('cookie-notice.button_allow_all')), + 'button_customize' => Str::upper(__('cookie-notice.button_customize')), + 'button_back' => Str::upper(__('cookie-notice.button_back')), + 'banner_title' => __('cookie-notice.banner_title', ['site_name' => $site_name]), + 'banner_text_open' => __('cookie-notice.banner_text_open', ['site_name' => $site_name]), + 'banner_text' => __('cookie-notice.banner_text'), + 'banner_details' => __('cookie-notice.banner_details'), + 'banner_details_link' => __('cookie-notice.banner_details_link'), + 'banner_details_link_title' => __('cookie-notice.banner_details_link_title'), + 'banner_more_details' => __('cookie-notice.banner_more_details'), + 'banner_less_details' => __('cookie-notice.banner_less_details'), + 'Panel_title' => __('cookie-notice.Panel_title'), + 'Panel_subtitle' => __('cookie-notice.Panel_subtitle'), + 'Panel_details' => __('cookie-notice.Panel_details'), +]; + +$cookies = []; + +// YOUTUBE +$cookies['youtube'] = [ + 'name' => __('cookie-notice.custom.youtube.name'), + 'description' => __('cookie-notice.custom.youtube.name'), + 'labelLink' => __('cookie-notice.custom.youtube.labelLink'), + 'link' => 'https://policies.google.com/technologies/cookies?hl=fr&utm_source=ucb', + 'cookies' => ['NID', 'VISITOR_INFO1_LIVE', 'YSC'] +]; + +$cookies['youtube']['script'] = << +@endpush diff --git a/routes/web-fo.php b/routes/web-fo.php index 9fc9b2c2..3ce8573d 100644 --- a/routes/web-fo.php +++ b/routes/web-fo.php @@ -13,6 +13,10 @@ Route::namespace('\App\Http\Controllers\Fo')->middleware(['throttle:credentials' // * API Route. Route::get('stores', [StoreController::class, 'city'])->name('stores.city'); + // * Iframe cookies + Route::get('/frame/{frame}', fn (string $frame) => view('fo.pages.frame', ['frame' => $frame])) + ->where('frame', '[a-z]+')->name('front.frame'); + Route::localized(function () { Route::get('/', [HomeController::class, 'index'])->name('home');