* Clear Live, Draft and Homepage indicators.
* Direct Edit page, Edit content and View page actions.
* Page editor divided into:
* Page details
* SEO and URL
* Publishing
* Automatic, unique URL slugs.
* Publish and expiry scheduling.
* Page templates: Default, Landing and Full Width.
* Homepage replacement handled automatically.
* Current homepage protected from accidental deletion.
* Page duplication includes its complete content structure.
* Content composer now works from the actual page record and page ID.
* Clearer section-based terminology for editors.
* Reusable content remains available as an advanced content library rather than dominating the workflow.
The package contains the eight complete changed files, preserving their project paths. It is not a patch collection.
152 lines
5.8 KiB
PHP
152 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace Modules\Pages\Http\Controllers;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\View\View;
|
|
use Modules\Content\Models\PageContent;
|
|
use Modules\Pages\Models\Page;
|
|
|
|
final class PageController extends Controller
|
|
{
|
|
public function index(Request $request): View
|
|
{
|
|
$records = Page::query()
|
|
->withCount('contentPlacements')
|
|
->when($request->string('search')->isNotEmpty(), function ($query) use ($request): void {
|
|
$search = $request->string('search')->toString();
|
|
$query->where(fn ($page) => $page
|
|
->where('title', 'like', "%{$search}%")
|
|
->orWhere('slug', 'like', "%{$search}%")
|
|
->orWhere('summary', 'like', "%{$search}%"));
|
|
})
|
|
->when($request->string('status')->isNotEmpty(), fn ($query) => $query->where('status', $request->string('status')->toString()))
|
|
->orderByDesc('is_home')
|
|
->orderBy('title')
|
|
->paginate(24)
|
|
->withQueryString();
|
|
|
|
return view('pages::admin.index', compact('records'));
|
|
}
|
|
|
|
public function create(): View
|
|
{
|
|
return view('pages::admin.form', ['record' => new Page(['status' => 'draft', 'template' => 'default'])]);
|
|
}
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
{
|
|
$data = $this->validated($request);
|
|
$data['slug'] = $this->uniqueSlug($data['slug'] ?: $data['title']);
|
|
$data['is_home'] = $request->boolean('is_home');
|
|
$data['published_at'] = $data['status'] === 'published' ? ($data['published_at'] ?? now()) : null;
|
|
|
|
$page = Page::query()->create($data);
|
|
$this->enforceSingleHomepage($page);
|
|
|
|
return redirect()->route('administration.pages.edit', $page)->with('status', 'Page created. Add content sections when you are ready.');
|
|
}
|
|
|
|
public function edit(Page $page): View
|
|
{
|
|
$page->loadCount('contentPlacements');
|
|
|
|
return view('pages::admin.form', ['record' => $page]);
|
|
}
|
|
|
|
public function update(Request $request, Page $page): RedirectResponse
|
|
{
|
|
$oldSlug = $page->slug;
|
|
$data = $this->validated($request, $page);
|
|
$data['slug'] = $this->uniqueSlug($data['slug'] ?: $data['title'], $page);
|
|
$data['is_home'] = $request->boolean('is_home');
|
|
$data['published_at'] = $data['status'] === 'published' ? ($data['published_at'] ?? $page->published_at ?? now()) : null;
|
|
|
|
$page->update($data);
|
|
$this->enforceSingleHomepage($page);
|
|
|
|
if ($oldSlug !== $page->slug) {
|
|
PageContent::query()->where('page_id', $page->id)->orWhere('page_key', $oldSlug)->update(['page_key' => $page->slug, 'page_id' => $page->id]);
|
|
}
|
|
|
|
return back()->with('status', 'Page saved.');
|
|
}
|
|
|
|
public function duplicate(Request $request, Page $page): RedirectResponse
|
|
{
|
|
$copy = $page->replicate(['slug', 'status', 'published_at', 'preview_token', 'locked_by', 'locked_at', 'is_home']);
|
|
$copy->title = $page->title.' copy';
|
|
$copy->slug = $this->uniqueSlug($copy->title);
|
|
$copy->status = 'draft';
|
|
$copy->published_at = null;
|
|
$copy->is_home = false;
|
|
$copy->save();
|
|
|
|
foreach ($page->contentPlacements()->orderBy('region')->orderBy('sort_order')->get() as $placement) {
|
|
$copy->contentPlacements()->create([
|
|
'page_key' => $copy->slug,
|
|
'content_document_id' => $placement->content_document_id,
|
|
'region' => $placement->region,
|
|
'sort_order' => $placement->sort_order,
|
|
'is_enabled' => $placement->is_enabled,
|
|
'visibility_rules' => $placement->visibility_rules,
|
|
'presentation' => $placement->presentation,
|
|
'starts_at' => $placement->starts_at,
|
|
'ends_at' => $placement->ends_at,
|
|
]);
|
|
}
|
|
|
|
return redirect()->route('administration.pages.edit', $copy)->with('status', 'Draft copy created with the same content structure.');
|
|
}
|
|
|
|
public function destroy(Page $page): RedirectResponse
|
|
{
|
|
if ($page->is_home) {
|
|
return back()->withErrors(['page' => 'Choose another homepage before deleting the current homepage.']);
|
|
}
|
|
|
|
$page->delete();
|
|
|
|
return redirect()->route('administration.pages.index')->with('status', 'Page moved to the bin.');
|
|
}
|
|
|
|
private function validated(Request $request, ?Page $page = null): array
|
|
{
|
|
return $request->validate([
|
|
'title' => ['required', 'string', 'max:255'],
|
|
'slug' => ['nullable', 'string', 'max:255', Rule::unique('pages', 'slug')->ignore($page?->id)->whereNull('deleted_at')],
|
|
'summary' => ['nullable', 'string', 'max:1000'],
|
|
'content' => ['nullable', 'string'],
|
|
'status' => ['required', Rule::in(['draft', 'published'])],
|
|
'template' => ['required', Rule::in(['default', 'landing', 'full-width'])],
|
|
'published_at' => ['nullable', 'date'],
|
|
'expires_at' => ['nullable', 'date', 'after:published_at'],
|
|
'is_home' => ['sometimes', 'boolean'],
|
|
]);
|
|
}
|
|
|
|
private function uniqueSlug(string $value, ?Page $except = null): string
|
|
{
|
|
$base = Str::slug($value) ?: 'page';
|
|
$slug = $base;
|
|
$suffix = 2;
|
|
|
|
while (Page::withTrashed()->where('slug', $slug)->when($except, fn ($query) => $query->whereKeyNot($except->getKey()))->exists()) {
|
|
$slug = $base.'-'.$suffix++;
|
|
}
|
|
|
|
return $slug;
|
|
}
|
|
|
|
private function enforceSingleHomepage(Page $page): void
|
|
{
|
|
if ($page->is_home) {
|
|
Page::query()->whereKeyNot($page->id)->where('is_home', true)->update(['is_home' => false]);
|
|
}
|
|
}
|
|
}
|