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', + ]); + } });