Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unique pages #61

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NL at end.

**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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Colon at end.


```html
<li><a href="{{ url(config('backpack.base.route_prefix').'/unique/about-us') }}"><i class="fa fa-file-o"></i> <span>Pages</span></a></li>
```

**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:
Expand Down
2 changes: 2 additions & 0 deletions src/PageManagerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 6 additions & 17 deletions src/app/Http/Controllers/Admin/PageCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@

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;

class PageCrudController extends CrudController
{
use PageTemplates;
use TraitReflections;

public function setup($template_name = false)
{
parent::__construct();

$modelClass = config('backpack.pagemanager.page_model_class', 'Backpack\PageManager\app\Models\Page');

$this->checkForTemplatesAndUniquePagesNotDistinct();

/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
Expand All @@ -27,6 +31,8 @@ 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 = collect($this->getTemplates())->pluck('name');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we're stuck with this colllect(...) here.

It would read better as $this->getTemplateNames() though.

$this->crud->addClause('whereIn', 'template', $template_names);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NL after.

/*
|--------------------------------------------------------------------------
| COLUMNS
Expand Down Expand Up @@ -179,23 +185,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.
*
Expand Down
190 changes: 190 additions & 0 deletions src/app/Http/Controllers/Admin/UniquePageCrudController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php

namespace Backpack\PageManager\app\Http\Controllers\Admin;

use App\UniquePages;
use Backpack\PageManager\app\TraitReflections;
use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\CRUD\app\Http\Controllers\CrudFeatures\SaveActions;

class UniquePageCrudController extends CrudController
{
use SaveActions;
use UniquePages;
use TraitReflections;

public function setup()
{
parent::__construct();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the constructor called from setup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point... this was just a copy&paste from the original controller...


$modelClass = config('backpack.pagemanager.unique_page_model_class', 'Backpack\PageManager\app\Models\Page');

$this->checkForTemplatesAndUniquePagesNotDistinct();

/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
|--------------------------------------------------------------------------
*/
$this->crud->setModel($modelClass);
// don't set route or entity names here. these depend on the page you are editing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't set route or entity names here. These depend on the page you are editing.


// unique pages can not be created nor deleted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot is one word 😺

$this->crud->denyAccess('create');
$this->crud->denyAccess('delete');

if (config('backpack.pagemanager.unique_page_revisions')) {
$this->crud->allowAccess('revisions');
}
}

/**
* As we want to edit pages by slug we need a new edit function.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a good description - that's a comment.

*
* @param string $slug the page slug
* @return Response
*/
public function uniqueEdit($slug)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string $slug

{
$model = $this->crud->model;
$entry = $model::findBySlug($slug);

if (! $entry) {
$entry = $this->createMissingPage($slug);
}

$this->uniqueSetup($entry);

return parent::edit($entry->id);
}

public function update($slug, $id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well have PHP doc.

{
$this->setRoute($slug);

return parent::updateCrud();
}

public function setRoute($slug)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And PHP Doc.

{
$this->crud->setRoute(config('backpack.base.route_prefix').'/unique/'.$slug);
}

/**
* Populate the update form with basic fields, that all pages need.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the comma.

*
* @param Model $page the page entity
*/
public function addDefaultPageFields($page)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Model $page maybe?

{
$this->crud->addField([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than a series of addField calls (badly spaced out with NLs) why not

$fields = // [ array of arrays ]; 
$this->crud->addFields($fields);

'name' => 'template',
'type' => 'hidden',
]);
$this->crud->addField([
'name' => 'name',
'type' => 'hidden',
]);
$this->crud->addField([
'name' => 'title',
'type' => 'hidden',
]);
$this->crud->addField([
'name' => 'slug',
'type' => 'hidden',
]);

$this->crud->addField([
'name' => 'open',
'type' => 'custom_html',
'value' => $this->buttons($page),
]);
}

public function buttons($page)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc.

{
$openButton = $page->getOpenButton();
$revisionsButton = view('crud::buttons.revisions', ['crud' => $this->crud, 'entry' => $page]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, if there is no revision setting thingo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the revisions button checks itself for $crud->hasAccess('revisions') I would not check for it here.
It's implemented like this (for the rows in the list.blade) in the standard crud I think.


return $openButton.' '.$revisionsButton;
}

public function createMissingPage($slug)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc.

{
$pages = collect($this->getUniquePages());

$slugs = $pages->mapWithKeys(function ($page) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->getUniqueSlugs() or something is more semantic.

return [str_slug($page->name) => $page->name];
});

if (! $page = $slugs->pull($slug)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errk, the old assign and check trick. It works but :(.

abort(404);
}

$model = $this->crud->model;

return $model::create([
'template' => $page,
'name' => camel_case($page),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And let's hard code the way to make names and titles ... the rest of the package probably does but what if I really want my names to be lIkE ThIs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createMissingPage function was more of a development fallback if currently the page is missing. Beside this I made name and title visible and editable in the edit form.

'title' => camel_case($page),
'slug' => $slug,
]);
}

public function uniqueRevisions($slug, $id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc!

{
$model = $this->crud->model;
$entry = $model::findBySlugOrFail($slug);

$this->uniqueSetup($entry);

return parent::listRevisions($entry->id);
}

public function restoreUniqueRevision($slug, $id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc.

{
$model = $this->crud->model;
$entry = $model::findBySlugOrFail($slug);

$this->uniqueSetup($entry);

return parent::restoreRevision($id);
}

protected function uniqueSetup($entry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc.

{
$this->setRoute($entry->slug);

$this->addDefaultPageFields($entry);
$this->crud->setEntityNameStrings($this->crud->makeLabel($entry->template), '');

$this->{$entry->template}();
}

/*
|--------------------------------------------------------------------------
| SaveActions overrides
|--------------------------------------------------------------------------
*/

/**
* Overrides trait version to remove.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and has no @return in the PHP doc.

This might cause some grief if someone has overridden these themselves.

*/
public function getSaveAction()
{
$saveCurrent = [
'value' => $this->getSaveActionButtonName('save_and_back'),
'label' => $this->getSaveActionButtonName('save_and_back'),
];

return [
'active' => $saveCurrent,
'options' => [],
];
}

public function setSaveAction($forceSaveAction = null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc.

BTW what does this do if it's not there?

{
// do nothing to preserve session value for other crud
}
}
10 changes: 0 additions & 10 deletions src/app/PageTemplates.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,4 @@ private function services()
'placeholder' => trans('backpack::pagemanager.content_placeholder'),
]);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be part of the PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just moved about_us to the UniquePages trait.

As these traits get published and need to be edited by developers, this serves as an example how to define your templates/uniquePages.


private function about_us()
{
$this->crud->addField([
'name' => 'content',
'label' => trans('backpack::pagemanager.content'),
'type' => 'wysiwyg',
'placeholder' => trans('backpack::pagemanager.content_placeholder'),
]);
}
}
48 changes: 48 additions & 0 deletions src/app/TraitReflections.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Backpack\PageManager\app;

trait TraitReflections
{
public function checkForTemplatesAndUniquePagesNotDistinct()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHP Doc!

{
if (config('backpack.pagemanager.page_model_class') != config('backpack.pagemanager.unique_page_model_class')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!==

return;
}

$uniquePages = collect($this->getUniquePages())->pluck('name');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sigh. It looks like all these getUniquePages and getTemplates things should just return collections...

$templates = collect($this->getTemplates())->pluck('name');

if ($uniquePages->intersect($templates)->isNotEmpty()) {
throw new \Exception('Templates and unique pages should not have the same function names when same model class is used.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it sounds like MUST not SHOULD 😈

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the new PHPDoc for reasons :)

}
}

/**
* Get all defined unique pages.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...without a @return.

*/
public function getUniquePages()
{
$pages_trait = new \ReflectionClass('App\UniquePages');
$pages = $pages_trait->getMethods(\ReflectionMethod::IS_PRIVATE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the private methods? That's odd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well that's exactly how the current page templates work at the moment...

All private methods in the Trait are defining one single template (or here on unique page)


return $pages;
}

/**
* Get all defined templates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No @return.

*/
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;
}
}
31 changes: 31 additions & 0 deletions src/app/UniquePages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App;

trait UniquePages
{
/*
|--------------------------------------------------------------------------
| Unique pages for Backpack\PageManager
|--------------------------------------------------------------------------
|
| Each unique page has its own method, that define what fields should show up using the Backpack\CRUD API.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No comma here!

| Use snake_case for naming and PageManager will generate the page on first edit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh? What if it's not snake_case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a convention I just copied from default page templates behaviour.

|
| Any fields defined here will show up after the standard page fields:
| - select template (hidden and fix)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "(hidden and fix)" mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improved the comment

| - page name (only seen by admins) (hidden and fix)
| - page title
| - page slug (hidden and fix, slug of method name)
*/

private function about_us()
{
$this->crud->addField([
'name' => 'content',
'label' => trans('backpack::pagemanager.content'),
'type' => 'wysiwyg',
'placeholder' => trans('backpack::pagemanager.content_placeholder'),
]);
}
}
Loading