diff --git a/app/Enums/InputType.php b/app/Enums/InputType.php index ae1b0ae..a7c563c 100644 --- a/app/Enums/InputType.php +++ b/app/Enums/InputType.php @@ -19,6 +19,7 @@ use Kwaadpepper\Enum\BaseEnumRoutable; * html autorisé * @method static self videoVimeo() * @method static self number() + * @method static self email() */ final class InputType extends BaseEnumRoutable { @@ -40,7 +41,8 @@ final class InputType extends BaseEnumRoutable 'dropdownArray' => 7, 'miniblocArray' => 8, 'videoVimeo' => 9, - 'number' => 10 + 'number' => 10, + 'email' => 11 ]; } } diff --git a/app/Http/Middleware/HtmlSanitizer.php b/app/Http/Middleware/HtmlSanitizer.php index e2c39ad..c5a7275 100644 --- a/app/Http/Middleware/HtmlSanitizer.php +++ b/app/Http/Middleware/HtmlSanitizer.php @@ -2,7 +2,7 @@ namespace App\Http\Middleware; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; use Illuminate\Http\Request; class HtmlSanitizer @@ -18,11 +18,17 @@ class HtmlSanitizer public function handle(Request $request, \Closure $next, ...$params) { $inputs = $request->all(); - array_walk_recursive($inputs, function (&$input, $name) use ($params) { + /** @var callable|null */ + $cleanInput = null; + $cleanInput = function (&$input, $name, $rootName = null) use ($params, &$cleanInput) { + // * Nested element array + if (is_array($input)) { + array_walk_recursive($input, $cleanInput, $name); + } if (!is_string($input)) { return; } - if (in_array($name, $params)) { + if (in_array($name, $params) or in_array($rootName ?? $name, $params)) { // ? Is a json input ? if ($isJson = \json_decode($input, true) and is_array($isJson)) { // ! Decode each json field recursive. @@ -39,7 +45,8 @@ class HtmlSanitizer } else { $input = strip_tags($input); } - }); + }; + \array_walk($inputs, $cleanInput); $request->merge($inputs); return $next($request); } diff --git a/app/Http/Requests/Bo/Engagements/UpdateEngagementsRequest.php b/app/Http/Requests/Bo/Engagements/UpdateEngagementsRequest.php index f7dde9e..246bd62 100644 --- a/app/Http/Requests/Bo/Engagements/UpdateEngagementsRequest.php +++ b/app/Http/Requests/Bo/Engagements/UpdateEngagementsRequest.php @@ -17,27 +17,17 @@ class UpdateEngagementsRequest extends UpdateMiniCrudRequest */ protected $modelName = 'engagement'; - /** - * Determine if the user is authorized to make this request. - * - * @return boolean - */ - public function authorize(): bool - { - return auth('backend')->check(); - } - /** * Prepare the data for validation. * * @return void */ - protected function prepareForValidation() + public function prepareForValidation(): void { - $this->merge([ - 'slug' => Str::slug($this->name) - ]); + $title = strip_tags($this->title); + $this->merge(['slug' => Str::slug($title)]); $this->mergePicture('banner'); + parent::prepareForValidation(); } /** @@ -50,10 +40,12 @@ class UpdateEngagementsRequest extends UpdateMiniCrudRequest return array_merge( [ 'title' => 'required|min:3|max:255', + 'title_simple' => 'required|min:3|max:255', + 'slug' => 'required|min:3|max:255', 'seo_title' => 'required|min:53|max:60', 'seo_description' => 'required|min:50|max:160', - 'banner_alt' => 'required_with:banner|nullable|string|min:6|max:255', - 'banner_title' => 'required_with:banner|nullable|string|min:6|max:255' + 'banner_alt' => 'required|nullable|string|min:6|max:255', + 'banner_title' => 'required|nullable|string|min:6|max:255' ], self::pictureRules('banner', 1920, 340, 1920, 340), parent::rules() @@ -68,10 +60,11 @@ class UpdateEngagementsRequest extends UpdateMiniCrudRequest public function attributes(): array { return [ - 'title' => trans('title'), - 'seo_title' => trans('title') . ' seo', - 'seo_description' => trans('description') . ' seo', - 'banner' => trans('bannière'), + 'title' => trans('Titre'), + 'title_simple' => trans('Titre sans style'), + 'seo_title' => trans('Titre') . ' seo', + 'seo_description' => trans('Description') . ' seo', + 'banner' => trans('Bannière'), 'banner_alt' => trans('Texte alternatif de la bannière'), 'banner_title' => trans('Titre de la bannière') ]; diff --git a/app/Http/Requests/Bo/Products/StoreProductRequest.php b/app/Http/Requests/Bo/Products/StoreProductRequest.php index a6d708f..7164b03 100644 --- a/app/Http/Requests/Bo/Products/StoreProductRequest.php +++ b/app/Http/Requests/Bo/Products/StoreProductRequest.php @@ -58,7 +58,7 @@ class StoreProductRequest extends FormRequest [ 'title' => 'required|min:6|max:255', 'slug' => 'required|max:255', - 'seo_title' => 'required|min:45|max:70', + 'seo_title' => 'required|min:53|max:60', 'seo_description' => 'required|min:50|max:160', 'description' => 'required|string|min:15', 'picture_alt' => 'required_with:picture|nullable|string|min:6|max:255', diff --git a/app/Http/Requests/Bo/Ranges/RangeModelTitleRequest.php b/app/Http/Requests/Bo/Ranges/RangeModelTitleRequest.php index 2f8003a..3f4304e 100644 --- a/app/Http/Requests/Bo/Ranges/RangeModelTitleRequest.php +++ b/app/Http/Requests/Bo/Ranges/RangeModelTitleRequest.php @@ -33,9 +33,10 @@ class RangeModelTitleRequest extends FormRequest // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed,Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed function ($attribute, $value, $fail) { $table = (new $value())->getTable(); - Validator::validate(['model' => $this->id], [ - 'model' => "required|exists:{$table},id", - ]); + Validator::validate( + ['model' => $this->id], + ['model' => "required|exists:{$table},id"] + ); } ] ]; diff --git a/app/Http/Requests/Bo/UpdateMiniCrudRequest.php b/app/Http/Requests/Bo/UpdateMiniCrudRequest.php index 0a13a4f..f33f708 100644 --- a/app/Http/Requests/Bo/UpdateMiniCrudRequest.php +++ b/app/Http/Requests/Bo/UpdateMiniCrudRequest.php @@ -3,8 +3,10 @@ namespace App\Http\Requests\Bo; use App\Enums\InputType; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; abstract class UpdateMiniCrudRequest extends FormRequest @@ -26,6 +28,29 @@ abstract class UpdateMiniCrudRequest extends FormRequest return auth('backend')->check(); } + /** + * Prepare the data for validation. + * + * @return void + */ + public function prepareForValidation(): void + { + foreach ($this->{$this->modelName}->data as $iptName => $input) { + $input = \json_decode(\json_encode($input), true); + switch ($input['inputType']['value']) { + case InputType::boolean()->value: + $all = $this->all(); + $nested = Arr::undot([ + "data.$iptName.value" => + $this->input("data.$iptName.value") ? true : false + ]); + $all = array_merge_recursive_distinct($all, $nested); + $this->merge($all); + break; + } //end switch + } //end foreach + } + /** * Get the validation rules that apply to the request. * @@ -46,13 +71,14 @@ abstract class UpdateMiniCrudRequest extends FormRequest ]); break; case InputType::string()->value: + case InputType::email()->value: $rules = array_merge($rules, [ "data.$iptName.value" => $this->compileValidate('required|string|min:1', $input) ]); break; case InputType::text()->value: $mergeRules = $this->compileValidate('required|string|min:10', $input); - $mergeRules = collect(\explode('|', $mergeRules)) + $mergeRules = collect($mergeRules) ->map(function ($rule) { if (\strpos($rule, 'max:') !== false) { $max = \intval(str_replace('max:', '', $rule)); @@ -71,31 +97,36 @@ abstract class UpdateMiniCrudRequest extends FormRequest case InputType::image()->value: $ipt = $this->data[$iptName]; $max = isset($ipt['dimensionsMax']); - $dim = collect(explode( - ' par ', - $ipt['dimensionsMax'] ?? $input['dimensions'] - ))->mapWithKeys(function ($v) { - $v = explode(' ', $v); - return [$v[1] => $v[0]]; - }); + $dim = collect($ipt['dimensionsMax'] ?? $input['dimensions']); $rules = array_merge($rules, [ - "data.$iptName.value" => $this->compileValidate( - implode('|', [ - 'sometimes', - 'image', - 'mimes:png,jpg,jpeg', - sprintf( - 'dimensions:%s=%d,%s=%d', - $max ? 'max_height' : 'height', - $dim['(hauteur)'], - $max ? 'max_width' : 'width', - $dim['(largeur)'] - ) - ]), - $input + "data.$iptName.value" => array_merge( + $this->compileValidate('sometimes', $input), + [function ($attribute, $value, $fail) use ($max, $dim) { + try { + if (\is_object($value)) { + Validator::make( + [$attribute => $value], + // Replace dots since laravel framework 8.76. + [ToolboxHelper::mbReplace('.', '\.', $attribute) => [ + 'file', + 'mimes:png,jpg,jpeg', + sprintf( + 'dimensions:%s=%d,%s=%d', + $max ? 'max_height' : 'height', + $dim['hauteur'], + $max ? 'max_width' : 'width', + $dim['largeur'] + ) + ]] + )->validate(); + } //end if + } catch (ValidationException $e) { + $fail(collect($e->errors())->first()[0]); + } //end try + }] ), - "data.$iptName.alt" => $this->compileValidate('sometimes|string|min:5', $input), - "data.$iptName.title" => $this->compileValidate('sometimes|string|min:5', $input) + "data.$iptName.alt" => $this->compileValidate('required|string|min:5', $input), + "data.$iptName.title" => $this->compileValidate('required|string|min:5', $input) ]); break; case InputType::url()->value: @@ -115,15 +146,8 @@ abstract class UpdateMiniCrudRequest extends FormRequest ]); break; case InputType::boolean()->value: - $all = $this->all(); - $nested = ToolboxHelper::dotStringToNestedArray( - "data.$iptName.value", - $this->input("data.$iptName.value") ? true : false - ); - $all = array_merge_recursive_distinct($all, $nested); - $this->merge($all); $rules = array_merge($rules, [ - "data.$iptName.value" => 'boolean' + "data.$iptName.value" => $this->compileValidate('boolean', $input) ]); break; } //end switch @@ -136,12 +160,12 @@ abstract class UpdateMiniCrudRequest extends FormRequest * * @param string $inputTypeValidate * @param array $input - * @return string + * @return array */ - public function compileValidate(string $inputTypeValidate, array $input): string + public function compileValidate(string $inputTypeValidate, array $input): array { if (!isset($input['validate']) or !\is_string($input['validate'])) { - return $inputTypeValidate; + return explode('|', $inputTypeValidate); } /** @var array */ $inputValidate = explode('|', $input['validate']); @@ -151,6 +175,6 @@ abstract class UpdateMiniCrudRequest extends FormRequest $out[] = $validate; } } - return \implode('|', $out); + return $out; } } diff --git a/app/Lib/Helpers/FileStorageHelper.php b/app/Lib/Helpers/FileStorageHelper.php new file mode 100644 index 0000000..bce62ee --- /dev/null +++ b/app/Lib/Helpers/FileStorageHelper.php @@ -0,0 +1,410 @@ + + */ +class FileStorageHelper +{ + /** + * Destroy the file associate with the attribute. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $attribute + * @return void + */ + public static function removeFile(Model $model, string $attribute): void + { + $path = \ltrim(\urldecode($model->{$attribute}), '/'); + if (\strpos($path, 'storage') !== 0) { + return; + } + $path = ToolboxHelper::mbReplace('storage/', '', $path); + if (Storage::disk('public')->exists($path)) { + Storage::disk('public')->delete($path); + } + } + + /** + * Destroy the file associate with the attribute old value (before updated to DB). + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $attribute + * @param boolean $onlyIfUpdated + * @return void + */ + public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void + { + // * Ignore If removal has to be done only if the field has changed and it actually hasnt. + if ($onlyIfUpdated and $model->{$attribute} === $model->getOriginal($attribute)) { + return; + } + $originalPath = \ltrim(\urldecode($model->getOriginal($attribute)), '/'); + if (\strpos($originalPath, 'storage') !== 0) { + return; + } + $originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath); + if (Storage::disk('public')->exists($originalPath)) { + Storage::disk('public')->delete($originalPath); + } + } + + /** + * Store a file using optimized storage strategy + * + * You can use filename param to overload file name. + * + * @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture. + * @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string. + * @param boolean $slugify Force filename slugification. + * @param string|null $filename This string may be used to store the file. + * @return string + * @throws \RuntimeException If file copy fails. + */ + public static function storeFile(Model $model, $file, bool $slugify = false, string $filename = null): string + { + $tableName = $model->getTable(); + if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) { + /** @var \Illuminate\Http\UploadedFile $file */ + $filename = $filename ?: $file->getClientOriginalName(); + $filename = $slugify ? self::slugifyFileName($filename) : $filename; + $newPath = self::prepareMoveFile($tableName, $filename); + $newPath = str_replace(storage_path('app'), '', $newPath); + $file->storePubliclyAs( + pathinfo($newPath, \PATHINFO_DIRNAME), + pathinfo($newPath, \PATHINFO_BASENAME) + ); + return 'storage' . str_replace('/public', '', $newPath); + } + if (\is_object($file) and $file instanceof \SplFileInfo) { + /** @var \SplFileInfo $file */ + $filename = $filename ?: $file->getBaseName(); + $filename = $slugify ? self::slugifyFileName($filename) : $filename; + $newPath = self::prepareMoveFile($tableName, $filename); + if (!File::copy($file->getRealPath(), $newPath)) { + throw new \RuntimeException(sprintf( + 'Failed to copy file %s to %s', + $file->getRealPath(), + $newPath + )); + } + return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/'); + } + return strval($file); + } + + /** + * Store all files from string attribute html + * If oldHtmlString is passed, a diff is done with new and old files + * to keep only files that are present in the new html string. + * + * + * @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture. + * @param string $htmlString The html string to parse and modify files. + * @param string|null $oldHtmlString The old html string with alreadyParsed + * files using storeModelFilesFromHtml. + * @return string Html with files pathes replaced using storage strategy. + */ + public static function storeModelFilesFromHtml( + Model $model, + string $htmlString, + string $oldHtmlString = null + ): string { + \collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString))) + ->each(function (string $path) use ($model, &$htmlString) { + /** @var \Illuminate\Filesystem\FilesystemAdapter */ + $disk = Storage::disk('uploads'); + $fileInfo = new \SplFileInfo($disk->path( + ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path)) + )); + $htmlString = ToolboxHelper::mbReplace( + $path, + '/' . self::storeFile($model, $fileInfo, true), + $htmlString + ); + }); + if ($oldHtmlString) { + self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString); + } + return $htmlString; + } + + /** + * Remove all old files from string attribute html that are stored in public that + * are nove present anymore on new html string + * + * @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml. + * @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml. + * @return void + */ + public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void + { + self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString)); + } + /** + * Remove all old files that are stored in public that + * are not present anymore on new array files + * + * @param string[] $oldFiles The old array files. + * @param string[] $newFiles The new array files. + * @return void + */ + public static function removeOldFiles(array $oldFiles, array $newFiles): void + { + $oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles)); + $filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles)); + $filesToRemove = $oldFiles->diff($filesToKeep); + $filesToRemove->each(function (string $publicStorageFilePath) { + $path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/')); + if (Storage::disk('public')->exists($path)) { + Storage::disk('public')->delete($path); + } + }); + } + /** + * Remove all files from string attribute html that are stored in public + * + * @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml. + * @return void + */ + public static function removeModelFilesOfHtml(string $htmlString): void + { + $filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString))); + $filesToRemove->each(function (string $publicStorageFilePath) { + $path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/')); + if (Storage::disk('public')->exists($path)) { + Storage::disk('public')->delete($path); + } + }); + } + /** + * Find all occurence of files in html + * @param string $html + * @return array + */ + public static function findFilesInHtml(string $html): array + { + preg_match_all('/href="(?P.*)"|src="(?P.*)"|url="(?P.*)"/Um', $html, $m, PREG_SET_ORDER, 0); + return collect($m)->pluck('href')->filter() + ->concat(collect($m)->pluck('url')->filter()) + ->concat(collect($m)->pluck('src')->filter()) + ->all(); + } + /** + * Filter array to get only uploaded files that exists + * on disk 'public' (using helper storage strategy) + * + * @param array $urls + * @return array + */ + public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array + { + return \collect($urls)->unique() + // * Filter to get only /uploads and using app url or empty + ->filter(function (string $url) { + $path = \parse_url($url, \PHP_URL_PATH); + return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0; + })->filter(function (string $url) { + $path = \parse_url($url, \PHP_URL_PATH); + return Storage::disk('public') + ->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/'))); + })->all(); + } + /** + * Filter array to get only uploaded files that exists + * on disk 'uploads' + * + * @param array $urls + * @return array + */ + private static function onlyUploadedFilesWithFileManager(array $urls): array + { + return \collect($urls)->unique() + // * Filter to get only /uploads and using app url or empty + ->filter(function (string $url) { + $host = \parse_url($url, \PHP_URL_HOST); + $path = \parse_url($url, \PHP_URL_PATH); + return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and + \strpos(\ltrim($path, '/'), 'uploads') === 0; + })->filter(function (string $url) { + $path = \parse_url($url, \PHP_URL_PATH); + return Storage::disk('uploads') + ->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/'))); + })->all(); + } + /** + * Prepare storage path and return + * the file full path + * + * @param string $tableName Model table name. + * @param string $filename The basename for the file. + * @return string + */ + private static function prepareMoveFile(string $tableName, string $filename): string + { + // * Security filter + beautifier + $filename = self::sanitizeFileName($filename); + // * Security filter + $filename = \filter_var($filename, \FILTER_SANITIZE_STRING); + // * Slugify filename + $ext = \pathinfo($filename, \PATHINFO_EXTENSION); + $filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) . + (\strlen($ext) ? ".{$ext}" : ''); + $folderPath = self::getStoragePath($tableName, $filename); + $filename = self::getFileUniqueName($folderPath, $filename); + return "{$folderPath}/{$filename}"; + } + /** + * Create sub folders to store files using pattern + * storage/modelfiles/posts/10/04/19/5f + * year/month/day/md5 first 2 letters + * + * @param string $table + * @param string $filename + * @return string + */ + private static function getStoragePath(string $table, string $filename): string + { + $filenameHash = \md5($filename); + $pathFolder = \storage_path(sprintf( + 'app/public/modelfiles/%s/%s/%s/%s/%s', + $table, + gmdate('y'), + gmdate('m'), + gmdate('d'), + substr($filenameHash, 0, 2) + )); + if (!File::exists($pathFolder)) { + File::makeDirectory($pathFolder, 0755, true); + } + return $pathFolder; + } + /** + * Get unique filename if the path is already occupied + * + * @param string $folderPath + * @param string $filename + * @return string + * @throws \RuntimeException If a unique filename cannot be obtained. + */ + private static function getFileUniqueName(string $folderPath, string $filename): string + { + $attempts = 0; + $maxAttempts = 20; + while (File::exists("{$folderPath}/{$filename}")) { + if ($attempts++ >= $maxAttempts) { + throw new \RuntimeException("Failed to has a unique filename for {$folderPath}/{$filename}"); + } + $filename = pathinfo($filename, \PATHINFO_FILENAME) . + \uniqid('-') . '.' . + pathinfo($filename, \PATHINFO_EXTENSION); + } + return $filename; + } + /** + * Slugify the filename + * + * @param string $filename + * @return string + */ + public static function slugifyFileName(string $filename): string + { + return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION); + } + /** + * Sanitize File name for uploaded files + * + * @param string $filename The original uploaded client file name. + * @param boolean $beautify Beautify the filename. + * @return string The sanitized file name. + */ + public static function sanitizeFileName(string $filename, bool $beautify = true): string + { + // * Sanitize filename + // phpcs:disable Generic.Files.LineLength.TooLong + $filename = preg_replace( + '~ + [<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words + [\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + [\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN + [#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2 + [{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt + ~x', + '-', + $filename + ); + // phpcs:enable + // * Avoids '.' '..' or '.hiddenFiles' . + $filename = Str::of($filename)->ltrim('.-'); + // * Optional beautification + if ($beautify) { + $filename = static::beautifyFilename($filename); + } + // * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086 + /** @var string */ + $extension = pathinfo($filename, \PATHINFO_EXTENSION); + /** @var string */ + $filename = pathinfo($filename, \PATHINFO_FILENAME); + $encoding = mb_detect_encoding($filename); + $encoding = $encoding ? $encoding : null; + return \mb_strcut( + $filename, + 0, + 255 - (!empty($extension) ? strlen($extension) + 1 : 0), + $encoding + ) . (!empty($extension) ? ".$extension" : ''); + } + /** + * Beautify a filename + * + * @param string $filename + * @return string + */ + private static function beautifyFilename(string $filename): string + { + // Reduce consecutive characters. + $filename = preg_replace([ + // * "file name.zip" becomes 'file-name.zip' + '/ +/', + // * "file___name.zip" becomes 'file-name.zip' + '/_+/', + // * "file---name.zip" becomes 'file-name.zip' + '/-+/' + ], '-', $filename); + $filename = preg_replace([ + // * "file--.--.-.--name.zip" becomes "file.name.zip" + '/-*\.-*/', + // * "file...name..zip" becomes "file.name.zip" + '/\.{2,}/' + ], '.', $filename); + // Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 . + $filename = mb_strtolower($filename, mb_detect_encoding($filename)); + // * ".file-name.-" becomes "file-name" + $filename = trim($filename, '.-'); + return $filename; + } +} diff --git a/app/Lib/Helpers/ToolboxHelper.php b/app/Lib/Helpers/ToolboxHelper.php new file mode 100644 index 0000000..0b55941 --- /dev/null +++ b/app/Lib/Helpers/ToolboxHelper.php @@ -0,0 +1,299 @@ +)/m', '', $input); + return $input; + } + + /** + * Assert That no Back Office url is present on txt + * + * @param string $field + * @param string $text + * @return string + * @throws \Illuminate\Validation\ValidationException If url is a bo url. + */ + public static function assertNoInternalBoUrls(string $field, string $text): string + { + $routes = Route::getRoutes(); + // * Url replace and email replace. + $text = preg_replace_callback( + // phpcs:ignore Generic.Files.LineLength.TooLong + '/(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', + function ($matches) use ($routes, $field) { + $match = collect($matches)->first() ?? ''; + $url = $match; + try { + $request = Request::create($url); + // If a route is match then it do not throws exception. + if ( + \parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) && + trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo' + ) { + // Removes the matched url. + throw ValidationException::withMessages([ + $field => ' :attribute : Il est interdit de coller une url du back office dans ce champ' + ]); + } + return $match; + } catch (NotFoundHttpException $e) { + return $match; + } + }, + $text + ); + return $text; + } + + /** + * Assert That only external url is present on txt + * + * @param string $field + * @param string $text + * @return string + * @throws \Illuminate\Validation\ValidationException If url is a bo url. + */ + public static function assertNoInternalUrls(string $field, string $text): string + { + self::assertNoInternalBoUrls($field, $text); + $routes = Route::getRoutes(); + // * Url replace and email replace. + $text = preg_replace_callback( + // phpcs:ignore Generic.Files.LineLength.TooLong + '/(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', + function ($matches) use ($routes, $field) { + $match = collect($matches)->first() ?? ''; + $url = $match; + try { + $request = Request::create($url); + // If a route is match then it do not throws exception. + if ( + \parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) && + $routes->match($request) + ) { + // Removes the matched url. + throw ValidationException::withMessages([ + $field => sprintf( + ':attribute : Il est interdit de coller une url de %s dans ce champ', + config('app.name') + ) + ]); + } + return $match; + } catch (NotFoundHttpException $e) { + return $match; + } + }, + $text + ); + return $text; + } + + /** + * Mutltibyte string replace + * @param mixed $search + * @param mixed $replace + * @param mixed $subject + * @param integer $count + * @return mixed + */ + public static function mbReplace($search, $replace, $subject, int &$count = 0) + { + if (!is_array($search) && is_array($replace)) { + return false; + } + if (is_array($subject)) { + // Call mb_replace for each single string in $subject . + foreach ($subject as &$string) { + $string = &self::mbReplace($search, $replace, $string, $count); + } + } elseif (is_array($search)) { + if (!is_array($replace)) { + foreach ($search as &$string) { + $subject = self::mbReplace($string, $replace, $subject, $count); + } + } else { + $n = max(count($search), count($replace)); + while ($n--) { + $subject = self::mbReplace(current($search), current($replace), $subject, $count); + next($search); + next($replace); + } + } + } else { + $parts = mb_split(preg_quote($search), $subject); + $count = count($parts) - 1; + $subject = implode($replace, $parts); + } //end if + return $subject; + } + + /** + * Asserts that the field is unique + * + * @param string $table + * @param string $field + * @param mixed $value + * @param integer|null $id + * @param callable|null $query + * @return void + * @throws \Illuminate\Validation\ValidationException If the field is not unique. + */ + public static function assertFieldIsUnique( + string $table, + string $field, + $value, + ?int $id = null, + ?callable $query = null + ) { + if (!$query) { + Validator::make([$field => $value], [ + self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id), + ], [ + self::mbReplace('.', '\.', "{$field}.unique") + => trans('La valeur :value pour le champ :attribute est déjà utilisé', [ + 'attribute' => $field, + 'value' => $value + ]) + ])->validate(); + return; + } + Validator::make([$field => $value], [ + $field => Rule::unique($table)->where($query)->ignore($id), + ], [ + $field . '.unique' => trans('La valeur pour le champ :attribute est déjà utilisé', [ + 'attribute' => $field + ]) + ])->validate(); + } + + /** + * * Register CSP report URI + * + * Put this function inside RouteServiceProvider, + * and call it in RouteServiceProvider::boot + * + * ! Use this in conjonction with kwaadpepper/exception-handler + * + * @return void + */ + public static function cspReportRoute() + { + // * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware + \Illuminate\Support\Facades\Route::post('/csp-report', function () { + $isCspRequest = request()->header('Content-Type') === 'application/csp-report'; + if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) { + abort(401); + } + // * Flatten if needed + $cspReport = collect(request()->json('csp-report')) + ->flatMap(function ($v, $k) { + return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v]; + })->all(); + // * Filter array keys + $cspReport = collect(array_intersect_key($cspReport, [ + 'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '', + 'referrer' => '', 'violated-directive' => '' + ])); + report(new \Illuminate\Http\Client\HttpClientException(sprintf( + "CSP REPORT for %s : \n %s", + $cspReport->get('document-uri'), + $cspReport->implode("\n") + ))); + })->middleware('throttle:5,2') + ->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class) + ->name('cspReportUri'); + } + + /** + * Remove unused pictures in ckeditor + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $attribute + * @return void + */ + public static function removeUnusedImagesOnCkEditor(Model $model, string $attribute) + { + // * Remove unused inline uploaded images + if ($model->{$attribute} != $model->getOriginal($attribute)) { + $updatedPicturesPath = collect( + ToolboxHelper::getCkEditorLocalUploadedFiles($model->{$attribute}) + ); + collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute))) + ->map(function (string $originalPicturePath) use ($updatedPicturesPath) { + // * If a picture is not used anymore + if (!$updatedPicturesPath->contains($originalPicturePath)) { + if (File::exists(\public_path($originalPicturePath))) { + File::delete(\public_path($originalPicturePath)); + } + } + }); + } + } + + /** + * Get files path uploaded with ckeditor custom upload plugin (crud-policies) + * + * @param string $text + * @param array $detectExtensions + * @return array + */ + public static function getCkEditorLocalUploadedFiles( + string $text, + array $detectExtensions = ['png', 'svg', 'jpg', 'gif'] + ): array { + $out = []; + \preg_match_all( + // phpcs:ignore Generic.Files.LineLength.TooLong + '/src="(?P\/storage\/images\/crud-policies\/uploads\/\d{4}-\d{2}-\d{2}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-[-a-zA-Z0-9@%._\+~#=]{2,256}\.(?:' . implode('|', $detectExtensions) . '))"/', + $text, + $out + ); + return collect($out['url'] ?? [])->map(function (string $urlEncodedFilePath) { + return \urldecode($urlEncodedFilePath); + })->all(); + } + + /** + * Remove pictures in ckeditor + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $attribute + * @return void + */ + public static function removeImagesOnCkEditor(Model $model, string $attribute) + { + // * Remove inline uploaded images + collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute))) + ->map(function (string $originalPicturePath) { + // * If a picture is not used anymore + if (File::exists(\public_path($originalPicturePath))) { + File::delete(\public_path($originalPicturePath)); + } + }); + } +} diff --git a/app/Lib/ToolboxHelper.php b/app/Lib/ToolboxHelper.php deleted file mode 100644 index f2e4e2c..0000000 --- a/app/Lib/ToolboxHelper.php +++ /dev/null @@ -1,499 +0,0 @@ -)/m', '', $input); - return $input; - } - - /** - * Assert That no Back Office url is present on txt - * - * @param string $field - * @param string $text - * @return string - * @throws \Illuminate\Validation\ValidationException If url is a bo url. - */ - public static function assertNoInternalBoUrls(string $field, string $text): string - { - $routes = Route::getRoutes(); - // * Url replace and email replace. - $text = preg_replace_callback( - // phpcs:ignore Generic.Files.LineLength.TooLong - '/(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', - function ($matches) use ($routes, $field) { - $match = collect($matches)->first() ?? ''; - $url = $match; - try { - $request = Request::create($url); - // If a route is match then it do not throws exception. - if ( - \parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) && - trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo' - ) { - // Removes the matched url. - throw ValidationException::withMessages([ - $field => sprintf( - '%s : Il est interdit de coller une url du back office dans ce champ', - trans($field) - ) - ]); - } - return $match; - } catch (NotFoundHttpException $e) { - return $match; - } - }, - $text - ); - return $text; - } - - /** - * Get files path uploaded with ckeditor custom upload plugin (crud-policies) - * - * @param string $text - * @param array $detectExtensions - * @return array - */ - public static function getCkEditorLocalUploadedFiles( - string $text, - array $detectExtensions = ['png', 'svg', 'jpg', 'gif'] - ): array { - $out = []; - \preg_match_all( - // phpcs:ignore Generic.Files.LineLength.TooLong - '/src="(?P\/storage\/images\/crud-policies\/uploads\/\d{4}-\d{2}-\d{2}\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-[-a-zA-Z0-9@%._\+~#=]{2,256}\.(?:' . implode('|', $detectExtensions) . '))"/', - $text, - $out - ); - return collect($out['url'] ?? [])->map(function (string $urlEncodedFilePath) { - return \urldecode($urlEncodedFilePath); - })->all(); - } - - /** - * Assert That only external url is present on txt - * - * @param string $field - * @param string $text - * @return string - * @throws \Illuminate\Validation\ValidationException If url is a bo url. - */ - public static function assertNoInternalUrls(string $field, string $text): string - { - self::assertNoInternalBoUrls($field, $text); - $routes = Route::getRoutes(); - // * Url replace and email replace. - $text = preg_replace_callback( - // phpcs:ignore Generic.Files.LineLength.TooLong - '/(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', - function ($matches) use ($routes, $field) { - $match = collect($matches)->first() ?? ''; - $url = $match; - try { - $request = Request::create($url); - // If a route is match then it do not throws exception. - if ( - \parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) && - $routes->match($request) - ) { - // Removes the matched url. - throw ValidationException::withMessages([ - $field => sprintf( - '%s : Il est interdit de coller une url de %s dans ce champ', - trans($field), - config('app.name') - ) - ]); - } - return $match; - } catch (NotFoundHttpException $e) { - return $match; - } - }, - $text - ); - return $text; - } - - /** - * Generates an array from a dot separated string - * - * @param string $path - * @param mixed $value - * @param string $separator - * @return array - */ - public static function dotStringToNestedArray(string $path, $value, string $separator = '.'): array - { - $arr = $value; - $keys = array_reverse(explode($separator, $path)); - - foreach ($keys as $key) { - $arr = [$key => $arr]; - } - return $arr; - } - - /** - * Destroy image - * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $attribute - * @return void - */ - public static function removeImage(Model $model, string $attribute) - { - $path = ltrim($model->{$attribute}, '/'); - if (File::exists($path) and \strpos($path, 'storage') === 0) { - File::delete($path); - } - } - - /** - * Destroy image - * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $attribute - * @return void - */ - public static function removeOldImage(Model $model, string $attribute) - { - $originalPath = ltrim($model->getOriginal($attribute), '/'); - if (File::exists($originalPath) and \strpos($originalPath, 'storage') === 0) { - File::delete($originalPath); - } - } - - /** - * Remove pictures in ckeditor - * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $attribute - * @return void - */ - public static function removeImagesOnCkEditor(Model $model, string $attribute) - { - // * Remove inline uploaded images - collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute))) - ->map(function (string $originalPicturePath) { - // * If a picture is not used anymore - if (File::exists(\public_path($originalPicturePath))) { - File::delete(\public_path($originalPicturePath)); - } - }); - } - /** - * Remove unused pictures in ckeditor - * - * @param \Illuminate\Database\Eloquent\Model $model - * @param string $attribute - * @return void - */ - public static function removeUnusedImagesOnCkEditor(Model $model, string $attribute) - { - // * Remove unused inline uploaded images - if ($model->{$attribute} != $model->getOriginal($attribute)) { - $updatedPicturesPath = collect( - ToolboxHelper::getCkEditorLocalUploadedFiles($model->{$attribute}) - ); - collect(ToolboxHelper::getCkEditorLocalUploadedFiles($model->getOriginal($attribute))) - ->map(function (string $originalPicturePath) use ($updatedPicturesPath) { - // * If a picture is not used anymore - if (!$updatedPicturesPath->contains($originalPicturePath)) { - if (File::exists(\public_path($originalPicturePath))) { - File::delete(\public_path($originalPicturePath)); - } - } - }); - } - } - - /** - * Store an image of different types - * - * @param \Illuminate\Database\Eloquent\Model $model - * @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $image - * @return string - * @throws \ErrorException If file copy fails. - */ - public static function storeImage(Model $model, $image): string - { - $table = $model->getTable(); - if (\is_object($image) and $image instanceof \Illuminate\Http\UploadedFile) { - /** @var \Illuminate\Http\UploadedFile $image */ - $newPath = self::prepareMoveFile($table, $image->getClientOriginalName()); - $newPath = str_replace(storage_path('app'), '', $newPath); - $image->storePubliclyAs( - pathinfo($newPath, \PATHINFO_DIRNAME), - pathinfo($newPath, \PATHINFO_BASENAME) - ); - return 'storage' . str_replace('/public', '', $newPath); - } - if (\is_object($image) and $image instanceof \SplFileInfo) { - /** @var \SplFileInfo $image */ - $newPath = self::prepareMoveFile($table, $image->getBaseName()); - if (!File::copy($image->getRealPath(), $newPath)) { - throw new \ErrorException(sprintf( - 'Failed to copy file %s to %s', - $image->getRealPath(), - $newPath - )); - } - return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/'); - } - return strval($image); - } - - /** - * Prepare storage path and return - * the image full path - * - * @param string $table Model table name. - * @param string $filename The basename for the file. - * @return string - */ - private static function prepareMoveFile(string $table, string $filename): string - { - // * Security filter + beautifier - $filename = self::sanitizeFileName($filename); - // * Security filter - $filename = \filter_var($filename, \FILTER_SANITIZE_STRING); - // * Slugify filename - $ext = \pathinfo($filename, \PATHINFO_EXTENSION); - $filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) . - (\strlen($ext) ? ".{$ext}" : ''); - - $pathFolder = \storage_path(sprintf('app/public/images/%s', $table)); - if (!File::exists($pathFolder)) { - File::makeDirectory($pathFolder, 0755, true); - } - $filename = sprintf('%s-%s', Str::uuid(), $filename); - $fullPath = sprintf('%s/%s', $pathFolder, $filename); - if (File::exists($fullPath)) { - File::delete($fullPath); - } - return $fullPath; - } - - /** - * Asserts that the field is unique - * - * @param string $table - * @param string $field - * @param mixed $value - * @param integer|null $id - * @param callable|null $query - * @return void - * @throws \Illuminate\Validation\ValidationException If the field is not unique. - */ - public static function assertFieldIsUnique( - string $table, - string $field, - $value, - ?int $id = null, - ?callable $query = null - ) { - if (!$query) { - Validator::make([$field => $value], [ - $field => Rule::unique($table, $field)->ignore($id), - ], [ - $field . '.unique' => trans('La valeur :value pour le champ :attribute est déjà utilisé', [ - 'attribute' => $field, - 'value' => $value - ]) - ])->validate(); - return; - } - Validator::make([$field => $value], [ - $field => Rule::unique($table)->where($query)->ignore($id), - ], [ - $field . '.unique' => trans('La valeur pour le champ :attribute est déjà utilisé', [ - 'attribute' => $field - ]) - ])->validate(); - } - - /** - * Validate recaptcha V3 - * - * @param Request $request - * @return void - */ - public static function validateRecaptchaV3(Request $request) - { - Validator::validate($request->all(), [ - 'g-recaptcha-response' => [ - 'required', 'string', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed - function ($attribute, $value, $fail) { - try { - $response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [ - 'secret' => config('recaptcha.private'), - 'response' => \strval($value), - 'remoteip' => app('clientIp') - ]); - if ($e = $response->toException()) { - throw $e; - } - - 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 \ErrorException(); - } - if (\floatval($response->json('score')) < 0.8) { - $fail('Vous n\'avez pas obtenu un score assez élevé lors de la validation du recaptcha'); - } - } catch (\Exception $e) { - $fail('Erreur lors de la validation du formulaire veuillez réssayer'); - } //end try - } - ] - ], [], ['g-recaptcha-response' => 'reCaptcha']); - } - - /** - * * Register CSP report URI - * - * Put this function inside RouteServiceProvider, - * and call it in RouteServiceProvider::boot - * - * ! Use this in conjonction with kwaadpepper/exception-handler - * - * @return void - */ - public static function cspReportRoute() - { - // * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware - \Illuminate\Support\Facades\Route::post('/csp-report', function () { - $isCspRequest = request()->header('Content-Type') === 'application/csp-report'; - if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) { - abort(401); - } - // * Flatten if needed - $cspReport = collect(request()->json('csp-report')) - ->flatMap(function ($v, $k) { - return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v]; - })->all(); - // * Filter array keys - $cspReport = collect(array_intersect_key($cspReport, [ - 'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '', - 'referrer' => '', 'violated-directive' => '' - ])); - report(new \Illuminate\Http\Client\HttpClientException(sprintf( - "CSP REPORT for %s : \n %s", - $cspReport->get('document-uri'), - $cspReport->implode("\n") - ))); - })->middleware('throttle:5,2') - ->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class) - ->name('cspReportUri'); - } - - /** - * Sanitize File name for uploaded files - * - * @param string $filename The original uploaded client file name. - * @param boolean $beautify Beautify the filename. - * @return string The sanitized file name. - */ - public static function sanitizeFileName(string $filename, bool $beautify = true): string - { - // * Sanitize filename - // phpcs:disable Generic.Files.LineLength.TooLong - $filename = preg_replace( - '~ - [<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words - [\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx - [\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN - [#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2 - [{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt - ~x', - '-', - $filename - ); - // phpcs:enable - // * Avoids '.' '..' or '.hiddenFiles' . - $filename = Str::of($filename)->ltrim('.-'); - // * Optional beautification - if ($beautify) { - $filename = static::beautifyFilename($filename); - } - // * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086 - /** @var string */ - $extension = pathinfo($filename, \PATHINFO_EXTENSION); - /** @var string */ - $filename = pathinfo($filename, \PATHINFO_FILENAME); - - $encoding = mb_detect_encoding($filename); - $encoding = $encoding ? $encoding : null; - return \mb_strcut( - $filename, - 0, - 255 - (!empty($extension) ? strlen($extension) + 1 : 0), - $encoding - ) . (!empty($extension) ? ".$extension" : ''); - } - - /** - * Beautify a filename - * - * @param string $filename - * @return string - */ - private static function beautifyFilename(string $filename): string - { - // Reduce consecutive characters. - $filename = preg_replace([ - // * "file name.zip" becomes 'file-name.zip' - '/ +/', - // * "file___name.zip" becomes 'file-name.zip' - '/_+/', - // * "file---name.zip" becomes 'file-name.zip' - '/-+/' - ], '-', $filename); - $filename = preg_replace([ - // * "file--.--.-.--name.zip" becomes "file.name.zip" - '/-*\.-*/', - // * "file...name..zip" becomes "file.name.zip" - '/\.{2,}/' - ], '.', $filename); - // Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 . - $filename = mb_strtolower($filename, mb_detect_encoding($filename)); - // * ".file-name.-" becomes "file-name" - $filename = trim($filename, '.-'); - return $filename; - } -} diff --git a/app/Models/Engagement.php b/app/Models/Engagement.php index 05d1e1b..50e9fc3 100644 --- a/app/Models/Engagement.php +++ b/app/Models/Engagement.php @@ -3,8 +3,9 @@ namespace App\Models; use App\Enums\Engagements\EngagementType; +use App\Lib\Helpers\FileStorageHelper; +use App\Lib\Helpers\ToolboxHelper; use App\Traits\Models\MiniCrudModel; -use App\Lib\ToolboxHelper; use Illuminate\Database\Eloquent\Model; use Kwaadpepper\Enum\Traits\CastsEnums; @@ -33,7 +34,8 @@ class Engagement extends Model * @var array */ protected $casts = [ - 'data' => 'array' + 'data' => 'array', + 'banner_dimensions' => 'array' ]; /** @@ -52,12 +54,14 @@ class Engagement extends Model */ protected $fillable = [ 'title', + 'title_simple', 'slug', 'seo_title', 'seo_description', 'banner', 'banner_title', 'banner_alt', + 'banner_dimensions', 'type', 'data' ]; @@ -70,14 +74,23 @@ class Engagement extends Model protected static function booted() { static::creating(function (self $model) { - $model->banner = ToolboxHelper::storeImage($model, $model->banner); + $model->data = clean($model->data); + $model->banner = FileStorageHelper::storeFile($model, $model->banner); }); static::updating(function (self $model) { - ToolboxHelper::removeOldImage($model, 'banner'); - $model->banner = ToolboxHelper::storeImage($model, $model->banner); + if ($model->banner != $model->getOriginal('banner')) { + $model->banner = FileStorageHelper::storeFile($model, $model->banner); + } + }); + static::updated(function (self $model) { + ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'title'); + if ($model->banner != $model->getOriginal('banner')) { + $model->banner = FileStorageHelper::removeOldFile($model, 'banner'); + } }); static::deleted(function (self $model) { - ToolboxHelper::removeImage($model, 'banner'); + FileStorageHelper::removeFile($model, 'banner'); + ToolboxHelper::removeImagesOnCkEditor($model, 'title'); }); } } diff --git a/app/Models/News.php b/app/Models/News.php index 8b5ebf1..305b496 100644 --- a/app/Models/News.php +++ b/app/Models/News.php @@ -3,7 +3,8 @@ namespace App\Models; use App\Enums\PublishedStatus; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; +use App\Lib\Helpers\FileStorageHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; @@ -61,7 +62,7 @@ class News extends Model ToolboxHelper::assertNoInternalBoUrls('content', $model->content); $model->slug = Str::slug($model->title); $model->content = clean($model->content); - $model->picture = ToolboxHelper::storeImage($model, $model->picture); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); if ($model->published->equals(PublishedStatus::published())) { $model->published_at = now(); } @@ -80,17 +81,17 @@ class News extends Model } // * Store new picture if changed if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::storeImage($model, $model->picture); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); } }); static::updated(function (self $model) { ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'content'); if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::removeOldImage($model, 'picture'); + $model->picture = FileStorageHelper::removeOldFile($model, 'picture'); } }); static::deleting(function (self $model) { - ToolboxHelper::removeImage($model, 'picture'); + FileStorageHelper::removeFile($model, 'picture'); ToolboxHelper::removeImagesOnCkEditor($model, 'content'); }); } diff --git a/app/Models/Product.php b/app/Models/Product.php index b88bc17..533168a 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -3,7 +3,8 @@ namespace App\Models; use App\Enums\Products\ProductLabelEnum; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; +use App\Lib\Helpers\FileStorageHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\ValidationException; @@ -90,8 +91,8 @@ class Product extends Model $model->preparation_advices = clean($model->preparation_advices); $model->sales_pitch = clean($model->sales_pitch); $model->allergen_list = clean($model->allergen_list); - $model->picture = ToolboxHelper::storeImage($model, $model->picture); - $model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); + $model->picture_thumb = FileStorageHelper::storeFile($model, $model->picture_thumb); if (!$model->status) { $model->publish_at = null; } else { @@ -121,11 +122,11 @@ class Product extends Model $model->allergen_list = clean($model->allergen_list); // * Store new picture if changed if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::storeImage($model, $model->picture); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); } // * Store new picture_thumb if changed if ($model->picture_thumb != $model->getOriginal('picture_thumb')) { - $model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb); + $model->picture_thumb = FileStorageHelper::storeFile($model, $model->picture_thumb); } if ($model->link1) { self::validateLinkAttribute('link1', $model->link1); @@ -141,12 +142,12 @@ class Product extends Model ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'sales_pitch'); ToolboxHelper::removeUnusedImagesOnCkEditor($model, 'allergen_list'); if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::removeOldImage($model, 'picture'); + $model->picture = FileStorageHelper::removeOldFile($model, 'picture'); } }); static::deleting(function (self $model) { - ToolboxHelper::removeImage($model, 'picture'); - ToolboxHelper::removeImage($model, 'picture_thumb'); + FileStorageHelper::removeFile($model, 'picture'); + FileStorageHelper::removeFile($model, 'picture_thumb'); ToolboxHelper::removeImagesOnCkEditor($model, 'description'); ToolboxHelper::removeImagesOnCkEditor($model, 'ingredients'); ToolboxHelper::removeImagesOnCkEditor($model, 'preparation_advices'); diff --git a/app/Models/Range.php b/app/Models/Range.php index 0bbca38..bd4d7a3 100644 --- a/app/Models/Range.php +++ b/app/Models/Range.php @@ -3,7 +3,8 @@ namespace App\Models; use App\Enums\Ranges\RangeCategoryEnum; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; +use App\Lib\Helpers\FileStorageHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Session; @@ -78,8 +79,8 @@ class Range extends Model $model->slug = Str::slug($model->title); $model->description = clean($model->description); $model->argumentation = clean($model->argumentation); - $model->picture = ToolboxHelper::storeImage($model, $model->picture); - $model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); + $model->picture_thumb = FileStorageHelper::storeFile($model, $model->picture_thumb); if (!$model->status) { $model->publish_at = null; } else { @@ -97,10 +98,10 @@ class Range extends Model ToolboxHelper::assertNoInternalBoUrls('description', $model->description); ToolboxHelper::assertNoInternalBoUrls('argumentation', $model->argumentation); if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::storeImage($model, $model->picture); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); } if ($model->picture_thumb != $model->getOriginal('picture_thumb')) { - $model->picture_thumb = ToolboxHelper::storeImage($model, $model->picture_thumb); + $model->picture_thumb = FileStorageHelper::storeFile($model, $model->picture_thumb); } // * If status changed if (\boolval($model->getOriginal('status')) !== $model->status) { @@ -121,15 +122,15 @@ class Range extends Model }); static::updated(function (self $model) { if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::removeOldImage($model, 'picture'); + $model->picture = FileStorageHelper::removeOldFile($model, 'picture'); } if ($model->picture_v != $model->getOriginal('picture_v')) { - $model->picture_v = ToolboxHelper::removeOldImage($model, 'picture_v'); + $model->picture_v = FileStorageHelper::removeOldFile($model, 'picture_v'); } }); static::deleting(function (self $model) { - ToolboxHelper::removeImage($model, 'picture'); - ToolboxHelper::removeImage($model, 'picture_v'); + FileStorageHelper::removeFile($model, 'picture'); + FileStorageHelper::removeFile($model, 'picture_v'); }); } diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index a4699ec..18db460 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; diff --git a/app/Models/Slider.php b/app/Models/Slider.php index a27bb7f..4f98397 100644 --- a/app/Models/Slider.php +++ b/app/Models/Slider.php @@ -5,7 +5,8 @@ namespace App\Models; use App\Casts\RgbaColorNullable; use App\Enums\Sliders\SliderElementPosition; use App\Enums\Sliders\SliderType; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; +use App\Lib\Helpers\FileStorageHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Session; @@ -84,7 +85,7 @@ class Slider extends Model { static::creating(function (self $model) { ToolboxHelper::assertNoInternalBoUrls('link', $model->link); - $model->picture = ToolboxHelper::storeImage($model, $model->picture); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); $model->order = self::whereType($model->type)->max('order') ?? 0; if (!$model->status) { $model->publish_at = null; @@ -95,7 +96,7 @@ class Slider extends Model static::updating(function (self $model) { ToolboxHelper::assertNoInternalBoUrls('link', $model->link); if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::storeImage($model, $model->picture); + $model->picture = FileStorageHelper::storeFile($model, $model->picture); } // * If status changed if (\boolval($model->getOriginal('status')) !== $model->status) { @@ -110,11 +111,11 @@ class Slider extends Model }); static::updated(function (self $model) { if ($model->picture != $model->getOriginal('picture')) { - $model->picture = ToolboxHelper::removeOldImage($model, 'picture'); + $model->picture = FileStorageHelper::removeOldFile($model, 'picture'); } }); static::deleting(function (self $model) { - ToolboxHelper::removeImage($model, 'picture'); + FileStorageHelper::removeFile($model, 'picture'); }); } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 483e0eb..c44090c 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; diff --git a/app/Models/User.php b/app/Models/User.php index 5c60527..27b78f7 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 0da5b87..ce1e390 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,7 +2,7 @@ namespace App\Providers; -use App\Lib\ToolboxHelper; +use App\Lib\Helpers\ToolboxHelper; use App\Models\News; use App\Models\Product; use App\Models\Range; diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php new file mode 100644 index 0000000..abb8810 --- /dev/null +++ b/app/Providers/ViewServiceProvider.php @@ -0,0 +1,25 @@ + $input) { switch ($input['inputType']->value ?? $input['inputType']['value']) { case InputType::image()->value: - $data[$o]['value'] = ToolboxHelper::storeImage($this, $input['value']); + $data[$o]['value'] = FileStorageHelper::storeFile($this, $input['value']); break; case InputType::boolean()->value: case InputType::text()->value: @@ -73,7 +74,7 @@ trait MiniCrudModel 'Unknown input Type %s', $input['inputType']->label ?? $input['inputType']['label'] )); - } + } //end switch } //end foreach return json_encode($data); } diff --git a/app/Traits/Requests/HasPicture.php b/app/Traits/Requests/HasPicture.php index 1a7a9a6..2b0f989 100644 --- a/app/Traits/Requests/HasPicture.php +++ b/app/Traits/Requests/HasPicture.php @@ -59,7 +59,7 @@ trait HasPicture ]; return [ $name => [ - 'required', + 'sometimes', function (string $attribute, $value, callable $fail) use ($sizes) { return $this->validatePicture($attribute, $value, $fail, $sizes); } diff --git a/config/app.php b/config/app.php index c98a813..1a38472 100644 --- a/config/app.php +++ b/config/app.php @@ -176,7 +176,7 @@ return [ App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\TranslationServiceProvider::class, - + App\Providers\ViewServiceProvider::class ], /* @@ -191,7 +191,6 @@ return [ */ 'aliases' => [ - 'App' => Illuminate\Support\Facades\App::class, 'Arr' => Illuminate\Support\Arr::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class, @@ -229,8 +228,6 @@ return [ 'Str' => Illuminate\Support\Str::class, 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - - ], - + 'View' => Illuminate\Support\Facades\View::class + ] ]; diff --git a/database/migrations/2022_09_05_131055_create_engagements_table.php b/database/migrations/2022_09_05_131055_create_engagements_table.php index a03fe35..4ed00dc 100644 --- a/database/migrations/2022_09_05_131055_create_engagements_table.php +++ b/database/migrations/2022_09_05_131055_create_engagements_table.php @@ -18,14 +18,17 @@ class CreateEngagementsTable extends Migration { Schema::create('engagements', function (Blueprint $table) { $table->string('title')->comment('Nom de l\'engagement'); + $table->string('title_simple')->comment('Nom de l\'engagement sans style'); $table->string('slug')->comment('Slug de l\'engagement'); $table->string('seo_title')->comment('Description SEO de l\'engagement'); $table->string('seo_description')->comment('Description SEO de l\'engagement'); $table->string('banner')->nullable()->comment('Bannière de l\'engagement'); $table->string('banner_title')->comment('Titre de la bannière'); $table->string('banner_alt')->comment('Attribut "alt" de la bannière'); + $table->string('banner_dimensions')->comment('Dimensions de la bannière'); - $table->smallInteger('type')->primary() + $table->smallInteger('type') + ->primary() ->comment('Enum value EngagementsType'); $table->mediumText('data')->comment('json array'); @@ -38,12 +41,14 @@ class CreateEngagementsTable extends Migration $title = 'Énergies renouvelables'; $config = new Engagement([ 'title' => $title, + 'title_simple' => $title, 'slug' => \Illuminate\Support\Str::slug($title), 'seo_title' => $title . $faker->words(5, true), 'seo_description' => 'Ceci est une description pour le SEO' . $faker->words(5, true), - 'banner' => '/assets/images/engagements/1920x340.jpg', + 'banner' => new SplFileInfo(resource_path('/assets/images/engagements/1920x340.jpg')), 'banner_title' => $title, - 'banner_alt' => 'Image représentant quelque chose', + 'banner_alt' => 'Image d\'un éleveur', + 'banner_dimensions' => ['largeur' => '1920', 'hauteur' => '340'], 'type' => EngagementType::renewableEnergies(), 'data' => [ @@ -53,35 +58,45 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->words(4, true), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la première section, requiert minimum 10 caractères' + 'helper' => 'Titre de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la + première section de la page ' . $title ], 'bloc1_description' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->words(4, true), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la + première section de la page ' . $title ], 'bloc1_citation' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Citation de la première section, requiert minimum 10 caractères' + 'helper' => 'Citation de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la citation de la première + section de la page ' . $title ], 'bloc1_links' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Liens utiles de la première section, requiert minimum 10 caractères' + 'helper' => 'Liens utiles de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour les liens utiles de la + première section de la page ' . $title ], 'bloc1_subtitle' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Résumé de la première section, requiert minimum 10 caractères' + 'helper' => 'Résumé de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le résumé de la + première section de la page ' . $title ], // * BLOC 2 @@ -90,21 +105,27 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la + deuxième section de la page ' . $title ], 'bloc2_description' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la + deuxième section de la page ' . $title ], 'bloc2_links' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Liens utiles de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Liens utiles de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour les liens utiles de la + deuxième section de la page ' . $title ], // * BLOC 3 @@ -113,79 +134,101 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nombre représentant la production ÉoLoué, requiert entre 4 et 100 caractères' + 'helper' => 'Nombre représentant la production ÉoLoué, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nombre représentant la production ÉoLoué de la + page ' . $title ], 'bloc3_puissance' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nombre représentant la puissance installé ÉoLoué, requiert entre 4 et 100 caractères' + 'helper' => 'Nombre représentant la puissance installé ÉoLoué, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nombre représentant la puissance installé + ÉoLoué de la page ' . $title ], 'bloc3_consommation' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nombre représentant la consommation électrique ÉoLoué, - requiert entre 4 et 100 caractères' + 'helper' => 'Nombre représentant la consommation électrique ÉoLoué, requiert entre 4 et 100 + caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nombre représentant la consommation électrique + ÉoLoué de la page ' . $title ], 'bloc3_émissions' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nombre représentant les émissions de CO2 ÉoLoué, requiert entre 4 et 100 caractères' + 'helper' => 'Nombre représentant les émissions de CO2 ÉoLoué, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nombre représentant les émissions de CO2 ÉoLoué + de la page ' . $title ], 'bloc3_avantages1' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(7, true), 'validate' => 'required|min:4|max:150', - 'helper' => 'Doit décrire les avantages d\'ÉoLoué, requiert entre 4 et 150 caractères' + 'helper' => 'Doit décrire les avantages d\'ÉoLoué, requiert entre 4 et 150 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour les avantages d\'ÉoLoué de la page ' . $title ], 'bloc3_production2' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Doit décrire la production des panneaux solaires, requiert entre 4 et 100 caractères' + 'helper' => 'Doit décrire la production des panneaux solaires, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour décrire la production des panneaux solaires + de la page ' . $title ], 'bloc3_avantages2' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(7, true), 'validate' => 'required|min:4|max:150', - 'helper' => 'Doit décrire les avantages des panneaux solaires, requiert entre 4 et 150 caractères' + 'helper' => 'Doit décrire les avantages des panneaux solaires, requiert entre 4 et 150 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour décrire les avantages des panneaux solaires + de la page ' . $title ], 'bloc3_subtitle' => [ 'label' => 'Sous-titre', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Sous-titre de la deuxième section, requiert entre 4 et 100 caractères' + 'helper' => 'Sous-titre de la deuxième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le sous-titre de la deuxième section de la + page ' . $title ], 'bloc3_productionTotale' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nombre représentant la production totale, requiert entre 4 et 100 caractères' + 'helper' => 'Nombre représentant la production totale, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nombre représentant la production totale + de la page ' . $title ], 'bloc3_consommationTotale' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nombre représentant la consommation totale, requiert entre 4 et 100 caractères' + 'helper' => 'Nombre représentant la consommation totale, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nombre représentant la consommation totale + de la page ' . $title ], // * BLOC 4 'bloc4_title' => [ 'label' => 'Titre', - 'inputType' => InputType::string(), - 'value' => $faker->words(4, true), - 'helper' => 'Titre de la quatrième section, requiert entre 4 et 100 caractères' + 'inputType' => InputType::text(), + 'text' => $faker->text(), + 'validate' => 'required|string|min:10', + 'helper' => 'Titre de la quatrième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le titre de la quatrième section de la + page ' . $title ], ] ]); @@ -194,12 +237,14 @@ class CreateEngagementsTable extends Migration $title = 'Élevage durable / Céréales locales'; $config = new Engagement([ 'title' => $title, + 'title_simple' => $title, 'slug' => \Illuminate\Support\Str::slug($title), 'seo_title' => $title . $faker->words(5, true), 'seo_description' => 'Ceci est une description pour le SEO' . $faker->words(5, true), - 'banner' => '/assets/images/engagements/1920x340.jpg', + 'banner' => new SplFileInfo(resource_path('/assets/images/engagements/1920x340.jpg')), 'banner_title' => $title, - 'banner_alt' => 'Image représentant quelque chose', + 'banner_alt' => 'Image d\'un éleveur', + 'banner_dimensions' => ['largeur' => '1920', 'hauteur' => '340'], 'type' => EngagementType::localCereals(), 'data' => [ @@ -209,22 +254,27 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la + première section de la page ' . $title ], 'bloc1_poulets' => [ 'label' => 'Image de poulets', - 'dimensions' => '342px (largeur) par 200px (hauteur)', + 'dimensions' => ['largeur' => '342', 'hauteur' => '200'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/342x200.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/342x200.jpg')), + 'tooltip' => 'Multiple champs décrivant l\'image de la première section de la page ' . $title ], 'bloc1_links' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Liens utiles de la première section, requiert minimum 10 caractères' + 'helper' => 'Liens utiles de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour les liens utiles de la première section + de la page ' . $title ], // * BLOC 2 @@ -233,31 +283,38 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Titre de la deuxième section, requiert entre 4 et 100 caractères' + 'helper' => 'Titre de la deuxième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le titre de la deuxième section de + la page ' . $title ], 'bloc2_paragraphes' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la deuxième section + de la page ' . $title ], 'bloc2_territoire' => [ 'label' => 'Image représentant un territoire', - 'dimensions' => '500px (largeur) par 637px (hauteur)', + 'dimensions' => ['largeur' => '500', 'hauteur' => '637'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/500x637.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/500x637.jpg')), + 'tooltip' => 'Multiple champs décrivant l\'image de la deuxième section de la page ' . $title ], // * BLOC 3 'bloc3_title' => [ 'label' => 'Titre', - 'inputType' => InputType::string(), - 'value' => $faker->words(4, true), - 'validate' => 'required|min:4|max:100', - 'helper' => 'Titre de la troisième section, requiert entre 4 et 100 caractères' + 'inputType' => InputType::text(), + 'text' => $faker->text(), + 'validate' => 'required|string|min:10', + 'helper' => 'Titre de la troisième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le titre de la troisième section de la + page ' . $title ], ] ]); @@ -266,12 +323,14 @@ class CreateEngagementsTable extends Migration $title = 'Agroforesterie'; $config = new Engagement([ 'title' => $title, + 'title_simple' => $title, 'slug' => \Illuminate\Support\Str::slug($title), 'seo_title' => $title . $faker->words(5, true), 'seo_description' => 'Ceci est une description pour le SEO' . $faker->words(5, true), - 'banner' => '/assets/images/engagements/1920x340.jpg', + 'banner' => new SplFileInfo(resource_path('/assets/images/engagements/1920x340.jpg')), 'banner_title' => $title, - 'banner_alt' => 'Image représentant quelque chose', + 'banner_alt' => 'Image d\'un éleveur', + 'banner_dimensions' => ['largeur' => '1920', 'hauteur' => '340'], 'type' => EngagementType::agroforestry(), 'data' => [ @@ -281,28 +340,36 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la première section, requiert minimum 10 caractères' + 'helper' => 'Titre de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le titre de la première section de la + page ' . $title ], 'bloc1_haies' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(4, true), 'validate' => 'required|digits_between:1,9999', - 'helper' => 'Nombre de haies installées, entre 1 et 9.999' + 'helper' => 'Nombre de haies installées, entre 1 et 9 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre de haies installées de la + première section de la page ' . $title ], 'bloc1_arbres' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(9, true), 'validate' => 'required|digits_between:1,999999999', - 'helper' => 'Nombre d\'arbres plantés, entre 1 et 999.999.999' + 'helper' => 'Nombre d\'arbres plantés, entre 1 et 999 999 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre d\'arbres plantés de la + première section de la page ' . $title ], 'bloc1_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la première section de la + page ' . $title ], // * BLOC 2 @@ -311,62 +378,72 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la deuxième section de la + page ' . $title ], 'bloc2_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la deuxième section de la + page ' . $title ], 'bloc2_image1' => [ 'label' => 'Image n°1 des fermiers', - 'dimensions' => '486px (largeur) par 278px (hauteur)', + 'dimensions' => ['largeur' => '486', 'hauteur' => '278'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/486x278.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/486x278.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], 'bloc2_image2' => [ 'label' => 'Image n°2 des fermiers', - 'dimensions' => '486px (largeur) par 278px (hauteur)', + 'dimensions' => ['largeur' => '486', 'hauteur' => '278'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/486x278.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/486x278.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], 'bloc2_image3' => [ 'label' => 'Image n°3 des fermiers', - 'dimensions' => '486px (largeur) par 278px (hauteur)', + 'dimensions' => ['largeur' => '486', 'hauteur' => '278'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/486x278.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/486x278.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], 'bloc2_image4' => [ 'label' => 'Image n°4 des fermiers', - 'dimensions' => '486px (largeur) par 278px (hauteur)', + 'dimensions' => ['largeur' => '486', 'hauteur' => '278'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/486x278.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/486x278.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], 'bloc2_image5' => [ 'label' => 'Image n°5 des fermiers', - 'dimensions' => '486px (largeur) par 278px (hauteur)', + 'dimensions' => ['largeur' => '486', 'hauteur' => '278'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/486x278.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/486x278.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], 'bloc2_image6' => [ 'label' => 'Image n°6 des fermiers', - 'dimensions' => '486px (largeur) par 278px (hauteur)', + 'dimensions' => ['largeur' => '486', 'hauteur' => '278'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => '/assets/images/engagements/486x278.jpg' + 'value' => new SplFileInfo(resource_path('/assets/images/engagements/486x278.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], // * BLOC 3 @@ -375,21 +452,27 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le premier paragraphe de la troisième section de la + page ' . $title ], 'bloc3_paragraphe2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Second paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Second paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le second paragraphe de la troisième section de la + page ' . $title ], 'bloc3_paragraphe3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Troisième paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Troisième paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le troisième paragraphe de la troisième section de + la page ' . $title ], // * BLOC 4 @@ -398,28 +481,36 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la quatrième section de + la page ' . $title ], 'bloc4_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la quatrième section de + la page ' . $title ], 'bloc4_url' => [ 'label' => 'Texte', 'inputType' => InputType::url(), 'value' => $faker->url(), 'validate' => 'required|min:4|max:100', - 'helper' => 'Url de la vidéo de la quatrième section, requiert entre 4 et 100 caractères' + 'helper' => 'Url de la vidéo de la quatrième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le lien d\'une vidéo de la quatrième section de + la page ' . $title ], 'bloc4_btn' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:30', - 'helper' => 'Bouton de la quatrième section, requiert entre 4 et 30 caractères' + 'helper' => 'Bouton de la quatrième section, requiert entre 4 et 30 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le bouton de la quatrième section de + la page ' . $title ], // * BLOC 5 @@ -428,21 +519,27 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la cinquième section de + la page ' . $title ], 'bloc5_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la cinquième section de + la page ' . $title ], 'bloc5_links' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Liens utiles de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Liens utiles de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour les liens utiles de la cinquième section de + la page ' . $title ], // * BLOC 6 @@ -451,7 +548,9 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la sixième section de + la page ' . $title ], ] ]); @@ -460,12 +559,14 @@ class CreateEngagementsTable extends Migration $title = 'Céréales'; $config = new Engagement([ 'title' => $title, + 'title_simple' => $title, 'slug' => \Illuminate\Support\Str::slug($title), 'seo_title' => $title . $faker->words(5, true), 'seo_description' => 'Ceci est une description pour le SEO' . $faker->words(5, true), - 'banner' => '/assets/images/engagements/1920x340.jpg', + 'banner' => new SplFileInfo(resource_path('/assets/images/engagements/1920x340.jpg')), 'banner_title' => $title, - 'banner_alt' => 'Image représentant quelque chose', + 'banner_alt' => 'Image d\'un éleveur', + 'banner_dimensions' => ['largeur' => '1920', 'hauteur' => '340'], 'type' => EngagementType::cereals(), 'data' => [ @@ -475,15 +576,18 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la première section de + la page ' . $title ], 'bloc1_image' => [ 'label' => 'Image représentant les OGM dans l\'alimentation', - 'dimensions' => '412px (largeur) par 319px (hauteur)', + 'dimensions' => ['largeur' => '412', 'hauteur' => '319'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => 'assets/images/engagements/412x319.jpg' + 'value' => new SplFileInfo(resource_path('assets/images/engagements/412x319.jpg')), + 'tooltip' => 'Multiple champs décrivant une image de la première section de la page ' . $title ], // * BLOC 2 @@ -492,14 +596,18 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Sous-titre de la deuxième section, requiert entre 4 et 100 caractères' + 'helper' => 'Sous-titre de la deuxième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le sous-titre de la deuxième section de + la page ' . $title ], 'bloc2_paragraphe' => [ 'label' => 'Texte', - 'inputType' => InputType::string(), - 'value' => $faker->words(4, true), - 'validate' => 'required|min:4|max:100', - 'helper' => 'Paragraphe de la deuxième section, requiert entre 4 et 100 caractères' + 'inputType' => InputType::text(), + 'text' => $faker->text(), + 'validate' => 'required|string|min:10', + 'helper' => 'Paragraphe de la deuxième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la deuxième section de + la page ' . $title ], // * BLOC 3 @@ -508,21 +616,27 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Titre de la troisième section, requiert entre 4 et 100 caractères' + 'helper' => 'Titre de la troisième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le titre de la troisième section de + la page ' . $title ], 'bloc3_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la troisième section de + la page ' . $title ], 'bloc3_links' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Liens utiles de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Liens utiles de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour les liens utiles de la troisième section de + la page ' . $title ], // * BLOC 4 @@ -531,7 +645,9 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la quatrième section de + la page ' . $title ], ] ]); @@ -540,12 +656,14 @@ class CreateEngagementsTable extends Migration $title = 'Bien être animal'; $config = new Engagement([ 'title' => $title, + 'title_simple' => $title, 'slug' => \Illuminate\Support\Str::slug($title), 'seo_title' => $title . $faker->words(5, true), 'seo_description' => 'Ceci est une description pour le SEO' . $faker->words(5, true), - 'banner' => '/assets/images/engagements/1920x340.jpg', + 'banner' => new SplFileInfo(resource_path('/assets/images/engagements/1920x340.jpg')), 'banner_title' => $title, - 'banner_alt' => 'Image représentant quelque chose', + 'banner_alt' => 'Image d\'un éleveur', + 'banner_dimensions' => ['largeur' => '1920', 'hauteur' => '340'], 'type' => EngagementType::animalWelfare(), 'data' => [ @@ -555,7 +673,9 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::url(), 'value' => $faker->url(), 'validate' => 'required|min:4|max:100', - 'helper' => 'Lien vidéo de la première section, requiert entre 4 et 100 caractères' + 'helper' => 'Lien vidéo de la première section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le lien d\'une vidéo de la première section de + la page ' . $title ], // * BLOC 2 @@ -564,14 +684,18 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la deuxième section de + la page ' . $title ], 'bloc2_paragraph' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la deuxième section de + la page ' . $title ], // * BLOC 3 @@ -580,22 +704,27 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la troisième section de + la page ' . $title ], 'bloc3_paragraph' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la troisième section de + la page ' . $title ], 'bloc3_image' => [ 'label' => 'Image d\'un fermier avec une poule', - 'dimensions' => '680px (largeur) par 350px (hauteur)', + 'dimensions' => ['largeur' => '680', 'hauteur' => '350'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => 'assets/images/engagements/680x350.jpg' + 'value' => new SplFileInfo(resource_path('assets/images/engagements/680x350.jpg')), + 'tooltip' => 'Multiple champs décrivant une image de la troisième section de la page ' . $title ], // * BLOC 4 @@ -604,56 +733,73 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la quatrième section de + la page ' . $title ], 'bloc4_number1' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(1, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Premier nombre de la quatrième section, requiert entre 1 et 7 caractères' + 'helper' => 'Premier nombre de la quatrième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le premier nombre de la quatrième section de + la page ' . $title ], 'bloc4_description1' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Première description de la quatrième section, requiert entre 4 et 100 caractères' + 'helper' => 'Première description de la quatrième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour la première description de la quatrième section de + la page ' . $title ], 'bloc4_number2' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(1, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Deuxième nombre de la quatrième section, requiert entre 1 et 7 caractères' + 'helper' => 'Deuxième nombre de la quatrième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le deuxième nombre de la quatrième section de + la page ' . $title ], 'bloc4_description2' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Deuxième description de la quatrième section, requiert entre 4 et 100 caractères' + 'helper' => 'Deuxième description de la quatrième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour la deuxième description de la quatrième section de + la page ' . $title ], 'bloc4_number3' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(1, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Troisième nombre de la quatrième section, requiert entre 1 et 7 caractères' + 'helper' => 'Troisième nombre de la quatrième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le troisième nombre de la quatrième section de + la page ' . $title ], 'bloc4_description3' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Troisième description de la quatrième section, requiert entre 4 et 100 caractères' + 'helper' => 'Troisième description de la quatrième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour la troisième description de la quatrième section + de la page ' . $title ], 'bloc4_data' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Informations supplémentaires relatives aux astérisques, requiert minimum 10 caractères' + 'helper' => 'Informations supplémentaires relatives aux astérisques, requiert minimum 10 + caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour des informations supplémentaires relatives aux + astérisques de la quatrième section de la page ' . $title ], // * BLOC 5 @@ -662,14 +808,18 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la cinquième section de + la page ' . $title ], 'bloc5_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la cinquième section de + la page ' . $title ], // * BLOC 6 @@ -678,56 +828,72 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la sixième section de + la page ' . $title ], 'bloc6_text1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier texte de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le premier paragraphe de la sixième section de + la page ' . $title ], 'bloc6_text2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième texte de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Deuxième paragraphe de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le deuxième paragraphe de la sixième section de + la page ' . $title ], 'bloc6_text3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Troisième texte de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Troisième paragraphe de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le troisième paragraphe de la sixième section de + la page ' . $title ], 'bloc6_text4' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Quatrième texte de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Quatrième paragraphe de la sixième section, requiert minimum 10 caractères', + 'tooltip' => 'Champ de texte riche, désigné pour le quatrième paragraphe de la sixième section de + la page ' . $title ], 'bloc6_text5' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Cinquième texte de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Cinquième paragraphe de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le cinquième paragraphe de la sixième section de + la page ' . $title ], 'bloc6_text6' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Sixième texte de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Sixième paragraphe de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le sixième paragraphe de la sixième section de + la page ' . $title ], 'bloc6_subtitle' => [ 'label' => 'Sous-titre', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Sous-titre de la sixième section, requiert minimum 10 caractères' + 'helper' => 'Sous-titre de la sixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le sous-titre de la sixième section de + la page ' . $title ], // * BLOC 7 @@ -736,14 +902,18 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la septième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la septième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la septième section de + la page ' . $title ], 'bloc7_subtitle' => [ 'label' => 'Sous-titre', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Sous-titre de la septième section, requiert minimum 10 caractères' + 'helper' => 'Sous-titre de la septième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le sous-titre de la septième section de + la page ' . $title ], // * BLOC 8 @@ -752,14 +922,18 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la huitième sectio, requiert minimum 10 caractères' + 'helper' => 'Titre de la huitième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la huitième section de + la page ' . $title ], 'bloc8_subtitle' => [ 'label' => 'Sous-titre', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Sous-titre de la huitième section, requiert minimum 10 caractères' + 'helper' => 'Sous-titre de la huitième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le sous-titre de la huitième section de + la page ' . $title ], // * BLOC 9 @@ -768,42 +942,54 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la neuvième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la neuvième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la neuvième section de + la page ' . $title ], 'bloc9_text1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier texte de la neuvième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la neuvième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le premier paragraphe de la neuvième section de + la page ' . $title ], 'bloc9_text2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Second texte de la neuvième section, requiert minimum 10 caractères' + 'helper' => 'Second paragraphe de la neuvième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le second paragraphe de la neuvième section de + la page ' . $title ], 'bloc9_text3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Troisième texte de la neuvième section, requiert minimum 10 caractères' + 'helper' => 'Troisième paragraphe de la neuvième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le troisième paragraphe de la neuvième section de + la page ' . $title ], 'bloc9_text4' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Quatrième texte de la neuvième section, requiert minimum 10 caractères' + 'helper' => 'Quatrième paragraphe de la neuvième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le quatrième paragraphe de la neuvième section de + la page ' . $title ], 'bloc9_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la neuvième section, requiert minimum 10 caractères' + 'helper' => 'Dernier paragraphe de la neuvième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le dernier paragraphe de la neuvième section de + la page ' . $title ], // * BLOC 10 @@ -812,28 +998,36 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la dixième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la dixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la dixième section de + la page ' . $title ], 'bloc10_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la dixième section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la dixième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la dixième section de + la page ' . $title ], 'bloc10_presidentName' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:4|max:100', - 'helper' => 'Nom du président de l\'association, requiert entre 4 et 100 caractères' + 'helper' => 'Nom du président de l\'association, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du président de l\'association + de la dixième section de la page ' . $title ], 'bloc10_presidentFunction' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du président de l\'association, requiert minimum 10 caractères' + 'helper' => 'Fonction du président de l\'association, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour la fonction du président de l\'association + de la dixième section de la page ' . $title ], // * BLOC 11 @@ -842,42 +1036,54 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la onzième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la onzième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la onzième section de + la page ' . $title ], 'bloc11_paragraphe1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier paragraphe de la onzième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la onzième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le premier paragraphe de la onzième + section de la page ' . $title ], 'bloc11_paragraphe2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième paragraphe de la onzième section, requiert minimum 10 caractères' + 'helper' => 'Deuxième paragraphe de la onzième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le deuxième paragraphe de la onzième + section de la page ' . $title ], 'bloc11_text1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier texte de la onzième section, requiert minimum 10 caractères' + 'helper' => 'Troisième texte de la onzième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le troisième paragraphe de la onzième + section de la page ' . $title ], 'bloc11_text2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième texte de la onzième section, requiert minimum 10 caractères' + 'helper' => 'Quatrième texte de la onzième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le quatrième paragraphe de la onzième + section de la page ' . $title ], 'bloc11_text3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Troisième texte de la onzième section, requiert minimum 10 caractères' + 'helper' => 'Cinquième texte de la onzième section, requiert minimum 10 caractères', + 'tooltip' => 'Champ de texte simple, désigné pour le cinquième paragraphe de la onzième + section de la page ' . $title ], ] ]); @@ -886,12 +1092,14 @@ class CreateEngagementsTable extends Migration $title = 'Apiculture'; $config = new Engagement([ 'title' => $title, + 'title_simple' => $title, 'slug' => \Illuminate\Support\Str::slug($title), 'seo_title' => $title . $faker->words(5, true), 'seo_description' => 'Ceci est une description pour le SEO' . $faker->words(5, true), - 'banner' => '/assets/images/engagements/1920x340.jpg', + 'banner' => new SplFileInfo(resource_path('/assets/images/engagements/1920x340.jpg')), 'banner_title' => $title, - 'banner_alt' => 'Image représentant quelque chose', + 'banner_alt' => 'Image d\'un éleveur', + 'banner_dimensions' => ['largeur' => '1920', 'hauteur' => '340'], 'type' => EngagementType::beekeeping(), 'data' => [ @@ -901,49 +1109,63 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la première section, requiert minimum 10 caractères' + 'helper' => 'Titre de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la première section de + la page ' . $title ], 'bloc1_text1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier texte de la première section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le premier paragraphe de la première + section de la page ' . $title ], 'bloc1_text2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième texte de la première section, requiert minimum 10 caractères' + 'helper' => 'Deuxième paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le deuxième paragraphe de la première + section de la page ' . $title ], 'bloc1_subtitle' => [ 'label' => 'Sous-titre', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Sous-titre de la première section, requiert minimum 10 caractères' + 'helper' => 'Sous-titre de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le sous-titre de la première section de + la page ' . $title ], 'bloc1_paragraphe' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères' + 'helper' => 'Paragraphe de la première section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le paragraphe de la première + section de la page ' . $title ], 'bloc1_number1' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(3, true), 'validate' => 'required|numeric|min:0,max:999', - 'helper' => 'Nombre de ruches installées, entre 1 et 999' + 'helper' => 'Nombre de ruches installées, entre 1 et 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre de ruches installées de la + première section de la page ' . $title ], 'bloc1_number2' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(9, true), 'validate' => 'required|numeric|min:0,max:999999999', - 'helper' => 'Nombre d\'abeilles hébergées, entre 1 et 999.999.999' + 'helper' => 'Nombre d\'abeilles hébergées, entre 1 et 999 999 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre d\'abeilles hébergées de la + première section de la page ' . $title ], // * BLOC 2 @@ -952,29 +1174,36 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la deuxième section de + la page ' . $title ], 'bloc2_paragraphe1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier paragraphe de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le premier paragraphe de la deuxième + section de la page ' . $title ], 'bloc2_paragraphe2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième paragraphe de la deuxième section, requiert minimum 10 caractères' + 'helper' => 'Deuxième paragraphe de la deuxième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le deuxième paragraphe de la deuxième + section de la page ' . $title ], 'bloc2_image' => [ 'label' => 'Image de plusieurs poules', - 'dimensions' => '525px (largeur) par 451px (hauteur)', + 'dimensions' => ['largeur' => '525', 'hauteur' => '451'], 'inputType' => InputType::image(), 'alt' => $faker->words(4, true), 'title' => $faker->words(4, true), - 'value' => 'assets/images/engagements/525x451.jpg' + 'value' => new SplFileInfo(resource_path('assets/images/engagements/525x451.jpg')), + 'tooltip' => 'Multiple champs décrivant une des images de la deuxième section de la page ' . $title ], // * BLOC 3 @@ -983,42 +1212,54 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la troisième section de + la page ' . $title ], 'bloc3_paragraphe1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le premier paragraphe de la troisième + section de la page ' . $title ], 'bloc3_paragraphe2 ' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => '"Le saviez-vous" de la troisième section, requiert minimum 10 caractères' + 'helper' => '"Le saviez-vous" de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le "Le saviez-vous" de la troisième + section de la page ' . $title ], 'bloc3_paragraphe3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième paragraphe de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Deuxième paragraphe de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le deuxième paragraphe de la troisième + section de la page ' . $title ], 'bloc3_farmerName' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:1|max:36', - 'helper' => 'Nom du fermier de la troisième section, requiert entre 1 et 36 caractères' + 'helper' => 'Nom du fermier de la troisième section, requiert entre 1 et 36 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du fermier de la troisième + section de la page ' . $title ], 'bloc3_farmerFunction' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du fermier de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Fonction du fermier de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour la fonction du fermier de la troisième + section de la page ' . $title ], // * BLOC 4 @@ -1027,42 +1268,54 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la quatrième section de + la page ' . $title ], 'bloc4_paragraphe1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Premier paragraphe de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Premier paragraphe de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le premier paragraphe de la quatrième + section de la page ' . $title ], 'bloc4_url' => [ 'label' => 'Texte', 'inputType' => InputType::url(), 'value' => $faker->url(), 'validate' => 'required|min:4|max:100', - 'helper' => 'Url de la vidéo de la quatrième section, requiert entre 4 et 100 caractères' + 'helper' => 'Url de la vidéo de la quatrième section, requiert entre 4 et 100 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le lien d\'une vidéo de la quatrième section de + la page ' . $title ], 'bloc4_paragraphe2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Second paragraphe de la quatrième section, requiert minimum 10 caractères' + 'helper' => 'Second paragraphe de la quatrième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le second paragraphe de la quatrième + section de la page ' . $title ], 'bloc4_farmerName' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Nom du fermier de la troisième section, requiert entre 1 et 7 caractères' + 'helper' => 'Nom du fermier de la troisième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du fermier de la quatrième + section de la page ' . $title ], 'bloc4_farmerFunction' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du fermier de la troisième section, requiert minimum 10 caractères' + 'helper' => 'Fonction du fermier de la troisième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour la fonction du fermier de la quatrième + section de la page ' . $title ], // * BLOC 5 @@ -1071,119 +1324,155 @@ class CreateEngagementsTable extends Migration 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Titre de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Titre de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour le titre de la cinquième section de + la page ' . $title ], 'bloc5_number1' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(2, true), 'validate' => 'required|numeric|min:1,max:99', - 'helper' => 'Nombre de ruches, entre 1 et 99' + 'helper' => 'Nombre de ruches, entre 1 et 99.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre de ruches de la + cinquième section de la page ' . $title ], 'bloc5_quote1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Première citation de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Première citation de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la première citation de la cinquième section de + la page ' . $title ], 'bloc5_farmerName1' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Nom du premier fermier de la cinquième section, requiert entre 1 et 7 caractères' + 'helper' => 'Nom du premier fermier de la cinquième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du premier fermier de la cinquième section + de la page ' . $title ], 'bloc5_farmerFunction1' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du premier fermier de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Fonction du premier fermier de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la fonction du premier fermier de la cinquième + section de la page ' . $title ], 'bloc5_number2' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(4, true), 'validate' => 'required|numeric|min:1,max:9999', - 'helper' => 'Nombre de ruches, entre 1 et 9.999' + 'helper' => 'Nombre de ruches, entre 1 et 9 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre de ruches de la + cinquième section de la page ' . $title ], 'bloc5_quote2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Deuxième citation de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Deuxième citation de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la deuxième citation de la cinquième section de + la page ' . $title ], 'bloc5_farmerName2' => [ 'label' => 'Nom du fermier de la cinquième section', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Nom du deuxième fermier de la cinquième section, requiert entre 1 et 7 caractères' + 'helper' => 'Nom du deuxième fermier de la cinquième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du deuxième fermier de la cinquième section + de la page ' . $title ], 'bloc5_farmerFunction2' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du deuxième fermier de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Fonction du deuxième fermier de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la fonction du deuxième fermier de la cinquième + section de la page ' . $title ], 'bloc5_number3' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(4, true), 'validate' => 'required|numeric|min:1,max:9999', - 'helper' => 'Nombre de ruches, entre 1 et 9.999' + 'helper' => 'Nombre de ruches, entre 1 et 9 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre de ruches de la + cinquième section de la page ' . $title ], 'bloc5_quote3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Troisième citation de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Troisième citation de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la troisième citation de la cinquième section de + la page ' . $title ], 'bloc5_farmerName3' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Nom du troisième fermier de la cinquième section, requiert entre 1 et 7 caractères' + 'helper' => 'Nom du troisième fermier de la cinquième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du troisième fermier de la cinquième section + de la page ' . $title ], 'bloc5_farmerFunction3' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du troisième fermier de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Fonction du troisième fermier de la cinquième section, requiert minimum 10 + caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la fonction du troisième fermier de la cinquième + section de la page ' . $title ], 'bloc5_number4' => [ 'label' => 'Nombre', 'inputType' => InputType::number(), 'value' => $faker->randomNumber(4, true), 'validate' => 'required|numeric|min:1,max:9999', - 'helper' => 'Nombre de ruches, entre 1 et 9.999' + 'helper' => 'Nombre de ruches, entre 1 et 9 999.', + 'tooltip' => 'Champ de chiffre/nombre, désigné pour le nombre de ruches de la + cinquième section de la page ' . $title ], 'bloc5_quote4' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Quatrième citation de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Quatrième citation de la cinquième section, requiert minimum 10 caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la quatrième citation de la cinquième section de + la page ' . $title ], 'bloc5_farmerName4' => [ 'label' => 'Texte', 'inputType' => InputType::string(), 'value' => $faker->words(4, true), 'validate' => 'required|min:1|max:7', - 'helper' => 'Nom du quatrième fermier de la cinquième section, requiert entre 1 et 7 caractères' + 'helper' => 'Nom du quatrième fermier de la cinquième section, requiert entre 1 et 7 caractères.', + 'tooltip' => 'Champ de texte simple, désigné pour le nom du quatrième fermier de la cinquième section + de la page ' . $title ], 'bloc5_farmerFunction4' => [ 'label' => 'Texte', 'inputType' => InputType::text(), 'text' => $faker->text(), 'validate' => 'required|string|min:10', - 'helper' => 'Fonction du quatrième fermier de la cinquième section, requiert minimum 10 caractères' + 'helper' => 'Fonction du quatrième fermier de la cinquième section, requiert minimum 10 + caractères.', + 'tooltip' => 'Champ de texte riche, désigné pour la fonction du quatrième fermier de la cinquième + section de la page ' . $title ], ] ]); diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php index c2ee29d..1886c5c 100644 --- a/resources/lang/en/models.php +++ b/resources/lang/en/models.php @@ -8,15 +8,6 @@ return [ 'ranges' => 'Ranges', 'products' => 'Products', 'recipes' => 'Recipes', - 'breeders' => 'Breeders', - 'engagements' => [ - 'title' => 'Engagements', - 'renewableEnergies' => 'Renewable energy', - 'localCereals' => 'Sustainable livestock / Local cereals', - 'agroforestry' => 'Agroforestry', - 'cereals' => 'Cereals', - 'animalWelfare' => 'Animal welfare', - 'beekeeping' => 'Beekeeping', - ], + 'breeders' => 'Breeders' ] ]; diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index d0333ef..3693f8b 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -9,14 +9,6 @@ return [ 'products' => 'Produits', 'recipes' => 'Recettes', 'breeders' => 'Éleveurs', - 'engagements' => [ - 'title' => 'Engagements', - 'renewableEnergies' => 'Énergies renouvelables', - 'localCereals' => 'Élevage durable / Céréales locales', - 'agroforestry' => 'Agroforesteries', - 'cereals' => 'Céréales', - 'animalWelfare' => 'Bien être animal', - 'beekeeping' => 'Apiculture', - ], + 'engagements' => 'Engagements' ] ]; diff --git a/resources/ts/components/bo/CkEditorBoComponent.vue b/resources/ts/components/bo/CkEditorBoComponent.vue index b798c73..0a2e8a7 100644 --- a/resources/ts/components/bo/CkEditorBoComponent.vue +++ b/resources/ts/components/bo/CkEditorBoComponent.vue @@ -137,9 +137,6 @@ export default defineComponent({ "|", "removeFormat", "|", - "undo", - "redo", - "|", "sourceEditing", ], }, diff --git a/resources/ts/components/bo/ImageInputComponent.vue b/resources/ts/components/bo/ImageInputComponent.vue index 00a9390..590a19b 100644 --- a/resources/ts/components/bo/ImageInputComponent.vue +++ b/resources/ts/components/bo/ImageInputComponent.vue @@ -109,8 +109,8 @@ class="fw-bold" > {{ humanFileSize(intOriginalFile.size) }} - {{ `hauteur ${intOriginalFileDimensions.height}px` }} {{ `largeur ${intOriginalFileDimensions.width}px` }} + {{ `hauteur ${intOriginalFileDimensions.height}px` }} title) +@section('title', $engagement->title_simple ) @section('breadcrumb', request()->route()->getName()) @@ -11,13 +11,13 @@ @method('PUT') @csrf
-
+
@include('back.engagements.form') @include('back.modules.minicrud-form-inputs', ['model' => $engagement])
diff --git a/resources/views/back/engagements/form.blade.php b/resources/views/back/engagements/form.blade.php index e4f3c55..cdc42ab 100644 --- a/resources/views/back/engagements/form.blade.php +++ b/resources/views/back/engagements/form.blade.php @@ -1,93 +1,132 @@ -
- -
- @php - $data = [ - 'name' => 'title', - 'text' => old('title', $engagement->title ?? ''), - ]; - @endphp -
- @include('back.modules.input-error', [ - 'inputName' => 'title', - 'helperId' => 'engagementTitleHelp', - 'helper' => __('Doit être un titre court (max 35 caractères)') - ]) -
-
- -
- -
-
- -
'engagementSeoTitle'])'>
- - @include('back.modules.input-error', [ - 'inputName' => 'seo_title', 'helperId' => 'engagementSeoTitleHelp' , 'helper' => __('Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 53 et 60 caractères, balise ') - ]) - </div> - <div class="col-12 col-md-6 form-group"> - <label for="engagementSeoDescription" class="col-form-label"><b>{{ __('Description seo') }}*</b></label> - @php - $data = [ - 'id' => 'engagementSeoDescription', - 'class' => 'form-control', - 'name' => 'seo_description', - 'placeholder' => __('Une description du contenu de la page pour les moteurs de recherche'), - 'ariaDescribedby' => 'engagementSeoDescriptionHelp', - 'value' => old('engagementSeoDescription', $engagement->seo_description), - 'required' => true, - 'value' => old('seo_description', $engagement->seo_description ?? '') - ]; - @endphp - <div id="input-text-auto" data-json='@json($data)'></div> - @include('back.modules.input-error', [ - 'inputName' => 'seo_description', - 'helperId' => 'engagementSeoDescriptionHelp', - 'helper' => __(' Doit décrire précisément le contenu de la page, requiert entre 50 et 160 caractères, balise <meta> description') - ]) - </div> -</div> - -<hr class="w-25 mx-auto"> - -<div class="row mb-3"> - <div class="col-12 col-md-6 form-group"> - <label for="engagementBannerAlt" class="col-form-label"><b>{{ __('Texte alternatif de la bannière') }}*</b></label> - <div class="word-counter" data-json='@json(['id' => 'engagementBannerAlt'])'></div> - <input name="banner_alt" type="text" value="{{ old('banner_alt', $engagement->banner_alt ?? '') }}" class="form-control" - id="engagementBannerAlt" aria-describedby="engagementBannerAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required> - @include('back.modules.input-error', [ - 'inputName' => 'banner_alt', - 'helperId' => 'engagementBannerAltHelp', - 'helper' => __('Doit décrire votre bannière pour les moteurs de recherches et les non voyants (5 à 6 mots)') - ]) - </div> - <div class="col-12 col-md-6 form-group"> - <label for="engagementBannerTitle" class="col-form-label"><b>{{ __('Titre de la bannière') }}*</b></label> - <div class="word-counter" data-json='@json(['id' => 'engagementBannerTitle'])'></div> - <input name="banner_title" type="text" value="{{ old('banner_title', $engagement->banner_title ?? '') }}" class="form-control" - id="engagementBannerTitle" aria-describedby="engagementBannerTitleHelp" placeholder="{{ __('Une description de l\'article pour les moteurs de recherche') }}" required> - @include('back.modules.input-error', [ - 'inputName' => 'banner_title', - 'helperId' => 'engagementBannerTitleHelp' , - 'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière') - ]) - </div> -</div> <div class="row"> - <div class="col-12"> - @php - $data = [ - 'id' => 'engagementBanner', - 'name' => 'banner', - 'helper' => __('L\'image qui va servir de bannière, doit être de 1920px (largeur) par 340px (hauteur)'), - 'value' => $engagement->banner ?? '', - ]; - @endphp - @include('back.modules.input-error', ['inputName' => 'banner']) - <div id="image-input" data-json='@json($data)'></div> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <legend>{{ __('Informations générales') }}</legend> + <div class="row mb-3"> + <div class="col-12 col-md-6 form-group"> + <label for="engagementTitle" class="col-form-label"><b>{{ __('Titre') }}</b></label> + @php + $data = [ + 'name' => 'title', + 'csrf' => csrf_token(), + 'text' => old('title', $engagement->title ?? ''), + ]; + @endphp + <div id="ck-editor-bo" data-json='@json($data)'></div> + @include('back.modules.input-error', [ + 'inputName' => 'title', + 'helperId' => 'engagementTitleHelp' , + 'helper' => __('Doit être un titre court (max 35 caractères).') + ]) + </div> + <div class="col-12 col-md-6 form-group"> + <label for="engagementTitreSimple" class="col-form-label"> + <b>{{ __('Titre sans style') }}*</b> + </label> + <div class="word-counter" data-json='@json(['id' => 'engagementTitreSimple'])'></div> + <input name="title_simple" type="text" value="{{ old('title_simple', $engagement->title_simple ?? '') }}" class="form-control" + id="engagementTitreSimple" aria-describedby="engagementTitreSimpleHelp" placeholder="{{ __('Une image de petits chatons') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'title_simple', + 'helperId' => 'engagementTitreSimpleHelp', + 'helper' => __('Doit être un titre court (max 35 caractères) sans style.') + ]) + </div> + </div> + </fieldset> + </div> +</div> + +<div class="row"> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <legend>{{ __('Seo') }}</legend> + <div class="row mb-3"> + <div class="col-12 col-md-6 form-group"> + <label for="engagementSeoTitle" class="col-form-label"> + <b>{{ __('Titre seo') }}*</b> + </label> + <div class="word-counter" data-json='@json(['id' => 'engagementSeoTitle'])'></div> + <input name="seo_title" type="text" value="{{ old('seo_title', $engagement->seo_title ?? '') }}" class="form-control" + id="engagementSeoTitle" aria-describedby="engagementSeoTitleHelp" placeholder="{{ __('Un titre en 70 caractères maximum') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'seo_title', + 'helperId' => 'engagementSeoTitleHelp' , + 'helper' => __('Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 53 et 60 caractères, balise <title>.') + ]) + </div> + <div class="col-12 col-md-6 form-group"> + <label for="engagementSeoDescription" class="col-form-label"> + <b>{{ __('Description seo') }}</b> + </label> + @php + $data = [ + 'id' => 'engagementSeoDescription', + 'class' => 'form-control', + 'name' => 'seo_description', + 'placeholder' => __('Une description du contenu de la page pour les moteurs de recherche.'), + 'ariaDescribedby' => 'engagementSeoDescriptionHelp', + 'value' => old('engagementSeoDescription', $engagement->seo_description), + 'required' => true, + 'value' => old('seo_description', $engagement->seo_description ?? '') + ]; + @endphp + <div id="input-text-auto" data-json='@json($data)'></div> + @include('back.modules.input-error', [ + 'inputName' => 'seo_description', + 'helperId' => 'engagementSeoDescriptionHelp', + 'helper' => __(' Doit décrire précisément le contenu de la page, requiert entre 50 et 160 caractères, balise <meta> description.') + ]) + </div> + </div> + </fieldset> + </div> +</div> + +<div class="row"> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <legend>{{ __('Bannière') }}</legend> + <div class="row mb-3"> + <div class="col-12 col-md-6 form-group"> + <label for="engagementBannerAlt" class="col-form-label"><b>{{ __('Texte alternatif de la bannière') }}*</b></label> + <div class="word-counter" data-json='@json(['id' => 'engagementBannerAlt'])'></div> + <input name="banner_alt" type="text" value="{{ old('banner_alt', $engagement->banner_alt ?? '') }}" class="form-control" + id="engagementBannerAlt" aria-describedby="engagementBannerAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'banner_alt', + 'helperId' => 'engagementBannerAltHelp', + 'helper' => __('Doit décrire votre bannière pour les moteurs de recherches et les non voyants (5 à 6 mots).') + ]) + </div> + <div class="col-12 col-md-6 form-group"> + <label for="engagementBannerTitle" class="col-form-label"><b>{{ __('Titre de la bannière') }}*</b></label> + <div class="word-counter" data-json='@json(['id' => 'engagementBannerTitle'])'></div> + <input name="banner_title" type="text" value="{{ old('banner_title', $engagement->banner_title ?? '') }}" class="form-control" + id="engagementBannerTitle" aria-describedby="engagementBannerTitleHelp" placeholder="{{ __('Une description de l\'article pour les moteurs de recherche') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'banner_title', + 'helperId' => 'engagementBannerTitleHelp' , + 'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière.') + ]) + </div> + </div> + <div class="row"> + <div class="col-12"> + @php + $data = [ + 'id' => 'engagementBanner', + 'name' => 'banner', + 'helper' => __('L\'image qui va servir de bannière, doit être de 1920px (largeur) par 340px (hauteur).'), + 'value' => $engagement->banner ?? '', + 'width' => $engagement->banner_dimensions['largeur'], + 'height' => $engagement->banner_dimensions['hauteur'] + ]; + @endphp + @include('back.modules.input-error', ['inputName' => 'banner']) + <div id="image-input" data-json='@json($data)'></div> + </div> + </div> + </fieldset> </div> </div> diff --git a/resources/views/back/layouts/nav-menu.blade.php b/resources/views/back/layouts/nav-menu.blade.php index 40f20e3..15dca14 100644 --- a/resources/views/back/layouts/nav-menu.blade.php +++ b/resources/views/back/layouts/nav-menu.blade.php @@ -1,59 +1,57 @@ @auth('backend') <ul class="navbar-nav me-auto"> <li class="nav-item"> - <a class="nav-link @if(request()->routeIs('bo.news.*')) active @endif" href="{{ route('bo.news.index') }}" title="{{ __('Administrer les :model', [ - 'model' => __('models.classes.news') - ]) }}" data-bs-toggle="tooltip">{{ __('models.classes.news') }}</a> + <a class="nav-link @if(request()->routeIs('bo.news.*')) active @endif" href="{{ route('bo.news.index') }}" + title="{{ __('Administrer les :model', ['model' => __('models.classes.news')]) }}" + data-bs-toggle="tooltip"> + {{ __('models.classes.news') }} + </a> </li> <li class="nav-item"> - <a class="nav-link @if(request()->routeIs('bo.tags.*')) active @endif" href="{{ route('bo.tags.index') }}" title="{{ __('Administrer les :model', [ - 'model' => __('models.classes.tags') - ]) }}" data-bs-toggle="tooltip">{{ __('models.classes.tags') }}</a> + <a class="nav-link @if(request()->routeIs('bo.tags.*')) active @endif" href="{{ route('bo.tags.index') }}" + title="{{ __('Administrer les :model', ['model' => __('models.classes.tags')]) }}" + data-bs-toggle="tooltip"> + {{ __('models.classes.tags') }} + </a> </li> <li class="nav-item"> - <a class="nav-link @if(request()->routeIs('bo.sliders.*')) active @endif" href="{{ route('bo.sliders.index') }}" title="{{ __('Administrer les :model', [ - 'model' => __('models.classes.sliders') - ]) }}" data-bs-toggle="tooltip">{{ __('models.classes.sliders') }}</a> + <a class="nav-link @if(request()->routeIs('bo.sliders.*')) active @endif" href="{{ route('bo.sliders.index') }}" + title="{{ __('Administrer les :model', ['model' => __('models.classes.sliders')]) }}" + data-bs-toggle="tooltip"> + {{ __('models.classes.sliders') }} + </a> </li> <li class="nav-item"> - <a class="nav-link @if(request()->routeIs('bo.ranges.*')) active @endif" href="{{ route('bo.ranges.index') }}" title="{{ __('Administrer les :model', [ - 'model' => __('models.classes.ranges') - ]) }}" data-bs-toggle="tooltip">{{ __('models.classes.ranges') }}</a> + <a class="nav-link @if(request()->routeIs('bo.ranges.*')) active @endif" href="{{ route('bo.ranges.index') }}" + title="{{ __('Administrer les :model', ['model' => __('models.classes.ranges')]) }}" + data-bs-toggle="tooltip"> + {{ __('models.classes.ranges') }} + </a> </li> <li class="nav-item"> - <a class="nav-link @if(request()->routeIs('bo.products.*')) active @endif" href="{{ route('bo.products.index') }}" title="{{ __('Administrer les :model', [ - 'model' => __('models.classes.products') - ]) }}" data-bs-toggle="tooltip">{{ __('models.classes.products') }}</a> + <a class="nav-link @if(request()->routeIs('bo.products.*')) active @endif" href="{{ route('bo.products.index') }}" + title="{{ __('Administrer les :model', ['model' => __('models.classes.products')]) }}" + data-bs-toggle="tooltip"> + {{ __('models.classes.products') }} + </a> </li> <li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle @if(request()->routeIs('bo.engagements.*')) active @endif" role="button" data-bs-toggle="dropdown" aria-expanded="false"> - {{ __('models.classes.engagements.title') }} + <a class="nav-link dropdown-toggle @if(request()->routeIs('bo.engagements.*')) active @endif" + role="button" + data-bs-toggle="dropdown" + aria-expanded="false"> + {{ __('models.classes.engagements') }} </a> <div class="dropdown-menu p-0"> - <a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'energies-renouvelables') }}" title="{{ __('Administrer la page :model', [ - 'model' => __('models.classes.engagements.renewableEnergies')]) }}" data-bs-toggle="tooltip" data-bs-placement="right"> - {{ __('models.classes.engagements.renewableEnergies') }} - </a> - <a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'elevage-durable-cereales-locales') }}" title="{{ __('Administrer la page :model', [ - 'model' => __('models.classes.engagements.localCereals')]) }}" data-bs-toggle="tooltip" data-bs-placement="right"> - {{ __('models.classes.engagements.localCereals') }} - </a> - <a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'agroforesterie') }}" title="{{ __('Administrer la page :model', [ - 'model' => __('models.classes.engagements.agroforestry')]) }}" data-bs-toggle="tooltip" data-bs-placement="right"> - {{ __('models.classes.engagements.agroforestry') }} - </a> - <a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'cereales') }}" title="{{ __('Administrer la page :model', [ - 'model' => __('models.classes.engagements.cereals')]) }}" data-bs-toggle="tooltip" data-bs-placement="right"> - {{ __('models.classes.engagements.cereals') }} - </a> - <a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'bien-etre-animal') }}" title="{{ __('Administrer la page :model', [ - 'model' => __('models.classes.engagements.animalWelfare')]) }}" data-bs-toggle="tooltip" data-bs-placement="right"> - {{ __('models.classes.engagements.animalWelfare') }} - </a> - <a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'apiculture') }}" title="{{ __('Administrer la page :model', [ - 'model' => __('models.classes.engagements.beekeeping')]) }}" data-bs-toggle="tooltip" data-bs-placement="right"> - {{ __('models.classes.engagements.beekeeping') }} - </a> + @foreach ($globalEngagements as $globalEngagement) + <a class="nav-link dropdown-item" + href="{{ route('bo.engagements.edit', $globalEngagement->slug) }}" + title="{{ __('Administrer la page :model', ['model' => $globalEngagement->title_simple]) }}" + data-bs-toggle="tooltip" + data-bs-placement="right"> + {{ $globalEngagement->title_simple }} + </a> + @endforeach </div> </li> </ul> diff --git a/resources/views/back/modules/minicrud-form-inputs.blade.php b/resources/views/back/modules/minicrud-form-inputs.blade.php index 6728c7a..424e397 100644 --- a/resources/views/back/modules/minicrud-form-inputs.blade.php +++ b/resources/views/back/modules/minicrud-form-inputs.blade.php @@ -1,6 +1,5 @@ <div id="minicrud"> @foreach ($model->data as $k => $input) - <hr class="w-25 mx-auto"> @php $input = json_decode(json_encode($input), true); @endphp @@ -8,118 +7,197 @@ @case(App\Enums\InputType::url()->value) @case(App\Enums\InputType::string()->value) - <div class="form-group"> - <label for="{{$k}}"><b>{{ $input['label'] }}*</b></label> - <div class="word-counter" data-json='@json(['id' => $k])'></div> - <input id="{{$k}}" type="text" class="form-control" name="data[{{$k}}][value]" - placeholder="{{ $input['label'] }}" value="{{ old("data.$k.value") ?? ($input['value'] ?? '') }}" required> - @include('back.modules.input-error', [ - 'inputName' => 'data[{{$k}}][value]', - 'helperId' => '{{$k}}StringHelp', - 'helper' => $input['helper'], - ]) + <div class="row"> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <div class="d-flex flex-row justify-content-start align-items-center mb-2"> + <legend class="w-auto m-0">{{ $input['label'] }}</legend> + @if(!empty($input['tooltip'])) + <span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}"> + <i class="fa fa-question-circle"></i> + </span> + @endif + </div> + <div class="row mb-3"> + <div class="form-group"> + <div class="word-counter" data-json='@json(['id' => $k])'></div> + <input id="{{$k}}" type="text" class="form-control" name="data[{{$k}}][value]" + placeholder="{{ $input['label'] }}" value="{{ old("data.$k.value") ?? ($input['value'] ?? '') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'data[{{$k}}][value]', + 'helperId' => '{{$k}}StringHelp', + 'helper' => $input['helper'], + ]) + </div> + </div> + </fieldset> + </div> </div> @break @case(App\Enums\InputType::number()->value) - <div class="form-group"> - <label for="{{$k}}"><b>{{ $input['label'] }}*</b></label> - <input id="{{$k}}" type="number" step="0.01" class="form-control" - name="data[{{ $k }}][value]" placeholder="{{ $input['label'] }}" - value="{{ old("data.$k.value") ?? ($input['value'] ?? '') }}" required> - @include('back.modules.input-error', [ - 'inputName' => 'data[{{$k}}][value]', - 'helperId' => '{{$k}}NumberHelp', - 'helper' => $input['helper'], - ]) + <div class="row"> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <div class="d-flex flex-row justify-content-start align-items-center mb-2"> + <legend class="w-auto m-0">{{ $input['label'] }}</legend> + @if(!empty($input['tooltip'])) + <span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}"> + <i class="fa fa-question-circle"></i> + </span> + @endif + </div> + <div class="row mb-3"> + <div class="form-group"> + <input id="{{$k}}" type="number" step="0.01" class="form-control" + name="data[{{ $k }}][value]" placeholder="{{ $input['label'] }}" + value="{{ old("data.$k.value") ?? ($input['value'] ?? '') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'data[{{$k}}][value]', + 'helperId' => '{{$k}}NumberHelp', + 'helper' => $input['helper'], + ]) + </div> + </div> + </fieldset> + </div> </div> @break @case(App\Enums\InputType::text()->value) - <div class="form-group vueComponent"> - <label class="col-form-label"><b>{{ $input['label'] }}*</b></label> - @php - $editor = [ - 'csrf' => csrf_token(), - 'name' => "data[$k][text]", - 'text' => old("data.$k.text") ?? ($input['text'] ?? ''), - ]; - @endphp - <div class="ck-editor-bo" data-json='@json($editor)'></div> - @include('back.modules.input-error', [ - 'inputName' => 'data[{{$k}}][text]', - 'helperId' => '{{$k}}TextHelp', - 'helper' => $input['helper'], - ]) + <div class="row"> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <div class="d-flex flex-row justify-content-start align-items-center mb-2"> + <legend class="w-auto m-0">{{ $input['label'] }}</legend> + @if(!empty($input['tooltip'])) + <span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}"> + <i class="fa fa-question-circle"></i> + </span> + @endif + </div> + <div class="row mb-3"> + <div class="form-group vueComponent"> + @php + $editor = [ + 'csrf' => csrf_token(), + 'name' => "data[$k][text]", + 'text' => old("data.$k.text") ?? ($input['text'] ?? ''), + ]; + @endphp + <div class="ck-editor-bo" data-json='@json($editor)'></div> + @include('back.modules.input-error', [ + 'inputName' => 'data[{{$k}}][text]', + 'helperId' => '{{$k}}TextHelp', + 'helper' => $input['helper'], + ]) + </div> + </div> + </fieldset> + </div> </div> @break @case(App\Enums\InputType::boolean()->value) - <div class="form-group"> - <label class="user-select-none"> - <input type="checkbox" name="data[{{$k}}][value]" value="1" - @if (old("data.$k.value") ?? ($input['value'] ?? 0)) checked @endif> - {{ $input['label'] }}* - @include('back.modules.input-error', [ - 'inputName' => 'data[{{$k}}][value]', - 'helperId' => '{{$k}}BooleanHelp', - 'helper' => $input['helper'], - ]) - </label> + <div class="row"> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <div class="d-flex flex-row justify-content-start align-items-center mb-2"> + <legend class="w-auto m-0">{{ $input['label'] }}</legend> + @if(!empty($input['tooltip'])) + <span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}"> + <i class="fa fa-question-circle"></i> + </span> + @endif + </div> + <div class="row mb-3"> + <div class="form-group"> + <label class="user-select-none"> + <input type="checkbox" name="data[{{$k}}][value]" value="1" + @if (old("data.$k.value") ?? ($input['value'] ?? 0)) checked @endif> + {{ $input['label'] }}* + @include('back.modules.input-error', [ + 'inputName' => 'data[{{$k}}][value]', + 'helperId' => '{{$k}}BooleanHelp', + 'helper' => $input['helper'], + ]) + </label> + </div> + </div> + </fieldset> + </div> </div> @break @case(App\Enums\InputType::image()->value) - <div class="row mb-3"> - <div class="col-12 col-md-6 form-group"> - <label for="{{$k}}alt" class="col-form-label"> - <b>{{ __('Texte alternatif de l\'image') }}</b> - </label> - <div class="word-counter" data-json='@json(['id' => $k.'alt'])'></div> - <input name="data[{{$k}}][alt]" type="text" - value="{{ old("data.$k.alt") ?? $input['alt'] ?? '' }}" - class="form-control" id="{{$k}}alt" - aria-describedby="{{$k}}altHelp" - placeholder="{{ __('Texte alternatif à l\'image') }}" required> - @include('back.modules.input-error', [ - 'inputName' => 'data[{{$k}}][alt]', - 'helperId' => '{{$k}}altHelp', - 'helper' => __( - 'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)' - ), - ]) - </div> - <div class="col-12 col-md-6 form-group"> - <label for="{{$k}}title" class="col-form-label"> - <b>{{ __('Titre de l\'image') }}</b> - </label> - <div class="word-counter" data-json='@json(['id' => $k.'title'])'></div> - <input name="data[{{$k}}][title]" type="text" - value="{{ old("data.$k.title") ?? $input['title'] ?? '' }}" - class="form-control" id="{{$k}}title" - aria-describedby="{{$k}}titleHelp" - placeholder="{{ __('Titre de l\'image') }}" required> - @include('back.modules.input-error', [ - 'inputName' => 'data[{{$k}}][title]', - 'helperId' => '{{$k}}titleHelp', - 'helper' => __( - 'Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image' - ), - ]) - </div> - </div> <div class="row"> - <div class="col-12"> - @php - $data = [ - 'id' => $k . 'picture', - 'name' => 'data[' . $k . '][value]', - 'helper' => __('L\'image qui va servir de vignette à l\'article, doit être de ') . $input['dimensions'], - 'value' => $input['value'] ?? '', - ]; - @endphp - @include('back.modules.input-error', ['inputName' => 'data[' . $k . '][value]']) - <div class="image-input" data-json='@json($data)'></div> + <div class="col"> + <fieldset class="border p-3 mb-3"> + <div class="d-flex flex-row justify-content-start align-items-center mb-2"> + <legend class="w-auto m-0">{{ $input['label'] }}</legend> + @if(!empty($input['tooltip'])) + <span class="ms-1" data-bs-toggle="tooltip" data-bs-html="true" title="{{ $input['tooltip'] }}"> + <i class="fa fa-question-circle"></i> + </span> + @endif + </div> + <div class="row mb-3"> + <div class="col-12 col-md-6 form-group"> + <label for="{{$k}}alt" class="col-form-label"> + <b>{{ __('Texte alternatif de l\'image') }}</b> + </label> + <div class="word-counter" data-json='@json(['id' => $k.'alt'])'></div> + <input name="data[{{$k}}][alt]" type="text" + value="{{ old("data.$k.alt") ?? $input['alt'] ?? '' }}" + class="form-control" id="{{$k}}alt" + aria-describedby="{{$k}}altHelp" + placeholder="{{ __('Texte alternatif à l\'image') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'data[{{$k}}][alt]', + 'helperId' => '{{$k}}altHelp', + 'helper' => __( + 'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)' + ), + ]) + </div> + <div class="col-12 col-md-6 form-group"> + <label for="{{$k}}title" class="col-form-label"> + <b>{{ __('Titre de l\'image') }}</b> + </label> + <div class="word-counter" data-json='@json(['id' => $k.'title'])'></div> + <input name="data[{{$k}}][title]" type="text" + value="{{ old("data.$k.title") ?? $input['title'] ?? '' }}" + class="form-control" id="{{$k}}title" + aria-describedby="{{$k}}titleHelp" + placeholder="{{ __('Titre de l\'image') }}" required> + @include('back.modules.input-error', [ + 'inputName' => 'data[{{$k}}][title]', + 'helperId' => '{{$k}}titleHelp', + 'helper' => __( + 'Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image' + ), + ]) + </div> + </div> + <div class="row"> + <div class="col-12"> + @php + $data = [ + 'id' => $k . 'picture', + 'name' => 'data[' . $k . '][value]', + 'helper' => __("L'image qui va servir de vignette à l'article, doit être de " . + $input['dimensions']['largeur'] . "px (largeur) et " . + $input['dimensions']['hauteur'] . "px (hauteur)."), + 'value' => $input['value'] ?? '', + 'width' => $input['dimensions']['largeur'], + 'height' => $input['dimensions']['hauteur'] + ]; + @endphp + <div class="image-input" data-json='@json($data)'></div> + @include('back.modules.input-error', ['inputName' => 'data[' . $k . '][value]']) + </div> + </div> + </fieldset> </div> </div> @break diff --git a/resources/views/back/news/edit.blade.php b/resources/views/back/news/edit.blade.php index 4ea1717..385df0a 100644 --- a/resources/views/back/news/edit.blade.php +++ b/resources/views/back/news/edit.blade.php @@ -51,7 +51,7 @@ @csrf @method('PUT') @include('back.news.form', compact('newsModel')) - <button type="submit" class="btn btn-primary" title="{{ __('Enregister :model en base', [ + <button type="submit" class="btn btn-primary" title="{{ __('Enregister :model en base', [ 'model' => Str::singular(__('models.classes.news')) ]) }}" data-bs-toggle="tooltip">{{ __('Enregister') }}</button> </form> diff --git a/resources/views/back/products/form.blade.php b/resources/views/back/products/form.blade.php index 93da89e..e4dab41 100644 --- a/resources/views/back/products/form.blade.php +++ b/resources/views/back/products/form.blade.php @@ -282,11 +282,6 @@ <legend>{{ __('Description du produit') }}</legend> <div class="row mb-3"> <div class="col-12 form-group"> - @include('back.modules.input-error', [ - 'inputName' => 'description', - 'helperId' => 'productModelDescriptionHelp', - 'helper' => __('Relisez bien votre description avant de la publier.'), - ]) @php $data = [ 'name' => 'description', @@ -294,6 +289,11 @@ ]; @endphp <div class="ck-editor-bo" data-json='@json($data)'></div> + @include('back.modules.input-error', [ + 'inputName' => 'description', + 'helperId' => 'productModelDescriptionHelp', + 'helper' => __('Relisez bien votre description avant de la publier.'), + ]) </div> </div> </fieldset> diff --git a/routes/web-bo.php b/routes/web-bo.php index 0b4540f..0a2b9c0 100644 --- a/routes/web-bo.php +++ b/routes/web-bo.php @@ -62,9 +62,12 @@ Route::prefix('bo')->name('bo.')->group(function () { Route::prefix('engagements') ->name('engagements.') ->group(function () { - Route::get('/{engagement:slug}/edit', [EngagementsController::class, 'edit'])->name('edit'); + Route::get('/{engagement:slug}/edit', [EngagementsController::class, 'edit']) + ->name('edit'); Route::match(['post', 'put'], '/{engagement}', [EngagementsController::class, 'update']) - ->name('update'); + ->name('update') + ->withoutMiddleware('htmlsanitize') + ->middleware('htmlsanitize:title,text'); }); }); });