belongsTo(Page::class, 'parent_id'); } public function children(): HasMany { return $this->hasMany(Page::class, 'parent_id')->orderBy('ord'); } public function siblings(): HasMany { return $this->hasMany(Page::class, 'parent_id', 'parent_id')->where('id', '!=', $this->id); } public function picture(): BelongsTo { return $this->belongsTo(Asset::class); } public function sections(): MorphToMany { return $this->objects()->wherePivot('group', '=', 'sections'); } public function sidebars(): MorphToMany { return $this->objects()->wherePivot('group', '=', 'sidebars'); } public function publications(): HasMany { return $this->hasMany(Publication::class); } public function registries(): HasMany { return $this->hasMany(Registry::class); } public function scopeBySlug($query, $slug) { $query->where(['slug' => $slug]); } public function scopeNthParentSlug($query, $nth, $slug) { $query->whereHas(implode('.', array_fill(0, $nth, 'parent')), function($query) use($slug) { $query->bySlug($slug); }); } public function getLinkAttribute(): string { return '/' . $this->parents->reverse()->push($this)->pluck('slug')->filter(function($v) {return $v;})->implode('/'); } public function getParentsAttribute(): Collection { $page = $this; $result = collect([]); while ($page = $page->parent) $result->push($page); return $result; } public function getParsedTypeAttribute(): array { return ['name' => $this->type, 'title' => PageType::TITLES[$this->type] ?? null]; } public function getRegistryAttribute(): Model { return $this->registries()->firstOrCreate(); } public function isEditable(User $user): bool { return $user->isModerator && $user->membership()->whereHas('objects', function($query) { Field::applyFilters($query, collect(['types' => 'company-member-properties', 'moderate-pages' => $this->parents->add($this)->pluck('uuid')->all()])); })->exists(); } public function addSection($typeName, $ord = null): ?Model { return $this->createObject($typeName, $ord, 'sections'); } public function addSidebar($typeName = 'page-sidebar', $ord = null): ?Model { return $this->createObject($typeName, $ord, 'sidebars'); } 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; } public function move($ord, ?Page $parent = null) { $prevParent = $this->parent()->withTrashed()->first(); if (($parent->id ?? 0) === ($prevParent->id ?? 0)) { ($ord > $this->ord) ? $this->moveSet('backward', $this->ord, $ord, $parent) : $this->moveSet('forward', $ord, $this->ord, $parent); } 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])->withTrashed()->orderBy('ord'); if ($ordFrom !== null) $query->where('ord', '>=', $ordFrom); if ($ordTo !== null) $query->where('ord', '<=', $ordTo); $query->get()->each(function($page) use($dir) { $page->update(['ord' => ($dir === 'forward') ? ($page->ord + 1) : ($page->ord - 1)]); }); } public function trimIndexes($parentIds) { collect(is_array($parentIds) ? $parentIds : [$parentIds])->unique()->each(function($parentId) { Page::query()->where(['parent_id' => $parentId])->withTrashed()->orderBy('ord')->orderBy('id')->get()->each(function($page, $index) { if ($page->ord !== $index) $page->update(['ord' => $index]); }); }); } public function getMaxOrd(): int { $res = $this->siblings()->withTrashed()->max('ord'); return ($res !== null) ? ($res + 1) : 0; } 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(); } }