locale pages, clone pages and objects, some other stuff

master
Константин 2023-11-19 13:08:10 +03:00
parent c478fa1636
commit 49d8143062
12 changed files with 207 additions and 37 deletions

View File

@ -19,7 +19,7 @@ class PagesController extends Controller {
} }
public function root(Request $request): JsonResponse { 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 { public function find(Request $request): ?JsonResponse {
@ -52,6 +52,12 @@ class PagesController extends Controller {
return fractal($model, new PageTransformer())->respond(); 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 { public function store(Request $request): void {

View File

@ -2,9 +2,11 @@
namespace App\Models\Localization; namespace App\Models\Localization;
use App\Models\Pages\Page;
use App\Support\RelationValuesTrait; use App\Support\RelationValuesTrait;
use App\Support\UuidScopeTrait; use App\Support\UuidScopeTrait;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\App; 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) { public function scopeEnabled($query) {
return $query->where(['is_enabled' => true]); 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 { public function getIsActiveAttribute(): bool {
return $this->name === App::getLocale(); 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();
}
} }

View File

@ -170,6 +170,7 @@ class NirObject extends Model {
$clone->setValue($field->name, $this->getValue($field->name)); $clone->setValue($field->name, $this->getValue($field->name));
}); });
}); });
$clone->copyObjectsFromObjectable($this);
return $clone; return $clone;
} }

View File

@ -10,6 +10,7 @@ use App\Models\User;
use App\Support\HasObjectsTrait; use App\Support\HasObjectsTrait;
use App\Support\RelationValuesTrait; use App\Support\RelationValuesTrait;
use App\Support\UuidScopeTrait; use App\Support\UuidScopeTrait;
use DragonCode\Support\Facades\Helpers\Str;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
@ -52,7 +53,7 @@ class Page extends Model {
} }
public function siblings(): HasMany { 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 { public function picture(): BelongsTo {
@ -123,18 +124,34 @@ class Page extends Model {
} }
public static function byUrl($url) { public function checkConflictedProps($props = ['name', 'slug']) {
if ($url === '/') return Page::query()->where(['parent_id' => 0, 'slug' => ''])->first(); collect($props)->each(function($prop) {
elseif ($url = trim($url, '/ ')) { $this->checkConflictedProp($prop);
$query = self::query(); });
collect(explode('/', $url))->reverse()->values()->each(function($slug, $index) use($query) { }
if ($slug !== '') { public function checkConflictedProp($prop): ?bool {
$index ? $query->nthParentSlug($index, $slug) : $query->bySlug($slug); if ($val = $this->$prop) {
} $i = 1;
}); while($this->siblings()->where([$prop => $val])->exists()) {
return $query->first(); $val = "{$this->$prop} (копия {$i})";
} if ($prop === 'slug') $val = Str::slug($val);
return null; $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); } else $this->moveSet('forward', $ord, null, $parent);
$this->update(['parent_id' => $parent->id ?? 0, 'ord' => $ord]); $this->update(['parent_id' => $parent->id ?? 0, 'ord' => $ord]);
$this->trimIndexes([$prevParent->id ?? 0, $parent->id ?? 0]); $this->trimIndexes([$prevParent->id ?? 0, $parent->id ?? 0]);
$this->checkConflictedProps();
} }
public function moveSet($dir = 'forward', $ordFrom = null, $ordTo = null, ?Page $parent = null) { public function moveSet($dir = 'forward', $ordFrom = null, $ordTo = null, ?Page $parent = null) {
$query = Page::query()->where(['parent_id' => $parent->id ?? 0])->orderBy('ord'); $query = Page::query()->where(['parent_id' => $parent->id ?? 0])->orderBy('ord');
@ -168,8 +186,25 @@ class Page extends Model {
} }
public static function root() { public static function byUrl($url) {
return self::query()->where(['parent_id' => 0])->orderBy('ord')->get(); 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();
} }
} }

View File

@ -0,0 +1,101 @@
<?php
namespace App\Services\Forms\Pages;
use App\Models\Asset;
use App\Models\Localization\Locale;
use App\Models\Objects\FieldType;
use App\Models\Pages\Page;
use App\Models\Pages\PageType;
use App\Services\Forms\FormsService;
use App\Transformers\Assets\AssetTransformer;
use App\Transformers\Localization\LocaleTransformer;
use App\Transformers\Pages\PageTransformer;
use Illuminate\Http\JsonResponse;
use Spatie\Fractal\Fractal;
class LocalePageForms extends FormsService {
public array $formTitles = ['create' => 'Создание страницы локализации', '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();
}
}

View File

@ -50,6 +50,7 @@ class PageForms extends FormsService {
'title' => 'Адрес', 'title' => 'Адрес',
'type' => FieldType::STRING, 'type' => FieldType::STRING,
'hidden' => !$model, 'hidden' => !$model,
'readonly' => !($model->parent_id ?? true),
'value' => $model->slug ?? null 'value' => $model->slug ?? null
], ],
[ [
@ -123,9 +124,10 @@ class PageForms extends FormsService {
$parent = Page::byUuid($data['parent'] ?? null)->first(); $parent = Page::byUuid($data['parent'] ?? null)->first();
$data['parent_id'] = $parent->id ?? 0; $data['parent_id'] = $parent->id ?? 0;
$data['picture_id'] = ($v = Asset::byUuid($data['picture'] ?? null)->first()) ? $v->id : null; $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 = Page::create($data);
$model->update(['ord' => $model->getMaxOrd()]); $model->update(['ord' => $model->getMaxOrd()]);
$model->checkConflictedProps();
if ($model->type === PageType::REGISTRY) $model->registry->update(['type' => $data['registry_type'] ?? RegistryType::SIMPLE]); if ($model->type === PageType::REGISTRY) $model->registry->update(['type' => $data['registry_type'] ?? RegistryType::SIMPLE]);
return fractal($model, new PageTransformer())->respond(); return fractal($model, new PageTransformer())->respond();
} }
@ -133,8 +135,10 @@ class PageForms extends FormsService {
public function update(string $id, array $data): ?JsonResponse { public function update(string $id, array $data): ?JsonResponse {
$model = Page::byUuid($id)->firstOrFail(); $model = Page::byUuid($id)->firstOrFail();
$data['picture_id'] = ($v = Asset::byUuid($data['picture'] ?? null)->first()) ? $v->id : null; $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->update($data);
$model->checkConflictedProps();
if ($model->type === PageType::REGISTRY) $model->registry->update(['type' => $data['registry_type'] ?? RegistryType::SIMPLE]); if ($model->type === PageType::REGISTRY) $model->registry->update(['type' => $data['registry_type'] ?? RegistryType::SIMPLE]);
return fractal($model->fresh(), new PageTransformer())->respond(); return fractal($model->fresh(), new PageTransformer())->respond();
} }

View File

@ -4,6 +4,7 @@ namespace App\Services\Forms\Pages;
class PageFormsServices { class PageFormsServices {
public static array $services = [ public static array $services = [
'page' => PageForms::class 'page' => PageForms::class,
'localePage' => LocalePageForms::class
]; ];
} }

View File

@ -58,7 +58,7 @@ trait HasObjectsTrait {
($ord > $currentOrd) ? $this->moveObjectsSet('backward', $currentOrd, $ord, $group) : $this->moveObjectsSet('forward', $ord, $currentOrd, $group); ($ord > $currentOrd) ? $this->moveObjectsSet('backward', $currentOrd, $ord, $group) : $this->moveObjectsSet('forward', $ord, $currentOrd, $group);
} else $this->moveObjectsSet('forward', $ord, null, $group); } else $this->moveObjectsSet('forward', $ord, null, $group);
$this->objects()->updateExistingPivot($object, ['ord' => $ord, 'group' => $group ?? 'default']); $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) { 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) { collect(is_array($groups) ? $groups : [$groups])->unique()->each(function($group) {
$this->objectsByGroup($group)->orderByPivot('ord')->each(function($object, $index) { $this->objectsByGroup($group)->orderByPivot('ord')->each(function($object, $index) {
if ($object->pivot->ord !== $index) $this->objects()->updateExistingPivot($object, ['ord' => $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) { public function getValue($fieldName) {
return $this->object ? $this->object->getValue($fieldName) : null; return $this->object ? $this->object->getValue($fieldName) : null;
} }

View File

@ -32,12 +32,13 @@ class PageTransformer extends TransformerAbstract {
'sub_type' => $model->sub_type, 'sub_type' => $model->sub_type,
'name' => $model->name, 'name' => $model->name,
'title' => $model->title, 'title' => $model->title,
'h1' => $model->h1,
'description' => $model->description, 'description' => $model->description,
'keywords' => $model->keywords, 'keywords' => $model->keywords,
'h1' => $model->h1,
'has_children' => $model->children()->exists(), 'has_children' => $model->children()->exists(),
'created_at' => $model->created_at ? $model->created_at->toIso8601String() : null, '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()); 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 { public function includeParent(Page $model): ?Item {
return $model->parent ? $this->item($model->parent, new PageTransformer()) : null; return $model->parent ? $this->item($model->parent, new PageTransformer()) : null;
} }

View File

@ -7,9 +7,13 @@ use Illuminate\Database\Seeder;
class LocalesTableSeeder extends Seeder { class LocalesTableSeeder extends Seeder {
public array $locales = [ public array $locales = [
'ru' => ['title' => 'Русский', 'is_enabled' => true, 'is_default' => true], 'ru' => ['title' => 'Русский'],
'en' => ['title' => 'English', 'is_enabled' => true], 'en' => ['title' => 'Английский'],
'ua' => ['title' => 'Українська', 'is_enabled' => true] 'de' => ['title' => 'Немецкий'],
'fr' => ['title' => 'Французский'],
'it' => ['title' => 'Итальянский'],
'es' => ['title' => 'Испанский'],
'cn' => ['title' => 'Китайский']
]; ];
public function run() { public function run() {

View File

@ -52,7 +52,7 @@ class PagesTableSeeder extends Seeder {
'Структура ТК' => ['type' => PageType::TK_STRUCTURE] 'Структура ТК' => ['type' => PageType::TK_STRUCTURE]
] ]
], ],
'Main page' => [ 'Агнлийский' => [
'slug' => 'en', 'slug' => 'en',
'children' => [ 'children' => [
'Content page' => [ 'Content page' => [

View File

@ -24,7 +24,8 @@ Route::group(['prefix' => 'pages'], function() {
Route::get('/find', 'Api\Pages\PagesController@find'); Route::get('/find', 'Api\Pages\PagesController@find');
Route::get('/{id}', 'Api\Pages\PagesController@show'); Route::get('/{id}', 'Api\Pages\PagesController@show');
Route::group(['middleware' => ['auth:api']], function() { 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'); Route::delete('/{id}', 'Api\Pages\PagesController@destroy');
}); });
}); });