diff --git a/README.md b/README.md
index c324f0c..aa49e9b 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,28 @@ php artisan migrate
1. Go to **yourapp/admin/page** and see how it works.
2. Define your own templates in app/PageTemplates.php using the Backpack\CRUD API.
+## Unique pages usage
+
+Unique pages are pages that exist only once. You can not create a second instance nor delete the current one.
+
+**Only editing**
+
+Each unique page is defined like a page template in app/UniquePages.php using the backpack\CRUD API.
+It will be available for editing in the backend at
+`< route_prefix >/unique/< page_function_slug >`
+
+For users to access the editing page for the page `about_us` you could add a menu item like so
+(remember the url will use the slug of your function):
+
+```html
+
Pages
+```
+
+**Unique pages can easily use revisions**
+
+Be sure to subclass `Backpack\PageManager\app\Models\Page` and follow the docs on setting up revisions for your CRUD.
+After that just set the config value `unique_page_revisions` to `true`and you are ready to go.
+
## Example front-end
No front-end is provided (Backpack only takes care of the admin panel), but for most projects this front-end code will be all you need:
diff --git a/src/PageManagerServiceProvider.php b/src/PageManagerServiceProvider.php
index 39036ee..0a2e464 100644
--- a/src/PageManagerServiceProvider.php
+++ b/src/PageManagerServiceProvider.php
@@ -33,6 +33,8 @@ public function boot()
$this->publishes([__DIR__.'/resources/views' => base_path('resources/views')], 'views');
// publish PageTemplates trait
$this->publishes([__DIR__.'/app/PageTemplates.php' => app_path('PageTemplates.php')], 'trait');
+ // publish UniquePages trait
+ $this->publishes([__DIR__.'/app/UniquePages.php' => app_path('UniquePages.php')], 'trait');
// publish migrations
$this->publishes([__DIR__.'/database/migrations' => database_path('migrations')], 'migrations');
// public config
diff --git a/src/app/Http/Controllers/Admin/PageCrudController.php b/src/app/Http/Controllers/Admin/PageCrudController.php
index aab7160..3b8d548 100644
--- a/src/app/Http/Controllers/Admin/PageCrudController.php
+++ b/src/app/Http/Controllers/Admin/PageCrudController.php
@@ -4,6 +4,7 @@
use App\PageTemplates;
// VALIDATION: change the requests to match your own file names if you need form validation
+use Backpack\PageManager\app\TraitReflections;
use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\PageManager\app\Http\Requests\PageRequest as StoreRequest;
use Backpack\PageManager\app\Http\Requests\PageRequest as UpdateRequest;
@@ -11,6 +12,7 @@
class PageCrudController extends CrudController
{
use PageTemplates;
+ use TraitReflections;
public function setup($template_name = false)
{
@@ -18,6 +20,8 @@ public function setup($template_name = false)
$modelClass = config('backpack.pagemanager.page_model_class', 'Backpack\PageManager\app\Models\Page');
+ $this->checkForTemplatesAndUniquePagesNotDistinct();
+
/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
@@ -27,6 +31,9 @@ public function setup($template_name = false)
$this->crud->setRoute(config('backpack.base.route_prefix').'/page');
$this->crud->setEntityNameStrings(trans('backpack::pagemanager.page'), trans('backpack::pagemanager.pages'));
+ $template_names = $this->getTemplateNames();
+ $this->crud->addClause('whereIn', 'template', $template_names);
+
/*
|--------------------------------------------------------------------------
| COLUMNS
@@ -179,23 +186,6 @@ public function useTemplate($template_name = false)
}
}
- /**
- * Get all defined templates.
- */
- public function getTemplates($template_name = false)
- {
- $templates_array = [];
-
- $templates_trait = new \ReflectionClass('App\PageTemplates');
- $templates = $templates_trait->getMethods(\ReflectionMethod::IS_PRIVATE);
-
- if (! count($templates)) {
- abort(503, trans('backpack::pagemanager.template_not_found'));
- }
-
- return $templates;
- }
-
/**
* Get all defined template as an array.
*
diff --git a/src/app/Http/Controllers/Admin/UniquePageCrudController.php b/src/app/Http/Controllers/Admin/UniquePageCrudController.php
new file mode 100644
index 0000000..8166fd8
--- /dev/null
+++ b/src/app/Http/Controllers/Admin/UniquePageCrudController.php
@@ -0,0 +1,247 @@
+checkForTemplatesAndUniquePagesNotDistinct();
+
+ /*
+ |--------------------------------------------------------------------------
+ | BASIC CRUD INFORMATION
+ |--------------------------------------------------------------------------
+ */
+ $this->crud->setModel($modelClass);
+ // Don't set route or entity names here. These depend on the page you are editing
+
+ $this->crud->denyAccess(['list', 'create', 'delete']);
+
+ if (config('backpack.pagemanager.unique_page_revisions')) {
+ $this->crud->allowAccess('revisions');
+ }
+ }
+
+ /**
+ * Edit the unique page retrieved by slug.
+ *
+ * @param string $slug
+ * @return Response
+ */
+ public function uniqueEdit($slug)
+ {
+ $model = $this->crud->model;
+ $entry = $model::findBySlug($slug);
+
+ if (! $entry) {
+ $entry = $this->createMissingPage($slug);
+ }
+
+ $this->uniqueSetup($entry);
+
+ return parent::edit($entry->id);
+ }
+
+ /**
+ * Update the unique page.
+ *
+ * @param string $slug
+ * @param int $id
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function update($slug, $id)
+ {
+ $model = $this->crud->model;
+ $entry = $model::findBySlugOrFail($slug);
+
+ $this->uniqueSetup($entry);
+
+ return parent::updateCrud();
+ }
+
+ /**
+ * Set the crud route.
+ *
+ * @param $slug
+ */
+ public function setRoute($slug)
+ {
+ $this->crud->setRoute(config('backpack.base.route_prefix').'/unique/'.$slug);
+ }
+
+ /**
+ * Populate the update form with basic fields that all pages need.
+ *
+ * @param Model $page
+ */
+ public function addDefaultPageFields($page)
+ {
+ $fields = [
+ [
+ 'name' => 'buttons',
+ 'type' => 'custom_html',
+ 'value' => $this->buttons($page),
+ ],
+ [
+ 'name' => 'template',
+ 'type' => 'hidden',
+ ],
+ [
+ 'name' => 'name',
+ ],
+ [
+ 'name' => 'title',
+ ],
+ [
+ 'name' => 'slug',
+ 'type' => 'hidden',
+ ],
+ ];
+
+ $this->crud->addFields($fields);
+ }
+
+ /**
+ * Build the buttons html for the edit form.
+ *
+ * @param Model $page
+ * @return string
+ */
+ public function buttons($page)
+ {
+ $openButton = $page->getOpenButton();
+ $revisionsButton = view('crud::buttons.revisions', ['crud' => $this->crud, 'entry' => $page]);
+
+ return $openButton.' '.$revisionsButton;
+ }
+
+ /**
+ * Create missing unique page by slug.
+ *
+ * @param $slug
+ * @return mixed
+ */
+ public function createMissingPage($slug)
+ {
+ $slugs = $this->getUniqueSlugs();
+
+ if (! $slugs->has($slug)) {
+ abort(404);
+ }
+
+ $page = $slugs->pull($slug);
+ $model = $this->crud->model;
+
+ return $model::create([
+ 'template' => $page,
+ 'name' => camel_case($page),
+ 'title' => camel_case($page),
+ 'slug' => $slug,
+ ]);
+ }
+
+ /**
+ * Display the revisions for specified resource.
+ *
+ * @param $slug
+ * @param $id
+ * @return Response
+ */
+ public function uniqueRevisions($slug, $id)
+ {
+ $model = $this->crud->model;
+ $entry = $model::findBySlugOrFail($slug);
+
+ $this->uniqueSetup($entry);
+
+ return parent::listRevisions($entry->id);
+ }
+
+ /**
+ * Restore a specific revision for the specified resource.
+ *
+ * Used via AJAX in the revisions view
+ *
+ * @param string $slug
+ * @param int $id
+ *
+ * @return JSON Response containing the new revision that was created from the update
+ * @return HTTP 500 if the request did not contain the revision ID
+ */
+ public function restoreUniqueRevision($slug, $id)
+ {
+ $model = $this->crud->model;
+ $entry = $model::findBySlugOrFail($slug);
+
+ $this->uniqueSetup($entry);
+
+ return parent::restoreRevision($id);
+ }
+
+ /**
+ * Setup the controller for the entry.
+ *
+ * @param $entry
+ */
+ protected function uniqueSetup($entry)
+ {
+ $this->crud->entry = $entry;
+
+ $this->setRoute($entry->slug);
+
+ $this->addDefaultPageFields($entry);
+ $this->crud->setEntityNameStrings($this->crud->makeLabel($entry->template), $this->crud->makeLabel($entry->template));
+
+ $this->{$entry->template}();
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | SaveActions overrides
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Overrides trait version to remove 'save_and_back' and 'save_and_new'.
+ *
+ * @return [type] [description]
+ */
+ public function getSaveAction()
+ {
+ $saveCurrent = [
+ 'value' => $this->getSaveActionButtonName('save_and_back'),
+ 'label' => $this->getSaveActionButtonName('save_and_back'),
+ ];
+
+ return [
+ 'active' => $saveCurrent,
+ 'options' => [],
+ ];
+ }
+
+ /**
+ * Override trait version to not update the session variable.
+ * This way we preserve the user chosen save action and don't overwrite with.
+ *
+ * @param [type] $forceSaveAction [description]
+ */
+ public function setSaveAction($forceSaveAction = null)
+ {
+ // do nothing to preserve session value for other crud
+ }
+}
diff --git a/src/app/PageTemplates.php b/src/app/PageTemplates.php
index d13501c..d1cb5cd 100644
--- a/src/app/PageTemplates.php
+++ b/src/app/PageTemplates.php
@@ -58,14 +58,4 @@ private function services()
'placeholder' => trans('backpack::pagemanager.content_placeholder'),
]);
}
-
- private function about_us()
- {
- $this->crud->addField([
- 'name' => 'content',
- 'label' => trans('backpack::pagemanager.content'),
- 'type' => 'wysiwyg',
- 'placeholder' => trans('backpack::pagemanager.content_placeholder'),
- ]);
- }
}
diff --git a/src/app/TraitReflections.php b/src/app/TraitReflections.php
new file mode 100644
index 0000000..e823294
--- /dev/null
+++ b/src/app/TraitReflections.php
@@ -0,0 +1,135 @@
+loadUniquePages()->pluck('name');
+ // use the internal function directly to prevent an 503 abort if no templates are defined
+ $templates = $this->loadTemplates()->pluck('name');
+
+ if ($uniquePages->intersect($templates)->isNotEmpty()) {
+ throw new \Exception('Templates and unique pages must not have the same function names when same model class is used.');
+ }
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | UNIQUE PAGES
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Load all defined unique pages.
+ *
+ * @return Collection
+ */
+ private function loadUniquePages()
+ {
+ $pages_trait = new \ReflectionClass('App\UniquePages');
+ $pages = $pages_trait->getMethods(\ReflectionMethod::IS_PRIVATE);
+
+ return collect($pages);
+ }
+
+ /**
+ * Get all defined unique pages.
+ *
+ * @return Collection
+ */
+ public function getUniquePages()
+ {
+ $pages = $this->loadUniquePages();
+
+ if (! count($pages)) {
+ abort(503, trans('backpack::pagemanager.template_not_found'));
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Get all defined unique page names.
+ *
+ * @return Collection
+ */
+ public function getUniquePageNames()
+ {
+ return $this->getUniquePages()->pluck('name');
+ }
+
+ /**
+ * Get the page names keyed with slugs.
+ *
+ * @return Collection
+ */
+ public function getUniqueSlugs()
+ {
+ return $this->getUniquePageNames()->mapWithKeys(function ($name) {
+ return [str_slug($name) => $name];
+ });
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | TEMPLATES
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Load all defined templates.
+ *
+ * @return Collection
+ */
+ private function loadTemplates()
+ {
+ $templates_trait = new \ReflectionClass('App\PageTemplates');
+ $templates = $templates_trait->getMethods(\ReflectionMethod::IS_PRIVATE);
+
+ return collect($templates);
+ }
+
+ /**
+ * Get all defined templates.
+ *
+ * @return Collection
+ */
+ public function getTemplates($template_name = false)
+ {
+ $templates = $this->loadTemplates();
+
+ if (! count($templates)) {
+ abort(503, trans('backpack::pagemanager.template_not_found'));
+ }
+
+ return collect($templates);
+ }
+
+ /**
+ * Get all defined template names.
+ *
+ * @return Collection
+ */
+ public function getTemplateNames()
+ {
+ return $this->getTemplates()->pluck('name');
+ }
+}
diff --git a/src/app/UniquePages.php b/src/app/UniquePages.php
new file mode 100644
index 0000000..2a66140
--- /dev/null
+++ b/src/app/UniquePages.php
@@ -0,0 +1,31 @@
+crud->addField([
+ 'name' => 'content',
+ 'label' => trans('backpack::pagemanager.content'),
+ 'type' => 'wysiwyg',
+ 'placeholder' => trans('backpack::pagemanager.content_placeholder'),
+ ]);
+ }
+}
diff --git a/src/config/pagemanager.php b/src/config/pagemanager.php
index 39c7189..b9dedb4 100644
--- a/src/config/pagemanager.php
+++ b/src/config/pagemanager.php
@@ -2,8 +2,20 @@
return [
// Change this class if you wish to extend PageCrudController
- 'admin_controller_class' => 'Backpack\PageManager\app\Http\Controllers\Admin\PageCrudController',
+ 'admin_controller_class' => 'Backpack\PageManager\app\Http\Controllers\Admin\PageCrudController',
// Change this class if you wish to extend the Page model
- 'page_model_class' => 'Backpack\PageManager\app\Models\Page',
+ // remember to change 'unique_page_model_class' if you want to use the same model
+ 'page_model_class' => 'Backpack\PageManager\app\Models\Page',
+
+ // Change this class if you wish to extend UniquePageCrudController
+ 'unique_admin_controller_class' => 'Backpack\PageManager\app\Http\Controllers\Admin\UniquePageCrudController',
+
+ // Change this class if you wish to extend the Page model for unique pages
+ 'unique_page_model_class' => 'Backpack\PageManager\app\Models\Page',
+
+ // Set to true if you want to use revisions on unique pages
+ // This requires you to extend the class you use for unique pages
+ // For setup details please check the backpack documentation on revisions
+ 'unique_page_revisions' => false,
];
diff --git a/src/routes/backpack/pagemanager.php b/src/routes/backpack/pagemanager.php
index c196ab5..57074fe 100644
--- a/src/routes/backpack/pagemanager.php
+++ b/src/routes/backpack/pagemanager.php
@@ -37,4 +37,21 @@
]);
Route::resource('page', $controller);
+
+ // Backpack\PageManager routes for unique pages
+ $uniqueController = config('backpack.pagemanager.unique_admin_controller_class', 'Backpack\PageManager\app\Http\Controllers\Admin\UniquePageCrudController');
+
+ Route::get('unique/{slug}', $uniqueController.'@uniqueEdit');
+ Route::put('unique/{slug}/{id}', $uniqueController.'@update');
+
+ if (config('backpack.pagemanager.unique_page_revisions')) {
+ Route::get('unique/{slug}/{id}/revisions', [
+ 'as' => 'crud.unique.listRevisions',
+ 'uses' => $uniqueController.'@uniqueRevisions',
+ ]);
+ Route::post('unique/{slug}/{id}/revisions/{revisionId}/restore', [
+ 'as' => 'crud.unique.restoreRevision',
+ 'uses' => $uniqueController.'@restoreUniqueRevision',
+ ]);
+ }
});