From 30bc99cfd7a260f31920cff4d95353e9287f7395 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 29 Jun 2018 12:27:01 -0400 Subject: [PATCH] Update ProcessPageList to have module configuration setting as to whether or not Trash and Restore is available to non-superusers. Plus add new Restore tab to ProcessPageEdit that appears when editing a page in the trash. Previously you could only restore by using the PageList "restore" action. --- wire/modules/PagePermissions.module | 80 ++++++++++--------- .../ProcessPageEdit/ProcessPageEdit.module | 62 +++++++++++++- .../ProcessPageList/ProcessPageList.module | 16 +++- .../ProcessPageListActions.php | 13 ++- .../ProcessPageList/ProcessPageListRender.php | 10 +++ .../ProcessPageListRenderJSON.php | 44 +--------- 6 files changed, 140 insertions(+), 85 deletions(-) diff --git a/wire/modules/PagePermissions.module b/wire/modules/PagePermissions.module index 45ac7968..5e879b48 100644 --- a/wire/modules/PagePermissions.module +++ b/wire/modules/PagePermissions.module @@ -607,44 +607,36 @@ class PagePermissions extends WireData implements Module { */ public function trashListable($page = null) { /** @var User $user */ - static $showTrashUsers = array(); $user = $this->wire('user'); + + // trash and anything in it always visible to superuser + if($user->isSuperuser()) return true; - if(isset($showTrashUsers[$user->id])) { - $showTrash = $showTrashUsers[$user->id]; + // determine if system has page-edit-trash-created permission installed + $petc = 'page-edit-trash-created'; + if(!$this->wire('permissions')->has($petc)) $petc = false; + + if($user->hasPermission('page-delete')) { + // has page-delete globally + } else if($petc && $user->hasPermission($petc)) { + // has page-edit-trash-created globally + } else if($user->hasPermission('page-delete', true)) { + // has page-delete added specifically at a template + } else if($petc && $user->hasPermission($petc, true)) { + // has page-edit-trash-created added specifically at a template } else { - $petc = 'page-edit-trash-created'; - if(!$this->wire('permissions')->has($petc)) $petc = false; - if($user->isSuperuser()) { - // superuser - $showTrash = true; - } else if($user->hasPermission('page-delete')) { - // has page-delete globally - $showTrash = true; - } else if($petc && $user->hasPermission($petc)) { - // has page-edit-trash-created globally - $showTrash = true; - } else if($user->hasPermission('page-delete', true)) { - // has page-delete added specifically at a template - $showTrash = true; - } else if($petc && $user->hasPermission($petc, true)) { - // has page-edit-trash-created added specifically at a template - $showTrash = true; - } else { - $showTrash = false; - } - $showTrashUsers[$user->id] = $showTrash; + // user does not have any of the permissions above, so trash is not listable + return false; } - if(!$showTrash) return false; - if($page === null || !$page->id) return $showTrash; - - $trashPageID = $this->wire('config')->trashPageID; - if($page->id == $trashPageID) return $showTrash; + // if request not asking about specific page, return general "trash is listable?" request + if($page === null || !$page->id) return true; - $listable = $this->pageEditable($page); - - return $listable; + // if request is for the actual Trash page, consider this to be a general request + if($page->id == $this->wire('config')->trashPageID) return true; + + // page is listable in the trash only if it is also editable + return $this->pageEditable($page); } /** @@ -859,14 +851,24 @@ class PagePermissions extends WireData implements Module { */ public function moveable($event) { /** @var Page $page */ - $page = $event->object; - $moveable = $page->editable('parent'); - if($moveable && count($event->arguments) && $event->arguments[0] instanceof Page) { - /** @var Page $parent */ - $parent = $event->arguments[0]; - $moveable = $parent->addable($page); + $page = $event->object; + + /** @var Page|null $parent */ + $parent = $event->arguments(0); + if(!$parent || !$parent instanceof Page || !$parent->id) $parent = null; + + if($page->id == 1) { + $moveable = false; + } else { + $moveable = $page->editable('parent'); } - if($page->id == 1) $moveable = false; + + if($moveable && $parent) { + $moveable = $parent->addable($page); + } else if($parent && $parent->isTrash() && $parent->id == $this->wire('config')->trashPageID) { + $moveable = $page->deletable(); + } + $event->return = $moveable; } diff --git a/wire/modules/Process/ProcessPageEdit/ProcessPageEdit.module b/wire/modules/Process/ProcessPageEdit/ProcessPageEdit.module index c458d4a4..ad03c3a3 100644 --- a/wire/modules/Process/ProcessPageEdit/ProcessPageEdit.module +++ b/wire/modules/Process/ProcessPageEdit/ProcessPageEdit.module @@ -893,7 +893,11 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod if($this->page->addable() || $this->page->numChildren) $form->append($this->buildFormChildren()); if(!$this->page->template->noSettings && $this->useSettings) $form->append($this->buildFormSettings()); - if($this->isTrash) $this->message($this->_("This page is in the Trash")); + if($this->isTrash && !$this->isPost) { + $this->message($this->_("This page is in the Trash")); + $tabRestore = $this->buildFormRestore(); + if($tabRestore) $form->append($tabRestore); + } $tabDelete = $this->buildFormDelete(); if($tabDelete->children()->count()) $form->append($tabDelete); if($this->page->viewable() && !$this->requestModal) $this->buildFormView($this->getViewUrl()); @@ -1427,6 +1431,47 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod return $wrapper; } + /** + * Build the 'restore' tab shown for pages in the trash + * + * Returns boolean false if restore not possible. + * + * @return InputfieldWrapper|bool + * + */ + protected function buildFormRestore() { + + if(!$this->page->isTrash()) return false; + /** @var InputfieldWrapper $wrapper */ + $wrapper = $this->wire(new InputfieldWrapper()); + $id = $this->className() . 'Restore'; + $restoreLabel = $this->_('Restore'); // Tab Label: Restore + $restoreLabel2 = $this->_('Move out of trash and restore to original location'); + $wrapper->attr('id', $id); + $wrapper->attr('title', $restoreLabel); + $this->addTab($id, $restoreLabel); + + if(!preg_match('/^(\d+)\.(\d+)\.(\d+)_(.+)$/', $this->page->name, $matches)) return false; + $parentID = (int) $matches[2]; + $parent = $parentID ? $this->wire('pages')->get($parentID) : null; + + if(!$parent || !$parent->id) return false; + + /** @var InputfieldCheckbox $field */ + $field = $this->modules->get('InputfieldCheckbox'); + $field->attr('id+name', 'restore_page'); + $field->attr('value', $this->page->id); + + $field->icon = 'trash-o'; + $field->label = $restoreLabel2; + $field->description = $this->_('Check the box to confirm that you want to restore this page.'); // Restore page confirmation instruction + $field->notes = sprintf($this->_('The page will be restored into parent: **%s**'), $parent->get('path')); + $field->label2 = $restoreLabel; + $wrapper->append($field); + + return $wrapper; + } + /** * Build the 'view' tab on the Page Edit form * @@ -1632,6 +1677,16 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod $message .= ' - ' . $this->_('Cannot be published until errors are corrected'); } } + + if($this->input->post('restore_page') && $this->page->isTrash() && $this->page->restorable()) { + if($formErrors) { + $this->warning($this->_('Page cannot be restored while errors are present')); + } else if($this->wire('pages')->restore($this->page, false)) { + $message = sprintf($this->_('Restored Page: %s'), '{path}') . $numChanges; + } else { + $this->warning($this->_('Error restoring page')); + } + } $this->wire('pages')->save($this->page, $options); $message = str_replace('{path}', $this->page->path, $message); @@ -1942,14 +1997,15 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod $afterDeleteRedirect = $this->config->urls->admin . "page/?open={$this->parent->id}"; if($this->wire('page')->process != $this->className()) $afterDeleteRedirect = "../"; + $pagePath = $this->page->path(); if(($this->isTrash || $this->page->template->noTrash) && $this->page->deleteable()) { - $this->session->message(sprintf($this->_('Deleted page: %s'), $this->page->url)); // Page deleted message + $this->session->message(sprintf($this->_('Deleted page: %s'), $pagePath)); // Page deleted message $this->pages->delete($this->page, true); $this->session->redirect($afterDeleteRedirect); } else if($this->pages->trash($this->page)) { - $this->session->message(sprintf($this->_('Moved page to trash: %s'), $this->page->url)); // Page moved to trash message + $this->session->message(sprintf($this->_('Moved page to trash: %s'), $pagePath)); // Page moved to trash message $this->session->redirect($afterDeleteRedirect); } else { diff --git a/wire/modules/Process/ProcessPageList/ProcessPageList.module b/wire/modules/Process/ProcessPageList/ProcessPageList.module index b979aaf6..546775cf 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageList.module +++ b/wire/modules/Process/ProcessPageList/ProcessPageList.module @@ -19,6 +19,7 @@ * @property int $hoverActionDelay Milliseconds delay between hover and showing of actions. * @property int $hoverActionFade Milliseconds to spend fading in or out actions. * @property bool|int $useBookmarks Allow use of PageList bookmarks? + * @property bool|int $useTrash Allow non-superusers to use Trash? * * @method string ajaxAction($action) * @method PageArray find($selectorString, Page $page) @@ -37,7 +38,7 @@ class ProcessPageList extends Process implements ConfigurableModule { return array( 'title' => 'Page List', 'summary' => 'List pages in a hierarchal tree structure', - 'version' => 120, + 'version' => 121, 'permanent' => true, 'permission' => 'page-edit', 'icon' => 'sitemap', @@ -113,6 +114,7 @@ class ProcessPageList extends Process implements ConfigurableModule { $this->set('limit', self::defaultLimit); $this->set('useHoverActions', false); $this->set('useBookmarks', false); + $this->set('useTrash', false); $this->set('bookmarks', array()); parent::__construct(); @@ -349,6 +351,7 @@ class ProcessPageList extends Process implements ConfigurableModule { $renderer->setLimit($limit); $renderer->setPageLabelField($this->getPageLabelField()); $renderer->setLabel('trash', $this->trashLabel); + $renderer->setUseTrash($this->useTrash || $this->wire('user')->isSuperuser()); return $renderer; } @@ -634,6 +637,17 @@ class ProcessPageList extends Process implements ConfigurableModule { $fields = $this->wire(new InputfieldWrapper()); /** @var Modules $modules */ $modules = $this->wire('modules'); + + /** @var InputfieldCheckbox $field */ + $field = $modules->get('InputfieldCheckbox'); + $field->attr('name', 'useTrash'); + $field->label = $this->_('Allow non-superuser editors to use Trash?'); + $field->icon = 'trash-o'; + $field->description = + $this->_('When checked, users will be able to see pages in the trash (only pages they have access to).') . ' ' . + $this->_('This will also enable the “Trash” and “Restore” actions, where access control allows.'); + if(!empty($data['useTrash'])) $field->attr('checked', 'checked'); + $fields->append($field); /** @var InputfieldText $field */ $field = $modules->get("InputfieldText"); diff --git a/wire/modules/Process/ProcessPageList/ProcessPageListActions.php b/wire/modules/Process/ProcessPageList/ProcessPageListActions.php index 57554d95..e0e20788 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageListActions.php +++ b/wire/modules/Process/ProcessPageList/ProcessPageListActions.php @@ -1,8 +1,15 @@ 'Edit', @@ -33,6 +40,10 @@ class ProcessPageListActions extends Wire { $this->actionLabels = array_merge($this->actionLabels, $actionLabels); } + public function setUseTrash($useTrash) { + $this->useTrash = (bool) $useTrash; + } + /** * Get an array of available Page actions, indexed by $label => $url * @@ -167,7 +178,7 @@ class ProcessPageListActions extends Wire { } } - $trashable = $page->trashable(); + $trashable = $this->useTrash && $page->trashable(); $trashIcon = " "; if($trashable && !$user->isSuperuser()) { // do not allow non-superuser ability to trash branches of pages, only individual pages diff --git a/wire/modules/Process/ProcessPageList/ProcessPageListRender.php b/wire/modules/Process/ProcessPageList/ProcessPageListRender.php index 4950f9a4..d4b192b2 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageListRender.php +++ b/wire/modules/Process/ProcessPageList/ProcessPageListRender.php @@ -19,6 +19,7 @@ abstract class ProcessPageListRender extends Wire { protected $superuser = false; protected $actions = null; protected $options = array(); + protected $useTrash = false; public function __construct(Page $page, PageArray $children) { $this->page = $page; @@ -66,6 +67,11 @@ abstract class ProcessPageListRender extends Wire { public function setLabel($key, $value) { $this->actionLabels[$key] = $value; } + + public function setUseTrash($useTrash) { + $this->useTrash = (bool) $useTrash; + $this->actions->setUseTrash($this->getUseTrash()); + } public function setPageLabelField($pageLabelField) { $this->pageLabelField = $pageLabelField; @@ -201,6 +207,10 @@ abstract class ProcessPageListRender extends Wire { public function getChildren() { return $this->children; } + + public function getUseTrash() { + return $this->useTrash; + } } diff --git a/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php b/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php index 5a5563e9..8f3e1670 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php +++ b/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php @@ -10,8 +10,6 @@ class ProcessPageListRenderJSON extends ProcessPageListRender { protected $systemIDs = array(); - protected $allowTrash = null; - public function __construct(Page $page, PageArray $children) { parent::__construct($page, $children); @@ -24,44 +22,6 @@ class ProcessPageListRenderJSON extends ProcessPageListRender { ); } - /** - * Are we allowed to display the Trash page? - * - * @return bool - * - */ - protected function allowTrash() { - - if($this->allowTrash !== null) return $this->allowTrash; - - /** @var User $user */ - $user = $this->wire('user'); - - $petc = 'page-edit-trash-created'; - if(!$this->wire('permissions')->has($petc)) $petc = false; - - if($user->isSuperuser()) { - // superuser - $this->allowTrash = true; - } else if($user->hasPermission('page-delete')) { - // has page-delete globally - $this->allowTrash = true; - } else if($petc && $user->hasPermission($petc)) { - // has page-edit-trash-created globally - $this->allowTrash = true; - } else if($user->hasPermission('page-delete', true)) { - // has page-delete added specifically at a template - $this->allowTrash = true; - } else if($petc && $user->hasPermission($petc, true)) { - // has page-edit-trash-created added specifically at a template - $this->allowTrash = true; - } else { - $this->allowTrash = false; - } - - return $this->allowTrash; - } - public function renderChild(Page $page) { $outputFormatting = $page->outputFormatting; @@ -173,7 +133,9 @@ class ProcessPageListRenderJSON extends ProcessPageListRender { if(!$this->superuser && $page404 && $page404->parent_id == 1 && !isset($extraPages[$idTrash])) { $pageTrash = $this->wire('pages')->get($idTrash); - if($pageTrash->id && $pageTrash->listable()) $extraPages[$pageTrash->id] = $pageTrash; + if($pageTrash->id && $this->getUseTrash() && $pageTrash->listable()) { + $extraPages[$pageTrash->id] = $pageTrash; + } } foreach($extraPages as $page) {