Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Jérémy Munsch
b1d8f5466f
Migration scripts 2023-05-30 22:05:42 +02:00
Jérémy Munsch
b44e61de35
Add migration mandatory changes 2023-05-30 20:26:00 +02:00
Jérémy Munsch
1ed68ddffa
Add marinade field to product 2023-05-30 17:04:24 +02:00
Jérémy Munsch
f2ae5b39f9
Certification remove les autres haltungs form keep ecc et haltung 3 2023-05-30 15:03:27 +02:00
Jérémy Munsch
663a43d631
upgrade base enum 2023-05-30 14:47:30 +02:00
32 changed files with 1091 additions and 32 deletions

View file

@ -8,21 +8,15 @@ enum CertificationsEnum : int
{
use BaseEnum;
case haltungsform1 = 1;
case haltungsform2 = 2;
case haltungsform3 = 3;
case haltungsform4 = 4;
case ecc = 5;
/**
* Custom added images definition
*/
private const IMAGES = [
self::haltungsform1->name => "haltungsform1.png",
self::haltungsform2->name => "haltungsform2.png",
self::haltungsform3->name => "haltungsform3.png",
self::haltungsform4->name => "haltungsform4.png",
self::ecc->name => "ecc.png",
self::ecc->name => "ECC-Logo-green.svg",
];
/**

View file

@ -0,0 +1,15 @@
<?php
namespace App\Exceptions;
use Exception;
/**
* This class Exception is used to skip a row import from
* database migration (2016 -> 2020)
*
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
class AbortRowImportException extends Exception
{
}

View file

@ -50,6 +50,7 @@ class StoreProductRequest extends FormRequest
'label' => 'required|min:3|max:255',
'slug' => 'unique_translation:products',
'description' => 'required|string|min:15',
'marinade' => 'nullable|string|min:15',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'certifications' => 'present|array',
@ -97,6 +98,7 @@ class StoreProductRequest extends FormRequest
'label' => trans('label'),
'slug' => trans('slug'),
'description' => trans('description'),
'marinade' => trans('marinade'),
'picture_alt' => trans('picture_alt'),
'picture_title' => trans('picture_title'),
'certifications' => trans('certifications'),

View file

@ -19,6 +19,7 @@ class UpdateProductRequest extends StoreProductRequest
'label' => 'required|min:3|max:255',
'slug' => UniqueTranslationRule::for('products')->ignore($this->product->id),
'description' => 'required|string|min:15',
'marinade' => 'nullable|string|min:15',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'certifications' => 'present|array',

View file

@ -138,8 +138,7 @@ class Concept extends Model
{
self::setSlug($model);
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
$model->description = clean($model->description);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**

View file

@ -127,11 +127,6 @@ class Home extends Model
ToolboxHelper::assertNoInternalBoUrls('part2_list', $item['value']);
}
}
$model->part1_text_1 = clean($model->part1_text_1);
$model->part1_text_2 = clean($model->part1_text_2);
$model->part1_text_3 = clean($model->part1_text_3);
$model->part2_text = clean($model->part2_text);
$model->part2_list = clean($model->part2_list);
$model->part3_picture = FileStorageHelper::storeFile($model, $model->part3_picture, true);
}

View file

@ -16,6 +16,7 @@ use Illuminate\Support\Str;
* @property string $label
* @property string $slug
* @property string $description
* @property string|null $marinade
* @property string $picture
* @property array $certifications
* @property array $nutritionnal_values
@ -40,6 +41,7 @@ class Product extends Model
'label',
'slug',
'description',
'marinade',
'picture',
'picture_alt',
'picture_title',
@ -74,6 +76,7 @@ class Product extends Model
'label',
'slug',
'description',
'marinade',
'picture',
'picture_alt',
'picture_title',
@ -130,9 +133,7 @@ class Product extends Model
ToolboxHelper::assertNoInternalBoUrls('nutritionnal_values', $itemNutritionnalValues['value'] ?? '');
}
}
$model->nutritionnal_values = clean($model->nutritionnal_values);
$model->list = clean($model->list);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**

View file

@ -144,9 +144,6 @@ class Recipe extends Model
}
ToolboxHelper::assertNoInternalBoUrls('steps', $model->steps);
ToolboxHelper::assertNoInternalBoUrls('tips', $model->tips);
$model->ingredients = clean($model->ingredients);
$model->steps = clean($model->steps);
$model->tips = clean($model->tips);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
$model->picture_thumbnail = FileStorageHelper::storeFile($model, $model->picture_thumbnail, true);
}

View file

@ -39,6 +39,16 @@ class Store extends Model
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'latitude' => 'float',
'longitude' => 'float'
];
// * EVENTS
/**

View file

@ -2,7 +2,6 @@
namespace App\Traits\Enums;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Str;
/**
@ -28,9 +27,17 @@ trait BaseEnum
*/
public function label(): string
{
$class = Str::snake(\class_basename(\get_class($this)));
return Lang::has("enums.{$class}.{$this->name}") ?
Lang::get("enums.{$class}.{$this->name}") : self::toLabels()[$this->name];
return self::toLabels()[$this->name];
}
/**
* Get enum name
*
* @return string
*/
public function name(): string
{
return $this->name;
}
// * STATIC FETCH METHODS
@ -71,6 +78,16 @@ trait BaseEnum
return self::getCache('values');
}
/**
* Get enums
*
* @return array
*/
public static function all(): array
{
return self::cases();
}
/**
* Get enums
*
@ -112,6 +129,40 @@ trait BaseEnum
\collect(self::cases())->keyBy('value')->get($value) : $value;
}
/**
* Test if 2 enum are equals
*
* @param object ...$others
* @return boolean
* @throws \Error If provided element are not enums taht extends BaseEnum.
*/
public function equals(object ...$others): bool
{
foreach ($others as $other) {
if (!\in_array(BaseEnum::class, \class_uses_recursive($other))) {
throw new \Error(__METHOD__ .
' requires $enum to use ' . BaseEnum::class);
}
if (
get_class($this) === get_class($other)
&& $this->value === $other->value
) {
return true;
}
}
return false;
}
/**
* Serialize to json
*
* @return mixed
*/
public function jsonSerialize(): mixed
{
return $this->value;
}
// * CACHE
@ -191,7 +242,7 @@ trait BaseEnum
private static function getCustomDefinitions(): array
{
// ? Has custom props in cache ?
if (\is_null($GLOBALS[self::class]['customProps'])) {
if (!isset($GLOBALS[self::class]) or \is_null($GLOBALS[self::class]['customProps'])) {
$names = \collect(self::cases())->keyBy('name');
$GLOBALS[self::class]['customProps'] = \collect((new \ReflectionClass(self::class))->getConstants())

View file

@ -44,6 +44,7 @@
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Migrations\\": "database/migrations/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}

View file

@ -4,6 +4,9 @@ use Illuminate\Support\Str;
return [
'oldDB' => 'wdmr_prod',
'newDB' => 'mysql',
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
@ -63,6 +66,26 @@ return [
]) : [],
],
'wdmr_prod' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL_NEW'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE_NEW', 'forge'),
'username' => env('DB_USERNAME_NEW', 'forge'),
'password' => env('DB_PASSWORD_NEW', ''),
'unix_socket' => env('DB_SOCKET_NEW', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),

View file

@ -50,6 +50,7 @@ final class ProductFactory extends Factory
'label' => $label,
'slug' => Str::slug($label),
'description' => fake()->word,
'marinade' => fake()->boolean() ? fake()->word : null,
'picture_alt' => fake()->word,
'picture_title' => fake()->word,
'certifications' => [\collect(CertificationsEnum::cases())->random()],

View file

@ -20,7 +20,7 @@ return new class extends Migration
$table->json('label')->comment('Libellé du produit');
$table->json('slug')->unique()->comment('Slug du label');
$table->json('description')->comment('Description du produit');
$table->string('picture')->comment('Path de l\'image du produit, 600px x 600x');
$table->json('picture')->comment('Path de l\'image du produit, 600px x 600x');
$table->json('picture_alt')->comment('Attribut alt de l\'image');
$table->json('picture_title')->comment('Titre de l\'image');
$table->string('certifications')->comment('Tableau d\'entier (CertificationsEnum)');

View file

@ -19,10 +19,10 @@ return new class extends Migration
$table->json('seo_description')->comment('Description SEO de la page');
$table->json('name')->comment('Nom de la recette');
$table->json('slug')->unique()->comment('Slug du nom');
$table->string('picture')->comment('Image de la recette');
$table->json('picture')->comment('Image de la recette');
$table->json('picture_alt')->comment('Attribut alt de l\'image');
$table->json('picture_title')->comment('Titre de l\'image');
$table->string('picture_thumbnail')->comment('Vignette de la recette');
$table->json('picture_thumbnail')->comment('Vignette de la recette');
$table->json('picture_thumbnail_alt')->comment('Attribut alt de l\'image');
$table->json('picture_thumbnail_title')->comment('Titre de l\'image');
$table->json('ingredients')->comment('Liste d\'ingrédients de la recette');

View file

@ -0,0 +1,43 @@
<?php
use App\Models\User;
use Database\Migrations\LeGaulois2016Migration;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
self::consoleLog("Users \n", true);
return [
'users' => [
'model' => User::class,
'comment' => 'Migrate Users',
'bind' => [
'id' => 'id',
'firstname' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
'apply' => function ($args, $model) {
$args = collect(explode(' ', $args));
$model->firstname = $args->get(0, 'Firstname');
$model->lastname = $args->get(0, 'Lastname');
return $model->firstname;
},
'name' => 'name',
'default' => ''
],
'email' => 'email',
'email_verified_at' => 'email_verified_at',
'password' => 'password',
'remember_token' => 'remember_token',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
]
]
];
}
};

View file

@ -0,0 +1,46 @@
<?php
use App\Lib\Helpers\FileStorageHelper;
use App\Models\StoreGroup;
use Database\Migrations\LeGaulois2016Migration;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
self::consoleLog("StoreGroups \n", true);
return [
'retailgroups' => [
'model' => StoreGroup::class,
'comment' => 'Migrate StoreGroups',
'bind' => [
'id' => 'id',
'name' => 'group_name',
'picture' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
'apply' => function ($args, $model) {
if (empty($args)) {
return $args;
}
$model->picture = FileStorageHelper::storeFile(
$model,
new \SplFileInfo(storage_path("app/public/{$args}")),
true
);
return $model->picture;
},
'name' => 'group_logo',
'default' => ''
],
'created_at' => 'created_at',
'updated_at' => 'updated_at',
]
]
];
}
};

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->text('marinade', 'Marinade champs migré sur la fin non demandé par le client')
->nullable()->after('description');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('marinade');
});
}
};

View file

@ -0,0 +1,31 @@
<?php
use App\Models\StoreRegion;
use Database\Migrations\LeGaulois2016Migration;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
self::consoleLog("StoreRegions \n", true);
return [
'retailregions' => [
'model' => StoreRegion::class,
'comment' => 'Migrate StoreRegions',
'bind' => [
'id' => 'id',
'name' => 'region_name',
'picture' => 'region_logo',
'group_id' => 'region_group',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
]
]
];
}
};

View file

@ -0,0 +1,127 @@
<?php
use App\Models\Store;
use App\Models\StoreType;
use Carbon\Exceptions\InvalidFormatException;
use Database\Migrations\LeGaulois2016Migration;
use Illuminate\Support\Carbon;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
self::consoleLog("Store \n", true);
return [
'stores' => [
'model' => Store::class,
'comment' => 'Migrate Stores',
'bind' => [
'id' => 'id',
'state' => 'state',
'street' => 'street',
'zipcode' => 'zipcode',
'city' => 'city',
'latitude' => [
'apply' => function ($args, $model) {
$args = explode('||', $args);
$ID = $args[0];
$long = $args[1];
$lat = $args[2];
$model->longitude = !empty(floatval($long)) ? floatval($long) : 0;
$model->latitude = !empty(floatval($lat)) ? floatval($lat) : 0;
return $model->latitude;
},
'name' => [
'id', 'long', 'lat'
],
'default' => ''
],
'longitude' => [
'apply' => function ($args, $model) {
$args = explode('||', $args);
$ID = $args[0];
$long = $args[1];
$lat = $args[2];
$model->longitude = !empty(floatval($long)) ? floatval($long) : 0;
$model->latitude = !empty(floatval($lat)) ? floatval($lat) : 0;
return $model->longitude;
},
'name' => [
'id', 'long', 'lat'
],
'default' => ''
],
'type_id' => [
'apply' => function ($args, $model) {
$args = explode('||', $args);
$ID = $args[0];
$name = $args[1];
$storeType = StoreType::query()->where('name', $name)->firstOrNew([
'name' => $name
]);
$storeType->saveOrFail();
/** @var \App\Models\Store $model */
$model->type_id = $storeType->id;
return $model->type_id;
},
'name' => [
'id', 'type'
],
'default' => ''
],
'region_id' => 'region_id',
'created_at' => [
'apply' => function ($args, $model) {
$args = explode('||', $args);
$ID = $args[0];
$created_at = $args[1];
try {
$model->created_at = Carbon::parse($created_at);
} catch (InvalidFormatException) {
$model->created_at = now();
}
return $model->type_id;
},
'name' => [
'id', 'created_at'
],
'default' => ''
],
'updated_at' => [
'apply' => function ($args, $model) {
$args = explode('||', $args);
$ID = $args[0];
$updated_at = $args[1];
try {
$model->updated_at = Carbon::parse($updated_at);
} catch (InvalidFormatException) {
$model->updated_at = now();
}
return $model->type_id;
},
'name' => [
'id', 'updated_at'
],
'default' => ''
],
]
]
];
}
};

View file

@ -0,0 +1,28 @@
<?php
use App\Enums\Ranges\PictoEnum;
use App\Models\Range;
use Database\Migrations\LeGaulois2016Migration;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
self::consoleLog("Range \n", true);
collect(PictoEnum::all())->map(function (PictoEnum $pictoEnum) {
Range::query()->where('id', $pictoEnum->value())->firstOrCreate([
'seo_title' => $pictoEnum->label(),
'seo_description' => $pictoEnum->label(),
'label' => $pictoEnum->label(),
'picto' => $pictoEnum,
'status' => true
])->saveOrFail();
});
return [];
}
};

View file

@ -0,0 +1,178 @@
<?php
use App\Enums\Products\CertificationsEnum;
use App\Enums\Ranges\PictoEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Product;
use Database\Migrations\LeGaulois2016Migration;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
self::consoleLog("Products \n", true);
return [
'products' => [
'model' => Product::class,
'comment' => 'Migrate Products',
'bind' => [
'id' => 'id',
'seo_title' => 'name',
'seo_description' => 'name',
'label' => 'name',
'description' => 'description',
'range_id' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
'apply' => function ($args, $model) {
return match ($args) {
'01_chicken' => PictoEnum::chicken->value,
'02_duck' => PictoEnum::duck->value,
'03_turkey' => PictoEnum::turkey->value,
'05_soupchicken' => PictoEnum::soupchicken->value,
'06_guineafowl' => PictoEnum::guineafowl->value,
'07_goose' => PictoEnum::goose->value,
default => throw new \Error("unhandled {$args}")
};
},
'name' => 'poultry',
'default' => ''
],
'list' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
'apply' => function ($args, $model) {
$args = collect($args);
$html = $args->get(0);
$html = !empty($html) ? $html : '<div></div>';
$doc = new \DOMDocument();
$doc->loadHTML($html);
$list = collect();
collect($doc->getElementsByTagName('ul'))
->each(fn (\DOMElement $domNode) => collect($domNode->getElementsByTagName('li'))
->each(fn (\DOMElement $domNode) => $list->push([
'value' => $domNode->textContent
])));
return $list->all();
},
'name' => [
'advantages',
],
'default' => ''
],
'certifications' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
'apply' => function ($args, $model) {
$parsed = collect(json_decode($args));
$enums = [];
if ($parsed->has('checkbox1')) {
$enums[] = CertificationsEnum::haltungsform3;
}
if ($parsed->has('checkbox2')) {
$enums[] = CertificationsEnum::ecc;
}
return $enums;
},
'name' => 'type',
'default' => ''
],
'nutritionnal_values' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
'apply' => function ($args, $model) {
$args = collect(explode(' ', $args));
$data = [
[
// Energy.
'label' => 'Energie',
'value' => $args->get(0),
],
[
// Fat.
'label' => 'Fett',
'value' => $args->get(1),
],
[
// Hereof: saturated fatty acids.
'label' => '• davon gesättigte Fettsäuren',
'value' => $args->get(2),
],
[
// Carbohydrates.
'label' => 'Kohlenhydrate',
'value' => $args->get(3),
],
[
// Of which sugars.
'label' => '• davon Zucker',
'value' => $args->get(4),
],
[
// Protein.
'label' => 'Eiweiß',
'value' => $args->get(5),
],
[
// Salt.
'label' => 'Salz',
'value' => $args->get(6),
],
[
// Fiber.
'label' => 'Balaststoffe',
'value' => $args->get(7),
],
];
return $data;
},
'name' => [
'energy', 'fat', 'sat_fat', 'carbs',
'sugar', 'protein', 'salt', 'fiber',
],
'default' => ''
],
'status' => [
'apply' => function () {
return true;
},
'name' => 'packshot_top',
'default' => ''
],
'picture' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
'apply' => function ($args, $model) {
$model->picture = FileStorageHelper::storeFile(
$model,
new \SplFileInfo(storage_path("app/public/{$args}")),
true
);
return $model->picture;
},
'name' => 'packshot_top',
'default' => ''
],
'picture_alt' => 'name',
'picture_title' => 'name',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
]
]
];
}
};

View file

@ -0,0 +1,149 @@
<?php
use App\Enums\Ranges\PictoEnum;
use App\Enums\Recipes\DifficultyEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Recipe;
use Database\Migrations\LeGaulois2016Migration;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
return new class extends LeGaulois2016Migration
{
/**
* Run migration instructions
*
* @return array
*/
public function instruct(): array
{
collect(File::allFiles(storage_path('app/public/recipes')))
->each(function (\SplFileInfo $splFileInfo) {
$path = pathinfo($splFileInfo->getPath(), PATHINFO_DIRNAME);
$file = Str::slug(pathinfo($splFileInfo->getBasename(), PATHINFO_FILENAME));
$ext = pathinfo($splFileInfo->getBasename(), PATHINFO_EXTENSION);
if (!empty($ext)) {
$file = "{$file}.{$ext}";
}
$path = "{$path}/recipes/{$file}";
File::move(
$splFileInfo,
$path
);
});
self::consoleLog("Recipes \n", true);
return [
'recipes' => [
'model' => Recipe::class,
'comment' => 'Migrate Recipes',
'bind' => [
'id' => 'id',
'seo_title' => 'name',
'seo_description' => 'name',
'name' => 'name',
'picture' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
'apply' => function ($args, $model) {
$path = pathinfo($args, PATHINFO_DIRNAME);
$file = Str::slug(pathinfo($args, PATHINFO_FILENAME));
$ext = pathinfo($args, PATHINFO_EXTENSION);
if (!empty($ext)) {
$file = "{$file}.{$ext}";
}
$path = "{$path}/{$file}";
$model->picture = FileStorageHelper::storeFile(
$model,
new \SplFileInfo(storage_path("app/public/{$path}")),
true
);
return $model->picture;
},
'name' => 'heropic',
'default' => ''
],
'picture_alt' => 'name',
'picture_title' => 'name',
'picture_thumbnail' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
'apply' => function ($args, $model) {
if (empty($args)) {
return '';
}
$path = pathinfo($args, PATHINFO_DIRNAME);
$file = Str::slug(pathinfo($args, PATHINFO_FILENAME));
$ext = pathinfo($args, PATHINFO_EXTENSION);
if (!empty($ext)) {
$file = "{$file}.{$ext}";
}
$path = "{$path}/{$file}";
$model->picture_thumbnail = FileStorageHelper::storeFile(
$model,
new \SplFileInfo(storage_path("app/public/{$path}")),
true
);
return $model->picture_thumbnail;
},
'name' => 'thumbnail',
'default' => ''
],
'picture_thumbnail_alt' => 'name',
'picture_thumbnail_title' => 'name',
'range_id' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
'apply' => function ($args, $model) {
return match ($args) {
'chicken' => PictoEnum::chicken->value,
'goose' => PictoEnum::goose->value,
default => throw new \Error("unhandled {$args}")
};
},
'name' => 'cat',
'default' => ''
],
'tips' => 'tipps',
'teaser' => 'teaser',
'duration' => 'duration',
'difficulty' => [
'apply' => function ($args) {
return match ($args) {
'easy' => DifficultyEnum::easy->value,
'medium' => DifficultyEnum::medium->value,
'hard' => DifficultyEnum::hard->value,
default => throw new \Error("unhandled {$args}")
};
},
'name' => 'difficulty',
'default' => ''
],
'ingredients' => [
'apply' => function ($args) {
return [];
},
'name' => 'ingredients',
'default' => ''
],
'steps' => 'instructions',
'status' => [
'apply' => function () {
return true;
},
'name' => 'difficulty',
'default' => ''
],
'created_at' => 'created_at',
'updated_at' => 'updated_at',
]
]
];
}
};

View file

@ -0,0 +1,314 @@
<?php
namespace Database\Migrations;
use App\Exceptions\AbortRowImportException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use ReflectionFunction;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Terminal;
/**
* Migration Intructions
* Examples
*[
* 'content_ocb' => [
* 'model' => OCBContent::class,
* 'bind' => [
* 'id' => 'ocb_id',
* 'picture' => 'ocb_img',
* 'content' => 'ocb_content',
* 'status' => 'ocb_is_published',
* 'type' => [
* 'apply' => function ($value) {
* switch ($value) {
* case 'VOLAILLE':
* return OCBType::poultry();
* case 'LAPIN':
* return OCBType::rabbit();
* case 'OEUF':
* return OCBType::egg();
* }
* },
* 'name' => 'ocb_type',
* 'default' => ''
* ],
* 'order' => 'ocb_position',
* 'created_at' => [
* 'name' => '',
* 'default' => date('Y-m-d H:i:s')
* ],
* 'updated_at' => [
* 'name' => '',
* 'default' => date('Y-m-d H:i:s')
* ]
* ]
* ],
* 'Recipe ratings' => [
* 'table' => 'recipe',
* 'comment' => 'Add ratings to recipes',
* 'model' => Rating::class,
* 'bind' => [
* 'anyprop' => [
* 'apply' => function ($recipe_id) {
* $recipe_rating = $this->oldDB->table('recipe_rating')
* ->select('recipe_rating_value')
* ->where('recipe_rating_recipe_id', '=', $recipe_id)
* ->get();
* $count = $recipe_rating->count();
* $sum = $recipe_rating->sum(function ($obj) {
* return $obj->recipe_rating_value;
* });
* $this->newDB->table('ratings')
* ->insert([
* 'total' => $sum,
* 'count' => $count,
* 'rating' => $count ? floatval($sum / $count) : 0,
* 'recipes_id' => $recipe_id,
* 'created_at' => date('Y-m-d H:i:s'),
* 'updated_at' => date('Y-m-d H:i:s'),
* ]);
* throw new AbortRowImportException();
* },
* 'name' => 'recipe_id'
* ]
* ]
* ],
*]
*
* @property \Illuminate\Database\ConnectionInterface $oldDB
* @property \Illuminate\Database\ConnectionInterface $newDB
* @param array $instructions
* @return void
*
* Helps to migration from legaulois2016 to legaulois2020
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
abstract class LeGaulois2016Migration extends Migration
{
/** @var ConsoleOutput */
private static $output = null;
/** @var ConsoleSectionOutput */
private static $outputSection = null;
/** @var \Illuminate\Database\ConnectionInterface */
private $oldDB = null;
/** @var \Illuminate\Database\ConnectionInterface */
private $newDB = null;
/** @var array */
private $instructions = null;
/**
* LeGaulois2016Migration
* @throws \ErrorException If failed to connect db.
*/
public function __construct()
{
$oldDB = config(sprintf('database.connections.%s', config('database.oldDB')));
$newDB = config(sprintf('database.connections.%s', config('database.newDB')));
if (!$oldDB) {
throw new \ErrorException(sprintf('Unknown database connection %s for newDB', config('database.newDB')));
}
if (!$newDB) {
throw new \ErrorException(sprintf('Unknown database connection %s for oldDB', config('database.oldDB')));
}
$this->oldDB = DB::connection(config('database.oldDB'));
$this->newDB = DB::connection(config('database.newDB'));
}
/**
* run migration
*
* @return void
*/
public function up()
{
$this->instructions = $this->instruct();
$this->processData();
}
/**
* Do nothing
*
* @return void
*/
public function down()
{
}
/**
* Undocumented function
*
* @param string $name
* @return \Illuminate\Database\ConnectionInterface
*/
public function __get(string $name)
{
if ($name === 'oldDB') {
return $this->oldDB;
}
if ($name === 'newDB') {
return $this->newDB;
}
}
/**
* Set migration instructions to execute on "up" only
* see Instructions in \App\Database\Migrations\LeGaulois2016Migration
* @return array
*/
abstract public function instruct(): array;
/**
* Core execution on instruction
*
* @return void
*/
private function processData()
{
$getOldData = function ($keyOrKeys, $array) {
if (is_array($keyOrKeys)) {
$o = [];
foreach ($keyOrKeys as $key) {
$o[] = strval($array[$key]);
}
return implode('||', $o);
}
return $array[$keyOrKeys] ?? null;
};
foreach ($this->instructions as $oldTableName => $modelData) {
$migrationName = $oldTableName;
$oldTableName = $modelData['table'] ?? $oldTableName;
$rowNbrs = $this->oldDB->table($oldTableName)->count();
$rowInserted = 0;
$orderBy = $modelData['orderBy'] ?? false;
self::consoleLog($this->renderProgress($migrationName, $rowInserted, $rowNbrs));
$i = 0;
$page = 1;
$perPage = env('MIGRATIONPAGINATION', 300);
do {
$q = $this->oldDB->table($oldTableName);
if ($orderBy) {
$q = $q->orderBy($orderBy);
}
$results = $q->paginate($perPage, ['*'], 'page', $page);
$i += $perPage;
$page++;
foreach ($results as $oldData) {
$model = null;
try {
// * Try to get exisiting model ID (to avoid SQL Integrity violations doing delete and create)
$model = $modelData['model']::findOrFail(
$getOldData($modelData['bind']['id'] ?? 0, (array)$oldData)
);
} catch (ModelNotFoundException $e) {
$model = new $modelData['model']();
}
try {
// * bind each property to it's data and save
foreach ($modelData['bind'] as $modelAttr => $oldModelAttr) {
if (is_array($oldModelAttr)) {
if (array_key_exists('apply', $oldModelAttr)) {
$argC = (new ReflectionFunction($oldModelAttr['apply']))->getNumberOfParameters();
$argV = [
$getOldData($oldModelAttr['name'], (array)$oldData)
];
if ($argC > 1) {
$argV[] = $model;
}
$model[$modelAttr] =
call_user_func_array($oldModelAttr['apply'], $argV) ??
$oldModelAttr['default'] ??
null;
} else {
$model[$modelAttr] =
$getOldData($oldModelAttr['name'], (array)$oldData) ??
$oldModelAttr['default'] ??
null;
}
} else {
$model[$modelAttr] = ((array)$oldData)[$oldModelAttr];
} //end if
} //end foreach
$model->saveOrFail();
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch (AbortRowImportException $e) {
// * DO nothing
} finally {
++$rowInserted;
if (rand(0, 15) == 10) {
// * prevent screen ficklering
self::consoleLog($this->renderProgress($migrationName, $rowInserted, $rowNbrs));
}
} //end try
} //end foreach
} while ($i <= $rowNbrs);
if (array_key_exists('comment', $modelData)) {
$migrationName .= sprintf(' <%s> ', $modelData['comment']);
}
$terminal = new Terminal();
$consoleLength = $terminal->getWidth();
$r = round(80 * 100 / $consoleLength) - strlen($migrationName);
$r = $r >= 0 ? $r : 0;
self::consoleLog(sprintf('%s%smigrated', $migrationName, str_repeat(' ', $r)));
// * Cariage return
self::consoleLog("", true);
} //end foreach
}
/**
* Prints a message to the console
*
* @param string $message
* @param boolean $newLine
* @return void
*/
public static function consoleLog(string $message, bool $newLine = false)
{
if (!self::$output) {
self::$output = new ConsoleOutput();
}
if ($newLine) {
self::$outputSection = null;
self::$output->write(sprintf("<info>%s</info>", $message));
} else {
if (!self::$outputSection) {
self::$outputSection = self::$output->section();
}
self::$outputSection->overwrite(sprintf('<info>%s</info>', $message));
}
}
/**
* Render the progressbar to a string
*
* @param string $oldTableName
* @param integer $count
* @param integer $total
* @return string
*/
protected function renderProgress(string $oldTableName, int $count, int $total)
{
$total = !$total ? 1 : $total;
$oldTableName = sprintf('%s ', $oldTableName);
$percent = $count * 100 / $total;
$percentText = sprintf(' [%d of %d]', $count, $total);
$terminal = new Terminal();
$consoleLength = $terminal->getWidth();
$bar = $consoleLength - strlen($oldTableName) - strlen($percentText);
$bar = $bar < 0 ? 0 : $bar;
$prog = round($percent / 100 * $bar);
$rep = $bar - $prog;
return $oldTableName . str_repeat('#', $prog) . str_repeat('_', $rep >= 0 ? $rep : 0) . $percentText;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -186,6 +186,24 @@
</div>
</fieldset>
</div>
{{-- MARINADE --}}
<div class="col-12 mb-3">
<fieldset class="border bg-white p-3">
<legend>{{ __('Marinade') }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.text-html', [
'id' => 'productMarinade',
'label' => __('Marinade of the :model', ['model' => Str::singular(__('models.classes.products'))]),
'name' => 'marinade',
'model' => $productModel ?? null,
'required' => false,
'helper' => __('Proofread your content before publishing it (minimum 15 characters).'),
])
</div>
</div>
</fieldset>
</div>
{{-- PICTURE --}}
<div class="col-12 mb-3">
<fieldset class="border h-100 bg-white p-3">

View file

@ -1,9 +1,6 @@
@php
$pictoUrl = match($picto) {
\App\Enums\Products\CertificationsEnum::haltungsform1 => Vite::asset('resources/assets/images/Icons/haltungsform1.png'),
\App\Enums\Products\CertificationsEnum::haltungsform2 => Vite::asset('resources/assets/images/Icons/haltungsform2.png'),
\App\Enums\Products\CertificationsEnum::haltungsform3 => Vite::asset('resources/assets/images/Icons/haltungsform3.png'),
\App\Enums\Products\CertificationsEnum::haltungsform4 => Vite::asset('resources/assets/images/Icons/haltungsform4.png'),
\App\Enums\Products\CertificationsEnum::ecc => Vite::asset('resources/assets/images/Icons/ecc.png'),
default => throw new \Error("Undefined \App\Enums\Products\CertificationsEnum::class value {$picto->value}")
};

View file

@ -79,6 +79,11 @@
</ul>
@endforeach
</div>
@if ($productModel->marinade)
<div id="marinade" class="col-span-2 mt-4 ck-content">
{!! clean($productModel->marinade) !!}
</div>
@endif
</div>
</div>

View file

@ -71,7 +71,7 @@ Route::prefix('bo')
// * PRODUCTS
Route::resource('products', ProductController::class)->except(['show'])
->withoutMiddleware('htmlsanitize')
->middleware(['htmlsanitize:description']);
->middleware(['htmlsanitize:description,marinade']);
Route::post('/products/{product}/change-order', [ProductController::class, 'changeOrder'])
->name('products.change-order');
Route::post('/products/{product}/change-published', [ProductController::class, 'changePublished'])