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 {
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 {

View File

@ -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();
}
}

View File

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

View File

@ -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();
}
}

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' => 'Адрес',
'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();
}

View File

@ -4,6 +4,7 @@ namespace App\Services\Forms\Pages;
class PageFormsServices {
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);
} 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;
}

View File

@ -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;
}

View File

@ -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() {

View File

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

View File

@ -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');
});
});