diff --git a/app/Enums/Concepts/ColorsEnum.php b/app/Enums/Concepts/ColorsEnum.php index 00008698..0566a131 100644 --- a/app/Enums/Concepts/ColorsEnum.php +++ b/app/Enums/Concepts/ColorsEnum.php @@ -9,25 +9,39 @@ enum ColorsEnum : int { case lightgreen = 4; case brown = 5; + public function label(): string { + return static::getLabel($this); + } + public function color(): string { return static::getColor($this); } + public static function getLabel(self $value): string { + return match ($value) { + self::redwine => __('Redwine'), + self::orange => __('Orange'), + self::blue => __('Blue'), + self::lightgreen => __('Lightgreen'), + self::brown => __('Brown'), + }; + } public static function getColor(self $value): string { return match ($value) { - self::redwine => rgb(165 25 60), - self::orange => rgb(240 165 0), - self::blue => rgb(95 155 210), - self::lightgreen => rgb(180 190 0), - self::brown => rgb(95 40 45), + self::redwine => "rgb(165 25 60)", + self::orange => "rgb(240 165 0)", + self::blue => "rgb(95 155 210)", + self::lightgreen => "rgb(180 190 0)", + self::brown => "rgb(95 40 45)", }; } - public static function getColors(): array { + public static function getData(): array { $arrayEnum = []; foreach (self::cases() as $key => $value) { $arrayEnum = array_merge($arrayEnum, [[ "value" => $value->value, + "label" => $value->label(), "color" => $value->color() ]]); } diff --git a/app/Enums/InputType.php b/app/Enums/InputType.php new file mode 100644 index 00000000..3c2b833b --- /dev/null +++ b/app/Enums/InputType.php @@ -0,0 +1,18 @@ +search) { + $this->searchQuery( + $concepts, + $request->search, + null, + 'seo_title', + 'name', + 'title', + 'subtitle', + ); + } + + $this->sortQuery($concepts); + $searchFields = trans('Name'); + + $conceptModels = $concepts->paginate(10); + + return view('bo.pages.concepts.index', compact('conceptModels', 'searchFields')); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + */ + public function create() + { + $conceptData = Concept::getEmptyData(); + + return view('bo.pages.concepts.create', compact('conceptData')); + } + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Bo\Concepts\StoreConceptRequest $request + * @return \Illuminate\Http\Response + */ + public function store(StoreConceptRequest $request) + { + return DB::transaction(function () use ($request) { + $concept = new Concept(); + $concept->data = $concept->getEmptyData(); + $concept->fill(array_replace_recursive( + \json_decode(\json_encode($concept->toArray()), true), + $request->validated() + )); + + if ($concept->saveOrFail()) { + return redirect() + ->route('bo.concepts.edit', $concept) + ->with('success', trans( + ':model has been created', + ['model' => Str::singular(trans('models.classes.concepts'))] + )); + } + return redirect() + ->back() + ->with('error', trans( + ':model cannot be created', + ['model' => Str::singular(trans('models.classes.concepts'))] + )); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Concept $concept + * @return \Illuminate\Http\Response + */ + public function edit(Concept $concept) + { + return view('bo.pages.concepts.edit', [ + 'conceptModel' => $concept + ]); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Bo\Concepts\UpdateConceptRequest $request + * @param \App\Models\Concept $concept + * @return \Illuminate\Http\Response + */ + public function update(UpdateConceptRequest $request, Concept $concept) + { + return DB::transaction(function () use ($request, $concept) { + $concept->fill(array_replace_recursive( + \json_decode(\json_encode($concept->toArray()), true), + $request->validated() + )); + + if ($concept->saveOrFail()) { + return redirect() + ->route('bo.concepts.edit', $concept) + ->with('success', trans( + 'Modification(s) from :model saved', + ['model' => Str::singular(trans('models.classes.concepts'))] + )); + } + return redirect() + ->back() + ->with('error', trans( + 'Modification(s) from :model cannot be saved', + ['model' => Str::singular(trans('models.classes.concepts'))] + )); + }); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Concept $concept + * @return \Illuminate\Http\Response + */ + public function destroy(Concept $concept) + { + if ($concept->deleteOrFail()) { + return redirect() + ->route('bo.concepts.index') + ->with('success', trans( + ':model has been deleted', + ['model' => Str::singular(trans('models.classes.concepts'))] + )); + } + return redirect() + ->back() + ->with('error', trans( + ':model cannot be deleted', + ['model' => Str::singular(trans('models.classes.concepts'))] + )); + } +} diff --git a/app/Http/Controllers/Bo/HomeController.php b/app/Http/Controllers/Bo/HomeController.php index 677b61fa..9fef2903 100644 --- a/app/Http/Controllers/Bo/HomeController.php +++ b/app/Http/Controllers/Bo/HomeController.php @@ -44,11 +44,14 @@ class HomeController extends Controller return redirect() ->route('bo.homes.edit', $home) ->with('success', trans( - ':model a été mis à jour', + 'Modification(s) from :model saved', ['model' => Str::singular(trans('models.classes.homes'))] )); } //end if - return back(); + return back()->with('success', trans( + 'Modification(s) from :model cannot be saved', + ['model' => Str::singular(trans('models.classes.homes'))] + )); }); } } diff --git a/app/Http/Requests/Bo/Concepts/StoreConceptRequest.php b/app/Http/Requests/Bo/Concepts/StoreConceptRequest.php new file mode 100644 index 00000000..60ec946a --- /dev/null +++ b/app/Http/Requests/Bo/Concepts/StoreConceptRequest.php @@ -0,0 +1,87 @@ +check(); + } + + /** + * Prepare the data for validation. + * + * @return void + */ + protected function prepareForValidation() + { + $this->merge([ + 'slug' => Str::slug(strip_tags($this->name)) + ]); + $this->mergePicture('picture'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = [ + 'seo_title' => 'required|min:3|max:70', + 'seo_description' => 'required|min:50|max:160', + + 'name' => 'required|min:3|max:70', + 'title' => 'required|min:3|max:70', + 'subtitle' => 'required|min:3|max:70', + 'description' => 'required|string|min:15', + 'data' => 'present|array', + 'data.*' => 'required|array', + + 'picture_alt' => 'required|min:3|max:255', + 'picture_title' => 'required|min:3|max:255', + ]; + return \array_merge( + $rules, + $this->pictureRules('picture', true, 1512, 354, 1512, 354), + ); + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes() + { + return [ + 'seo_title' => trans('titre') . ' seo', + 'seo_description' => trans('description') . ' seo', + + 'name' => trans('name of the concept'), + 'slug' => trans('slug of the concept'), + 'title' => trans('title of the concept'), + 'subtitle' => trans('subtitle of the concept'), + 'description' => trans('description of the concept'), + 'data' => trans('values of the data'), + 'data.*.value' => trans('value of the data'), + + 'picture' => trans('illustration'), + 'picture_alt' => trans('alt attribute of the image'), + 'picture_title' => trans('title attribute of the image'), + ]; + } +} diff --git a/app/Http/Requests/Bo/Concepts/UpdateConceptRequest.php b/app/Http/Requests/Bo/Concepts/UpdateConceptRequest.php new file mode 100644 index 00000000..14e04c0c --- /dev/null +++ b/app/Http/Requests/Bo/Concepts/UpdateConceptRequest.php @@ -0,0 +1,9 @@ + 'required|min:50|max:160', 'part1_title' => 'required|min:3|max:70', - 'part1_title_span' => 'required|min:3|max:70', + 'part1_subtitle' => 'required|min:3|max:70', 'part1_text_1' => 'required|string|min:15', 'part1_text_2' => 'required|string|min:15', 'part1_text_3' => 'required|string|min:15', 'part2_title' => 'required|min:3|max:70', - 'part2_title_span' => 'required|min:3|max:70', + 'part2_subtitle' => 'required|min:3|max:70', 'part2_text' => 'required|string|min:15', 'part2_list' => 'present|array', 'part2_list.*' => 'required|array', @@ -76,13 +76,13 @@ class UpdateHomeRequest extends FormRequest 'seo_description' => trans('description') . ' seo', 'part1_title' => trans('title part.1'), - 'part1_title_span' => trans('subtitle part.1'), + 'part1_subtitle' => trans('subtitle part.1'), 'part1_text_1' => trans('text 1 part.1'), 'part1_text_2' => trans('text 2 part.1'), 'part1_text_3' => trans('text 3 part.1'), 'part2_title' => trans('title part.2'), - 'part2_title_span' => trans('subtitle part.2'), + 'part2_subtitle' => trans('subtitle part.2'), 'part2_text' => trans('text part.2'), 'part2_list' => trans('values of the list part.2'), 'part2_list.*.value' => trans('value of the list part.2'), diff --git a/app/Http/Requests/Bo/UpdateMiniCrudRequest.php b/app/Http/Requests/Bo/UpdateMiniCrudRequest.php new file mode 100644 index 00000000..be642de4 --- /dev/null +++ b/app/Http/Requests/Bo/UpdateMiniCrudRequest.php @@ -0,0 +1,156 @@ +check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh + * @phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + public function rules() + { + // phpcs:enable + $rules = []; + foreach ($this->{$this->modelName}->data as $iptName => $input) { + $input = \json_decode(\json_encode($input), true); + switch ($input['inputType']['value']) { + case InputType::number()->value: + $rules = array_merge($rules, [ + "data.$iptName.value" => $this->compileValidate('required|numeric', $input) + ]); + break; + case InputType::string()->value: + $rules = array_merge($rules, [ + "data.$iptName.value" => $this->compileValidate('required|string|min:3', $input) + ]); + break; + case InputType::text()->value: + $mergeRules = $this->compileValidate('required|string|min:15', $input); + $mergeRules = collect(\explode('|', $mergeRules)) + ->map(function ($rule) { + if (\strpos($rule, 'max:') !== false) { + $max = \intval(str_replace('max:', '', $rule)); + return function ($attribute, $value, $fail) use ($max) { + if (\strlen(\strip_tags($value)) > $max) { + $fail("$attribute ne doit pas excéder $max caractères"); + } + }; + } + return $rule; + }); + $rules = array_merge($rules, [ + "data.$iptName.text" => $mergeRules + ]); + break; + case InputType::image()->value: + $ipt = $this->data[$iptName]; + $max = isset($ipt['dimensionsMax']); + $dim = collect(explode( + ',', + $ipt['dimensionsMax'] ?? $input['dimensions'] + ))->mapWithKeys(function ($v) { + $v = explode(' ', $v); + return [$v[0] => $v[1]]; + }); + $rules = array_merge($rules, [ + "data.$iptName.value" => $this->compileValidate( + implode('|', [ + 'required', + '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.alt" => $this->compileValidate('required|string|min:5', $input), + "data.$iptName.title" => $this->compileValidate('required|string|min:5', $input) + ]); + break; + case InputType::url()->value: + $rules = array_merge($rules, [ + "data.$iptName.value" => [ + 'required', 'url', + // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + try { + ToolboxHelper::assertNoInternalUrls($attribute, $value); + ToolboxHelper::assertNoInternalBoUrls($attribute, $value); + } catch (ValidationException $e) { + $fail(collect($e->errors())->first()[0]); + } + } + ] + ]); + 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' + ]); + break; + } //end switch + } //end foreach + return $rules; + } + + /** + * Undocumented function + * + * @param string $inputTypeValidate + * @param array $input + * @return string + */ + public function compileValidate(string $inputTypeValidate, array $input): string + { + if (!isset($input['validate']) or !\is_string($input['validate'])) { + return $inputTypeValidate; + } + /** @var array */ + $inputValidate = explode('|', $input['validate']); + $out = explode('|', $inputTypeValidate); + foreach ($inputValidate as $validate) { + if (!\in_array($validate, $out)) { + $out[] = $validate; + } + } + return \implode('|', $out); + } +} diff --git a/app/Lib/Helpers/ToolboxHelper.php b/app/Lib/Helpers/ToolboxHelper.php index 25b545b6..3723dd33 100644 --- a/app/Lib/Helpers/ToolboxHelper.php +++ b/app/Lib/Helpers/ToolboxHelper.php @@ -234,4 +234,24 @@ class ToolboxHelper throw new \RuntimeException('cannot handle $input of type ' . \gettype($input)); } } + + + /** + * 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; + } } diff --git a/app/Models/Concept.php b/app/Models/Concept.php index 531f4a85..9c3c780a 100644 --- a/app/Models/Concept.php +++ b/app/Models/Concept.php @@ -2,10 +2,15 @@ namespace App\Models; +use App\Enums\Concepts\ColorsEnum; +use App\Enums\InputType; use App\Lib\Helpers\FileStorageHelper; use App\Lib\Helpers\ToolboxHelper; +use App\Traits\Models\MiniCrudModel; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; +use Illuminate\Validation\Rules\Enum; /** * @property integer $id @@ -14,16 +19,19 @@ use Illuminate\Database\Eloquent\Model; * @property string $name * @property string $slug * @property string $title - * @property string $title_span + * @property string $subtitle * @property string $description * @property string $content * @property string $picture + * @property string $picture_alt + * @property string $picture_title * @property string $created_at * @property string $updated_at */ class Concept extends Model { use HasFactory; + use MiniCrudModel; /** * @var array @@ -34,14 +42,26 @@ class Concept extends Model 'name', 'slug', 'title', - 'title_span', + 'subtitle', 'description', - 'content', + 'data', 'picture', + 'picture_alt', + 'picture_title', + 'order', 'created_at', 'updated_at' ]; + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'data' => 'array', + ]; + // * EVENTS /** @@ -52,9 +72,13 @@ class Concept extends Model protected static function booted() { static::creating(function (self $model) { + self::setSlug($model); + self::assertFieldIsUnique('name', $model->name); self::manageFields($model); + self::setOrder($model); }); static::updating(function (self $model) { + self::setSlug($model); self::manageFields($model); }); static::updated(function (self $model) { @@ -68,17 +92,41 @@ class Concept extends Model // * METHODS /** - * Undocumented function + * Set the slug. + * + * @param \Illuminate\Database\Eloquent\Model $model + * + * @return void + */ + private static function setSlug(Model $model) + { + $model->slug = Str::slug($model->name); + } + + /** + * Common methods function. * * @param Model $model * @return void */ private static function manageFields(Model $model) { - self::assertFieldIsUnique('name', $model->name); + ToolboxHelper::assertNoInternalBoUrls('description', $model->description); + $model->description = clean($model->description); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); } + /** + * Set order after the last element of the list. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + private static function setOrder(Model $model): void + { + $model->order = \intval(self::query()->max('order')) + 1; + } + /** * Asserts using validation that the field is unique. * @@ -92,4 +140,130 @@ class Concept extends Model $table = (new self())->getTable(); ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id); } + + /** + * Return the structure necessary of data. + * + * @param Model $model + * @return void + */ + public static function getEmptyData( + string $title = null, + int $color = null, + string $text = null, + string $img = null, + string $imgAlt = null, + string $imgTitle = null + ) { + return [ + // * BLOC 1 + "bloc_1_title" => [ + "label" => "Bloc 1 - Title", + "inputType" => InputType::string, + "value" => $title ?? "", + "validate" => "required|min:4|max:100" + ], + "bloc_1_color" => [ + "label" => "Bloc 1 - Color", + "inputType" => InputType::selectEnum, + "value" => $color ?? "", + "validate" => ["required", "integer", new Enum(ColorsEnum::class)] + ], + "bloc_1_text" => [ + "label" => "Bloc 1 - Text", + "inputType" => InputType::text, + "value" => $text ?? "", + "validate" => "string|min:10" + ], + "bloc_1_image" => [ + "label" => "Bloc 1 - Image", + "dimensions" => ["width" => 1140, "height" => 795], + "inputType" => InputType::image, + "alt" => $imgAlt ?? "", + "title" => $imgTitle ?? "", + "value" => $img ?? "", + ], + // * BLOC 2 + "bloc_2_title" => [ + "label" => "Bloc 2 - Title", + "inputType" => InputType::string, + "value" => $title ?? "", + "validate" => "required|min:4|max:100" + ], + "bloc_2_color" => [ + "label" => "Bloc 2 - Color", + "inputType" => InputType::selectEnum, + "value" => $color ?? "", + "validate" => ["required", "integer", new Enum(ColorsEnum::class)] + ], + "bloc_2_text" => [ + "label" => "Bloc 2 - Text", + "inputType" => InputType::text, + "value" => $text ?? "", + "validate" => "string|min:10" + ], + "bloc_2_image" => [ + "label" => "Bloc 2 - Image", + "dimensions" => ["width" => 1140, "height" => 795], + "inputType" => InputType::image, + "alt" => $imgAlt ?? "", + "title" => $imgTitle ?? "", + "value" => $img ?? "", + ], + // * BLOC 3 + "bloc_3_title" => [ + "label" => "Bloc 3 - Title", + "inputType" => InputType::string, + "value" => $title ?? "", + "validate" => "required|min:4|max:100" + ], + "bloc_3_color" => [ + "label" => "Bloc 3 - Color", + "inputType" => InputType::selectEnum, + "value" => $color ?? "", + "validate" => ["required", "integer", new Enum(ColorsEnum::class)] + ], + "bloc_3_text" => [ + "label" => "Bloc 3 - Text", + "inputType" => InputType::text, + "value" => $text ?? "", + "validate" => "string|min:10" + ], + "bloc_3_image" => [ + "label" => "Bloc 3 - Image", + "dimensions" => ["width" => 1140, "height" => 795], + "inputType" => InputType::image, + "alt" => $imgAlt ?? "", + "title" => $imgTitle ?? "", + "value" => $img ?? "", + ], + // * BLOC 4 + "bloc_4_title" => [ + "label" => "Bloc 4 - Title", + "inputType" => InputType::string, + "value" => $title ?? "", + "validate" => "required|min:4|max:100" + ], + "bloc_4_color" => [ + "label" => "Bloc 4 - Color", + "inputType" => InputType::selectEnum, + "value" => $color ?? "", + "validate" => ["required", "integer", new Enum(ColorsEnum::class)] + ], + "bloc_4_text" => [ + "label" => "Bloc 4 - Text", + "inputType" => InputType::text, + "value" => $text ?? "", + "validate" => "string|min:10" + ], + "bloc_4_image" => [ + "label" => "Bloc 4 - Image", + "dimensions" => ["width" => 1140, "height" => 795], + "inputType" => InputType::image, + "alt" => $imgAlt ?? "", + "title" => $imgTitle ?? "", + "value" => $img ?? "", + ], + ]; + } } diff --git a/app/Models/Home.php b/app/Models/Home.php index 6883ad83..45963ad5 100644 --- a/app/Models/Home.php +++ b/app/Models/Home.php @@ -14,12 +14,12 @@ use Illuminate\Database\Eloquent\Model; * @property string $seo_title * @property string $seo_description * @property string $part1_title - * @property string $part1_title_span + * @property string $part1_subtitle * @property string $part1_text_1 * @property string $part1_text_2 * @property string $part1_text_3 * @property string $part2_title - * @property string $part2_title_span + * @property string $part2_subtitle * @property string $part2_text * @property string $part2_list * @property string $part3_picture @@ -41,12 +41,12 @@ class Home extends Model 'seo_title', 'seo_description', 'part1_title', - 'part1_title_span', + 'part1_subtitle', 'part1_text_1', 'part1_text_2', 'part1_text_3', 'part2_title', - 'part2_title_span', + 'part2_subtitle', 'part2_text', 'part2_list', 'part3_picture', diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 1f09e245..80a31708 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Models\Concept; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Http\Request; @@ -39,6 +40,8 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace) ->group(base_path('routes/web-fo.php')); }); + + Route::model('concept', Concept::class); } /** diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index ca411fdd..dbd29caf 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -6,6 +6,7 @@ use App\Models\Home; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; @@ -22,9 +23,9 @@ class ViewServiceProvider extends ServiceProvider // * FORCE BOOTSTRAP PAGINATOR (or custom if in front) // Early boot, request wont be filled as expected. if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') { - Paginator::useBootstrap(); + Paginator::defaultview('bo.modules.pagination'); } else { - Paginator::defaultview('fo.partials.pagination'); + Paginator::defaultview('fo.modules.pagination'); } } @@ -32,10 +33,14 @@ class ViewServiceProvider extends ServiceProvider return json_decode(File::get(\app_path('../composer.json'))); }); // Share composer.json infos. - View::share('cpsr_name', $appInfos->name); - View::share('cpsr_version', $appInfos->version); - View::share('cpsr_license', $appInfos->license); - View::share('cpsr_time', now()->parse($appInfos->time)->isoFormat('Y')); - View::share('globalHome', Home::firstOrFail()); + if (isset($appInfos->name) and isset($appInfos->version) and isset($appInfos->license) and isset($appInfos->time)) { + View::share('cpsr_name', $appInfos->name); + View::share('cpsr_version', $appInfos->version); + View::share('cpsr_license', $appInfos->license); + View::share('cpsr_time', now()->parse($appInfos->time)->isoFormat('Y')); + } + if (Schema::hasTable('homes')) { + View::share('globalHome', Home::first()); + } } } diff --git a/app/Traits/Models/MiniCrudModel.php b/app/Traits/Models/MiniCrudModel.php new file mode 100644 index 00000000..cabd0976 --- /dev/null +++ b/app/Traits/Models/MiniCrudModel.php @@ -0,0 +1,94 @@ +setData($attributes['data']); + } + } + + /** + * Set a specific prop + * + * @param array $data + * @return void + */ + private function setData(array $data): void + { + $attrs = $this->attributes; + if (isset($attrs['data'])) { + unset($attrs['data']); + } + $data = $this->serializeData($data); + $this->setRawAttributes(\array_merge_recursive( + $attrs, + ['data' => $data] + )); + } + + /** + * Get data attribute + * Cast json_decode to object. + * + * @return object|null + */ + protected function getDataAttribute(): ?object + { + return \json_decode($this->getAttributes()['data'] ?? json_encode($this->getEmptyData())) ?? '[]'; + } + + /** + * Get data attribute + * Cast json_decode to object. + * + * @param array $data + * @return void + */ + protected function setDataAttribute(array $data): void + { + $this->setData($data); + } + + /** + * Serialize properties + * + * @param array $data + * @return string + * @throws \ErrorException If input type is no handled. + * @throws \Illuminate\Validation\ValidationException If url is a bo url. + * @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber + * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh + */ + private function serializeData(array $data): string + { + foreach ($data as $o => $input) { + switch ($input['inputType'] ?? $input['inputType']) { + case InputType::image->value: + $data[$o]['value'] = FileStorageHelper::storeFile($this, $input['value'], true); + break; + case InputType::boolean->value: + case InputType::text->value: + case InputType::number->value: + case InputType::string->value: + case InputType::videoVimeo->value: + case InputType::url->value: + } + } //end foreach + return json_encode($data); + } +} diff --git a/app/Traits/Requests/.gitkeep b/app/Traits/Requests/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/composer.json b/composer.json index 522ea1ff..22a5ce9e 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "Wenn dann mit respekt", + "name": "ldcweb/wenn-dann-mit-respekt", "version": "1.0.0", "type": "project", "time": "2023-03-16", @@ -52,6 +52,14 @@ } }, "scripts": { + "refresh": [ + "@php artisan optimize:clear", + "@php artisan clear-compiled", + "@php artisan view:clear", + "@php artisan route:clear", + "@php artisan config:clear", + "@php artisan cache:clear" + ], "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" diff --git a/composer.lock b/composer.lock index 389c48fc..c02cbe5e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eee884d4ea0363ba9b1f0f62410bb38f", + "content-hash": "0bc4ba3bc4ba2da26748340ed29c18bc", "packages": [ { "name": "brick/math", diff --git a/config/breadcrumbs.php b/config/breadcrumbs.php index 0d7aa2ab..6c96342a 100644 --- a/config/breadcrumbs.php +++ b/config/breadcrumbs.php @@ -23,7 +23,7 @@ return [ | */ - 'view' => 'breadcrumbs/breadrcumb-layout', + 'view' => 'modules/breadcrumbs/breadcrumb-layout', /* |-------------------------------------------------------------------------- diff --git a/database/factories/ConceptFactory.php b/database/factories/ConceptFactory.php index e4af491e..8da6f1a1 100644 --- a/database/factories/ConceptFactory.php +++ b/database/factories/ConceptFactory.php @@ -26,15 +26,17 @@ final class ConceptFactory extends Factory public function definition(): array { $name = fake()->unique()->firstname; + return [ 'seo_title' => fake()->word, 'seo_description' => fake()->text, 'name' => $name, 'slug' => Str::slug($name), 'title' => fake()->word, - 'title_span' => fake()->word, + 'subtitle' => fake()->word, 'description' => fake()->text, - 'content' => fake()->text, + 'picture_alt' => fake()->word, + 'picture_title' => fake()->word, ]; } } diff --git a/database/factories/HomeFactory.php b/database/factories/HomeFactory.php index 91c59729..3a9c504b 100644 --- a/database/factories/HomeFactory.php +++ b/database/factories/HomeFactory.php @@ -36,12 +36,12 @@ final class HomeFactory extends Factory 'seo_title' => fake()->word, 'seo_description' => fake()->text, 'part1_title' => fake()->word, - 'part1_title_span' => fake()->word, + 'part1_subtitle' => fake()->word, 'part1_text_1' => fake()->text, 'part1_text_2' => fake()->text, 'part1_text_3' => fake()->text, 'part2_title' => fake()->word, - 'part2_title_span' => fake()->word, + 'part2_subtitle' => fake()->word, 'part2_text' => fake()->text, 'part2_list' => $part2_list, 'part2_product1_id' => fake()->randomElement(Product::pluck('id')), diff --git a/database/factories/assets/concepts/default-picture-1140x795.png b/database/factories/assets/concepts/default-picture-1140x795.png new file mode 100644 index 00000000..0488710d Binary files /dev/null and b/database/factories/assets/concepts/default-picture-1140x795.png differ diff --git a/database/factories/assets/concepts/default-picture-1512x354.png b/database/factories/assets/concepts/default-picture-1512x354.png new file mode 100644 index 00000000..0da4479d Binary files /dev/null and b/database/factories/assets/concepts/default-picture-1512x354.png differ diff --git a/database/factories/assets/concepts/default-picture-1512x3540.png b/database/factories/assets/concepts/default-picture-1512x3540.png deleted file mode 100644 index 1f6b3b52..00000000 Binary files a/database/factories/assets/concepts/default-picture-1512x3540.png and /dev/null differ diff --git a/database/migrations/2023_03_15_104522_create_concepts_table.php b/database/migrations/2023_03_15_104522_create_concepts_table.php index 8f01f03a..121c3527 100644 --- a/database/migrations/2023_03_15_104522_create_concepts_table.php +++ b/database/migrations/2023_03_15_104522_create_concepts_table.php @@ -20,10 +20,13 @@ return new class extends Migration $table->string('name')->comment('Nom du concept'); $table->string('slug')->unique()->comment('Slug du nom de la page'); $table->string('title')->comment('Titre de la page'); - $table->string('title_span')->comment('Seconde partie du titre de la page'); - $table->string('description')->comment('Description du concept'); - $table->mediumText('content')->comment('Tableau d\'objets'); + $table->string('subtitle')->comment('Seconde partie du titre de la page'); + $table->text('description')->comment('Description du concept'); + $table->json('data')->comment('Tableau d\'objets'); $table->string('picture')->comment('Path de l\'image, 1512px x 354px'); + $table->string('picture_alt')->comment('Attribut alt de l\'image'); + $table->string('picture_title')->comment('Titre de l\'image'); + $table->integer('order')->comment('Pour ordonner les concepts'); $table->timestamps(); }); } diff --git a/database/migrations/2023_03_15_104522_create_homes_table.php b/database/migrations/2023_03_15_104522_create_homes_table.php index d2807709..855ec3eb 100644 --- a/database/migrations/2023_03_15_104522_create_homes_table.php +++ b/database/migrations/2023_03_15_104522_create_homes_table.php @@ -18,12 +18,12 @@ return new class extends Migration $table->string('seo_title')->comment('Utilisé pour la balise title de la page'); $table->string('seo_description')->comment('Utilisé pour la balise méta description de la page'); $table->string('part1_title')->comment('Titre de la partie 1'); - $table->string('part1_title_span')->comment('Seconde partie du titre de la partie 1'); + $table->string('part1_subtitle')->comment('Seconde partie du titre de la partie 1'); $table->string('part1_text_1')->comment('Premier paragraphe de la partie 1'); $table->string('part1_text_2')->comment('Second paragraphe de la partie 1'); $table->string('part1_text_3')->comment('Troisième paragraphe de la partie 1'); $table->string('part2_title')->comment('Titre de la partie 2'); - $table->string('part2_title_span')->comment('Seconde partie du titre de la partie 2'); + $table->string('part2_subtitle')->comment('Seconde partie du titre de la partie 2'); $table->string('part2_text')->comment('Paragraphe de la partie 2'); $table->json('part2_list')->comment('Tableau JSON de string'); $table->unsignedBigInteger('part2_product1_id')->comment('Produit lié 1, FK'); diff --git a/database/seeders/ConceptSeeder.php b/database/seeders/ConceptSeeder.php index 9fd73cf9..67dc97d8 100644 --- a/database/seeders/ConceptSeeder.php +++ b/database/seeders/ConceptSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Enums\Concepts\ColorsEnum; use App\Lib\Helpers\FileStorageHelper; use App\Models\Concept; use Illuminate\Database\Seeder; @@ -18,8 +19,20 @@ class ConceptSeeder extends Seeder $jj = 1; Concept::factory(20)->make()->each(function (Concept $concept) use (&$jj) { $concept->picture = FileStorageHelper::storeFile($concept, new \SplFileInfo(\resource_path( - '../database/factories/assets/concepts/default-picture-1512x3540.png' + '../database/factories/assets/concepts/default-picture-1512x354.png' ))); + $dataImg = FileStorageHelper::storeFile($concept, new \SplFileInfo(\resource_path( + '../database/factories/assets/concepts/default-picture-1140x795.png' + ))); + $concept->data = $concept->getEmptyData( + fake()->words(2, true), + \collect(ColorsEnum::cases())->random()->value, + fake()->text(), + $dataImg, + fake()->words(4, true), + fake()->words(4, true) + ); + $concept->order = $jj; $concept->saveOrFail(); $jj = $jj + 1; }); diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php index 231fb874..e41b8847 100644 --- a/resources/lang/en/models.php +++ b/resources/lang/en/models.php @@ -4,5 +4,6 @@ return [ 'classes' => [ 'homes' => 'Homepage', 'products' => 'Products', + 'concepts' => 'Concepts', ] ]; diff --git a/resources/sass/bo/base/_common.scss b/resources/sass/bo/base/_common.scss index 8d8c66fb..0fbba2a8 100644 --- a/resources/sass/bo/base/_common.scss +++ b/resources/sass/bo/base/_common.scss @@ -1,4 +1,6 @@ .need-word-counter { + position: relative; + z-index: 1; border-radius: 0 0 0.375rem 0.375rem !important; } diff --git a/resources/sass/bo/vendor/_bootstrap.scss b/resources/sass/bo/vendor/_bootstrap.scss index ed7fcee1..9742e03e 100644 --- a/resources/sass/bo/vendor/_bootstrap.scss +++ b/resources/sass/bo/vendor/_bootstrap.scss @@ -37,6 +37,8 @@ @import "~/bootstrap/scss/close"; @import "~/bootstrap/scss/card"; @import "~/bootstrap/scss/offcanvas"; +@import "~/bootstrap/scss/badge"; +@import "~/bootstrap/scss/breadcrumb"; // 6. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` @import "~/bootstrap/scss/utilities/api"; diff --git a/resources/ts/bo/components/ImageInputComponent.vue b/resources/ts/bo/components/ImageInputComponent.vue index 1876e1e9..5fed50bd 100644 --- a/resources/ts/bo/components/ImageInputComponent.vue +++ b/resources/ts/bo/components/ImageInputComponent.vue @@ -482,7 +482,7 @@ export default defineComponent({ return { intId: "", intValue: "", - intValueFileName: this.__("Sélectionnez une image"), + intValueFileName: this.__("Choose a file"), intHelper: "", intName: "", intRequired: true, @@ -703,7 +703,7 @@ export default defineComponent({ removeFile() { const actualImage = this.$refs.actualImage as HTMLInputElement; actualImage.value = ""; - this.intValueFileName = this.__("Sélectionnez une image"); + this.intValueFileName = this.__("Choose a file"); this.intValue = ""; }, /** diff --git a/resources/ts/bo/components/WordCounterComponent.vue b/resources/ts/bo/components/WordCounterComponent.vue index 1c88c155..c8cbe872 100644 --- a/resources/ts/bo/components/WordCounterComponent.vue +++ b/resources/ts/bo/components/WordCounterComponent.vue @@ -3,13 +3,11 @@ class="word-counter-varchar position-relative d-flex flex-row justify-content-around align-items-center w-100" > - {{ __("Words") }} - :  + {{ __("Words") }}: {{ intWords }} - {{ __("Characters") }} - :  + {{ __("Characters") }}: {{ intChars }} diff --git a/resources/ts/modules/sweetalert.ts b/resources/ts/modules/sweetalert.ts index 852b0829..0e68b9e6 100644 --- a/resources/ts/modules/sweetalert.ts +++ b/resources/ts/modules/sweetalert.ts @@ -13,8 +13,8 @@ window.addEventListener("DOMContentLoaded", () => { ); } confirm.methods.confirm( - "Confirmez", - "Voulez-vous vraiment supprimer cet élément ?", + "Confirm", + "Do you really want to remove this element ?", el as HTMLFormElement, function () { (el as HTMLFormElement).submit(); @@ -35,8 +35,8 @@ window.addEventListener("DOMContentLoaded", () => { ); } confirm.methods.confirm( - "Confirmez", - "Voulez-vous vraiment marquer cet élément comme traité ?", + "Confirm", + "Do you really want to mark this item as treated ?", el as HTMLFormElement, function () { (el as HTMLFormElement).submit(); @@ -57,8 +57,8 @@ window.addEventListener("DOMContentLoaded", () => { ); } confirm.methods.confirm( - "Confirmez", - "Voulez-vous enregistrer cet élément tel-quel ?", + "Confirm", + "Do you want to save this element as it is ?", el as HTMLFormElement, function () { (el as HTMLFormElement).submit(); @@ -85,8 +85,8 @@ window.addEventListener("DOMContentLoaded", () => { const promise = new Promise( (resolve: (value: boolean) => void) => { confirm.methods.confirm( - "Confirmez", - "Êtes vous sure ?", + "Confirm", + "Are you sure ?", el, function (response: SweetAlertResult) { resolve(response.isConfirmed); diff --git a/resources/ts/modules/sweetalertConfirm.ts b/resources/ts/modules/sweetalertConfirm.ts index 124421c1..1664017c 100644 --- a/resources/ts/modules/sweetalertConfirm.ts +++ b/resources/ts/modules/sweetalertConfirm.ts @@ -27,10 +27,10 @@ export default { showCancelButton: true, confirmButtonText: options.confirmButtonText ?? - trans.default.methods.__("Oui"), + trans.default.methods.__("Yes"), cancelButtonText: options.cancelButtonText ?? - trans.default.methods.__("Annuler"), + trans.default.methods.__("Cancel"), reverseButtons: true, // * Bootstrap Styling customClass: { @@ -49,7 +49,7 @@ export default { ) { Swal.fire({ title: trans.default.methods.__( - "L'opération n'as pas été effectuée" + "The operation has not been performed" ), confirmButtonText: trans.default.methods.__("Ok"), // * Bootstrap Styling diff --git a/resources/views/bo/auth/login.blade.php b/resources/views/bo/auth/login.blade.php index 00cbce9e..260d1c06 100644 --- a/resources/views/bo/auth/login.blade.php +++ b/resources/views/bo/auth/login.blade.php @@ -1,6 +1,7 @@ @extends('bo.layout') @section('title', __('Authentification')) @section('description', __('Formulaire d\'authentification au back-office.')) +@section('breadcrumb', 'no') @section('content')
diff --git a/resources/views/bo/layout.blade.php b/resources/views/bo/layout.blade.php index 7489db27..4c8250ab 100644 --- a/resources/views/bo/layout.blade.php +++ b/resources/views/bo/layout.blade.php @@ -12,15 +12,8 @@ @else @php $authLayout = 'col px-md-4'; @endphp @endauth - @auth('backend') -
-
- @include('modules.breadcrumb.breadcrumb-layout') -
-
- @endauth
-
+
@@ -32,6 +25,9 @@
+ @auth('backend') + @include('modules.breadcrumbs.breadcrumb-body') + @endauth @yield('content')
diff --git a/resources/views/bo/layouts/head.blade.php b/resources/views/bo/layouts/head.blade.php index e0148089..26f6b7a6 100644 --- a/resources/views/bo/layouts/head.blade.php +++ b/resources/views/bo/layouts/head.blade.php @@ -31,7 +31,7 @@ -@include('modules.breadcrumb.breadcrumb-head') +@include('modules.breadcrumbs.breadcrumb-head') @vite(['resources/sass/bo/bo.scss']) diff --git a/resources/views/bo/layouts/header.blade.php b/resources/views/bo/layouts/header.blade.php index d34f7f13..46374b52 100644 --- a/resources/views/bo/layouts/header.blade.php +++ b/resources/views/bo/layouts/header.blade.php @@ -16,7 +16,7 @@
{{ __('Users menu') }}
-
+