From 49d8143062db6c9d78711157d13a58b2d8b306f5 Mon Sep 17 00:00:00 2001 From: panabonic Date: Sun, 19 Nov 2023 13:08:10 +0300 Subject: [PATCH] locale pages, clone pages and objects, some other stuff --- .../Controllers/Api/Pages/PagesController.php | 8 +- app/Models/Localization/Locale.php | 23 ++-- app/Models/Objects/NirObject.php | 1 + app/Models/Pages/Page.php | 65 ++++++++--- app/Services/Forms/Pages/LocalePageForms.php | 101 ++++++++++++++++++ app/Services/Forms/Pages/PageForms.php | 8 +- .../Forms/Pages/PageFormsServices.php | 3 +- app/Support/HasObjectsTrait.php | 11 +- app/Transformers/Pages/PageTransformer.php | 9 +- .../Localization/LocalesTableSeeder.php | 10 +- database/seeders/Pages/PagesTableSeeder.php | 2 +- routes/api.php | 3 +- 12 files changed, 207 insertions(+), 37 deletions(-) create mode 100644 app/Services/Forms/Pages/LocalePageForms.php diff --git a/app/Http/Controllers/Api/Pages/PagesController.php b/app/Http/Controllers/Api/Pages/PagesController.php index a410139..497bf70 100644 --- a/app/Http/Controllers/Api/Pages/PagesController.php +++ b/app/Http/Controllers/Api/Pages/PagesController.php @@ -19,7 +19,7 @@ class PagesController extends Controller { } public function root(Request $request): JsonResponse { - return fractal(Page::root(), new PageTransformer())->respond(); + return fractal(Page::root($request->get('with_trashed')), new PageTransformer())->respond(); } public function find(Request $request): ?JsonResponse { @@ -52,6 +52,12 @@ class PagesController extends Controller { return fractal($model, new PageTransformer())->respond(); } + public function clone(Request $request, $id): JsonResponse { + $model = $this->model->byUuid($id)->firstOrFail(); + $parent = $this->model->byUuid($request->get('parent'))->first(); + $clone = $model->clone($parent, $request->get('recursive')); + return fractal($clone, new PageTransformer())->respond(); + } public function store(Request $request): void { diff --git a/app/Models/Localization/Locale.php b/app/Models/Localization/Locale.php index c6ae03f..5684ae2 100644 --- a/app/Models/Localization/Locale.php +++ b/app/Models/Localization/Locale.php @@ -2,9 +2,11 @@ namespace App\Models\Localization; +use App\Models\Pages\Page; use App\Support\RelationValuesTrait; use App\Support\UuidScopeTrait; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\App; @@ -28,10 +30,22 @@ class Locale extends Model { ]; + public function page(): BelongsTo { + return $this->belongsTo(Page::class, 'name', 'slug'); + } + + public function scopeEnabled($query) { return $query->where(['is_enabled' => true]); } + public function scopeDefault($query) { + return $query->where(['name' => env('APP_LOCALE')]); + } + public function scopeNotDefault($query) { + return $query->where('name', '!=', env('APP_LOCALE')); + } + public function getIsActiveAttribute(): bool { return $this->name === App::getLocale(); @@ -49,13 +63,4 @@ class Locale extends Model { } - public function setAsDefault(): bool { - self::query()->where(['is_default' => true])->update(['is_default' => false]); - return $this->update(['is_default' => true]); - } - - public static function default() { - return self::query()->where(['is_default' => 1])->first(); - } - } diff --git a/app/Models/Objects/NirObject.php b/app/Models/Objects/NirObject.php index 354a9f7..2d098e9 100644 --- a/app/Models/Objects/NirObject.php +++ b/app/Models/Objects/NirObject.php @@ -170,6 +170,7 @@ class NirObject extends Model { $clone->setValue($field->name, $this->getValue($field->name)); }); }); + $clone->copyObjectsFromObjectable($this); return $clone; } diff --git a/app/Models/Pages/Page.php b/app/Models/Pages/Page.php index 78cc57b..f5cd565 100644 --- a/app/Models/Pages/Page.php +++ b/app/Models/Pages/Page.php @@ -10,6 +10,7 @@ use App\Models\User; use App\Support\HasObjectsTrait; use App\Support\RelationValuesTrait; use App\Support\UuidScopeTrait; +use DragonCode\Support\Facades\Helpers\Str; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -52,7 +53,7 @@ class Page extends Model { } public function siblings(): HasMany { - return $this->hasMany(Page::class, 'parent_id', 'parent_id'); + return $this->hasMany(Page::class, 'parent_id', 'parent_id')->where('id', '!=', $this->id); } public function picture(): BelongsTo { @@ -123,18 +124,34 @@ class Page extends Model { } - public static function byUrl($url) { - if ($url === '/') return Page::query()->where(['parent_id' => 0, 'slug' => ''])->first(); - elseif ($url = trim($url, '/ ')) { - $query = self::query(); - collect(explode('/', $url))->reverse()->values()->each(function($slug, $index) use($query) { - if ($slug !== '') { - $index ? $query->nthParentSlug($index, $slug) : $query->bySlug($slug); - } - }); - return $query->first(); - } - return null; + public function checkConflictedProps($props = ['name', 'slug']) { + collect($props)->each(function($prop) { + $this->checkConflictedProp($prop); + }); + } + public function checkConflictedProp($prop): ?bool { + if ($val = $this->$prop) { + $i = 1; + while($this->siblings()->where([$prop => $val])->exists()) { + $val = "{$this->$prop} (копия {$i})"; + if ($prop === 'slug') $val = Str::slug($val); + $i++; + } + return ($this->$prop !== $val) ? $this->update([$prop => $val]) : null; + } else return null; + } + + + public function clone(?Page $parent = null, $recursive = false): Model { + $model = $this->replicate(['uuid']); + if ($parent) $model->parent_id = $parent->id; + $model->push(); + $model->copyObjectsFromObjectable($this); + $model->checkConflictedProps(); + if ($recursive) $this->children->each(function($child) use($model) { + $child->clone($model, true); + }); + return $model; } @@ -145,6 +162,7 @@ class Page extends Model { } else $this->moveSet('forward', $ord, null, $parent); $this->update(['parent_id' => $parent->id ?? 0, 'ord' => $ord]); $this->trimIndexes([$prevParent->id ?? 0, $parent->id ?? 0]); + $this->checkConflictedProps(); } public function moveSet($dir = 'forward', $ordFrom = null, $ordTo = null, ?Page $parent = null) { $query = Page::query()->where(['parent_id' => $parent->id ?? 0])->orderBy('ord'); @@ -168,8 +186,25 @@ class Page extends Model { } - public static function root() { - return self::query()->where(['parent_id' => 0])->orderBy('ord')->get(); + public static function byUrl($url) { + if ($url === '/') return Page::query()->where(['parent_id' => 0, 'slug' => ''])->first(); + elseif ($url = trim($url, '/ ')) { + $query = self::query(); + collect(explode('/', $url))->reverse()->values()->each(function($slug, $index) use($query) { + if ($slug !== '') { + $index ? $query->nthParentSlug($index, $slug) : $query->bySlug($slug); + } + }); + return $query->first(); + } + return null; + } + + + public static function root($withTrashed = false) { + $query = self::query()->where(['parent_id' => 0])->orderBy('ord'); + if ($withTrashed) $query->withTrashed(); + return $query->get(); } } diff --git a/app/Services/Forms/Pages/LocalePageForms.php b/app/Services/Forms/Pages/LocalePageForms.php new file mode 100644 index 0000000..2964f1c --- /dev/null +++ b/app/Services/Forms/Pages/LocalePageForms.php @@ -0,0 +1,101 @@ + 'Создание страницы локализации', 'update' => 'Редактирование страницы локализации']; + + public function form(?string $id = null, array $data = []): array { + $model = Page::byUuid($id)->first(); + $groups = [ + ['name' => 'common', 'title' => 'Основные свойства страницы', 'fields' => $this->commonGroupFields($model)], + ['name' => 'seo', 'title' => 'Мета данные', 'fields' => $this->seoGroupFields($model)] + ]; + return ['title' => $this->formTitle($model), 'data' => $groups]; + } + + public function commonGroupFields(?Page $model): array { + $fields = [ + [ + 'name' => 'locale', + 'title' => 'Язык', + 'type' => FieldType::RELATION, + 'required' => true, + 'options' => $this->getLocales() + ], + [ + 'name' => 'picture', + 'title' => 'Изображение', + 'type' => FieldType::IMAGE, + 'value' => ($v = $model->picture ?? null) ? fractal($v, new AssetTransformer()) : null + ] + ]; + return ['data' => $fields]; + } + + public function seoGroupFields(?Page $model): array { + $fields = [ + [ + 'name' => 'h1', + 'title' => 'Заголовок H1', + 'type' => FieldType::STRING, + 'value' => $model->h1 ?? null + ], + [ + 'name' => 'title', + 'title' => 'Title страницы', + 'type' => FieldType::STRING, + 'value' => $model->title ?? null + ], + [ + 'name' => 'description', + 'title' => 'Описание', + 'type' => FieldType::TEXT, + 'value' => $model->description ?? null + ], + [ + 'name' => 'keywords', + 'title' => 'Ключевые слова', + 'type' => FieldType::STRING, + 'value' => $model->keywords ?? null + ], + + ]; + return ['data' => $fields]; + } + + + public function getLocales(): Fractal { + return fractal(Locale::query()->notDefault()->doesntHave('page')->get(), new LocaleTransformer()); + } + + + + public function store(array $data): ?JsonResponse { + $locale = Locale::byUuid($data['locale'] ?? null)->firstOrFail(); + $data['picture_id'] = ($v = Asset::byUuid($data['picture'] ?? null)->first()) ? $v->id : null; + $data += ['type' => PageType::CONTENT, 'name' => $locale->title, 'slug' => $locale->name, 'ord' => Page::query()->where(['parent_id' => 0])->max('ord') + 1]; + $model = Page::query()->where(['parent_id' => 0, 'slug' => $locale->name])->withTrashed()->first() ?? Page::create($data); + $model->restore(); + return fractal($model, new PageTransformer())->respond(); + } + + public function update(string $id, array $data): ?JsonResponse { + $model = Page::byUuid($id)->firstOrFail(); + $data['picture_id'] = ($v = Asset::byUuid($data['picture'] ?? null)->first()) ? $v->id : null; + $model->update($data); + return fractal($model->fresh(), new PageTransformer())->respond(); + } +} diff --git a/app/Services/Forms/Pages/PageForms.php b/app/Services/Forms/Pages/PageForms.php index afdc63a..a22ff31 100644 --- a/app/Services/Forms/Pages/PageForms.php +++ b/app/Services/Forms/Pages/PageForms.php @@ -50,6 +50,7 @@ class PageForms extends FormsService { 'title' => 'Адрес', 'type' => FieldType::STRING, 'hidden' => !$model, + 'readonly' => !($model->parent_id ?? true), 'value' => $model->slug ?? null ], [ @@ -123,9 +124,10 @@ class PageForms extends FormsService { $parent = Page::byUuid($data['parent'] ?? null)->first(); $data['parent_id'] = $parent->id ?? 0; $data['picture_id'] = ($v = Asset::byUuid($data['picture'] ?? null)->first()) ? $v->id : null; - $data['slug'] = $data['slug'] ?? Str::slug(Str::transliterate($data['name'] ?? null)); + $data['slug'] = Str::slug($data['slug'] ?? $data['name'] ?? 'unnamed'); $model = Page::create($data); $model->update(['ord' => $model->getMaxOrd()]); + $model->checkConflictedProps(); if ($model->type === PageType::REGISTRY) $model->registry->update(['type' => $data['registry_type'] ?? RegistryType::SIMPLE]); return fractal($model, new PageTransformer())->respond(); } @@ -133,8 +135,10 @@ class PageForms extends FormsService { public function update(string $id, array $data): ?JsonResponse { $model = Page::byUuid($id)->firstOrFail(); $data['picture_id'] = ($v = Asset::byUuid($data['picture'] ?? null)->first()) ? $v->id : null; - $data['slug'] = $data['slug'] ?? Str::slug(Str::transliterate($data['name'] ?? null)); + if ($model->parent_id) $data['slug'] = Str::slug($data['slug'] ?? $data['name'] ?? 'unnamed'); + else unset($data['slug']); $model->update($data); + $model->checkConflictedProps(); if ($model->type === PageType::REGISTRY) $model->registry->update(['type' => $data['registry_type'] ?? RegistryType::SIMPLE]); return fractal($model->fresh(), new PageTransformer())->respond(); } diff --git a/app/Services/Forms/Pages/PageFormsServices.php b/app/Services/Forms/Pages/PageFormsServices.php index 548869d..6446b8f 100644 --- a/app/Services/Forms/Pages/PageFormsServices.php +++ b/app/Services/Forms/Pages/PageFormsServices.php @@ -4,6 +4,7 @@ namespace App\Services\Forms\Pages; class PageFormsServices { public static array $services = [ - 'page' => PageForms::class + 'page' => PageForms::class, + 'localePage' => LocalePageForms::class ]; } diff --git a/app/Support/HasObjectsTrait.php b/app/Support/HasObjectsTrait.php index 48b97b2..47b3c48 100644 --- a/app/Support/HasObjectsTrait.php +++ b/app/Support/HasObjectsTrait.php @@ -58,7 +58,7 @@ trait HasObjectsTrait { ($ord > $currentOrd) ? $this->moveObjectsSet('backward', $currentOrd, $ord, $group) : $this->moveObjectsSet('forward', $ord, $currentOrd, $group); } else $this->moveObjectsSet('forward', $ord, null, $group); $this->objects()->updateExistingPivot($object, ['ord' => $ord, 'group' => $group ?? 'default']); - $this->trimIndexes([$group, $currentGroup]); + $this->trimObjectIndexes([$group, $currentGroup]); } public function moveObjectsSet($dir = 'forward', $ordFrom = null, $ordTo = null, $group = null) { @@ -70,7 +70,7 @@ trait HasObjectsTrait { }); } - public function trimIndexes($groups) { + public function trimObjectIndexes($groups) { collect(is_array($groups) ? $groups : [$groups])->unique()->each(function($group) { $this->objectsByGroup($group)->orderByPivot('ord')->each(function($object, $index) { if ($object->pivot->ord !== $index) $this->objects()->updateExistingPivot($object, ['ord' => $index]); @@ -79,6 +79,13 @@ trait HasObjectsTrait { } + public function copyObjectsFromObjectable($objectable) { + $objectable->objects->each(function($object) { + if ($clone = $object->clone()) $this->objects()->attach($clone->id, ['ord' => $object->pivot->ord, 'group' => $object->pivot->group]); + }); + } + + public function getValue($fieldName) { return $this->object ? $this->object->getValue($fieldName) : null; } diff --git a/app/Transformers/Pages/PageTransformer.php b/app/Transformers/Pages/PageTransformer.php index 107a0c6..d45b494 100644 --- a/app/Transformers/Pages/PageTransformer.php +++ b/app/Transformers/Pages/PageTransformer.php @@ -32,12 +32,13 @@ class PageTransformer extends TransformerAbstract { 'sub_type' => $model->sub_type, 'name' => $model->name, 'title' => $model->title, + 'h1' => $model->h1, 'description' => $model->description, 'keywords' => $model->keywords, - 'h1' => $model->h1, 'has_children' => $model->children()->exists(), 'created_at' => $model->created_at ? $model->created_at->toIso8601String() : null, - 'updated_at' => $model->updated_at ? $model->updated_at->toIso8601String() : null + 'updated_at' => $model->updated_at ? $model->updated_at->toIso8601String() : null, + 'deleted_at' => $model->deleted_at ? $model->deleted_at->toIso8601String() : null ]; } @@ -45,6 +46,10 @@ class PageTransformer extends TransformerAbstract { return $this->collection($model->children, new PageTransformer()); } + public function includeChildrenWithTrashed(Page $model): Collection { + return $this->collection($model->children()->withTrashed()->get, new PageTransformer()); + } + public function includeParent(Page $model): ?Item { return $model->parent ? $this->item($model->parent, new PageTransformer()) : null; } diff --git a/database/seeders/Localization/LocalesTableSeeder.php b/database/seeders/Localization/LocalesTableSeeder.php index 7cb1a0a..4f52c5f 100644 --- a/database/seeders/Localization/LocalesTableSeeder.php +++ b/database/seeders/Localization/LocalesTableSeeder.php @@ -7,9 +7,13 @@ use Illuminate\Database\Seeder; class LocalesTableSeeder extends Seeder { public array $locales = [ - 'ru' => ['title' => 'Русский', 'is_enabled' => true, 'is_default' => true], - 'en' => ['title' => 'English', 'is_enabled' => true], - 'ua' => ['title' => 'Українська', 'is_enabled' => true] + 'ru' => ['title' => 'Русский'], + 'en' => ['title' => 'Английский'], + 'de' => ['title' => 'Немецкий'], + 'fr' => ['title' => 'Французский'], + 'it' => ['title' => 'Итальянский'], + 'es' => ['title' => 'Испанский'], + 'cn' => ['title' => 'Китайский'] ]; public function run() { diff --git a/database/seeders/Pages/PagesTableSeeder.php b/database/seeders/Pages/PagesTableSeeder.php index 2cd9d77..04b24ca 100644 --- a/database/seeders/Pages/PagesTableSeeder.php +++ b/database/seeders/Pages/PagesTableSeeder.php @@ -52,7 +52,7 @@ class PagesTableSeeder extends Seeder { 'Структура ТК' => ['type' => PageType::TK_STRUCTURE] ] ], - 'Main page' => [ + 'Агнлийский' => [ 'slug' => 'en', 'children' => [ 'Content page' => [ diff --git a/routes/api.php b/routes/api.php index 123f97a..bbf61ed 100644 --- a/routes/api.php +++ b/routes/api.php @@ -24,7 +24,8 @@ Route::group(['prefix' => 'pages'], function() { Route::get('/find', 'Api\Pages\PagesController@find'); Route::get('/{id}', 'Api\Pages\PagesController@show'); Route::group(['middleware' => ['auth:api']], function() { - Route::put('/{id}', 'Api\Pages\PagesController@move'); + Route::put('/move/{id}', 'Api\Pages\PagesController@move'); + Route::put('/clone/{id}', 'Api\Pages\PagesController@clone'); Route::delete('/{id}', 'Api\Pages\PagesController@destroy'); }); });