From d77b23adbb6fda6fbec70a31736437040f23c900 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 10 May 2024 11:52:41 -0400 Subject: [PATCH] Add a new $page->cloneable() method that returns true if the user is allowed to clone the page. Or use $page->cloneable(true) if the user is allowed to clone the page and its children together. This moves the logic was was previously in the ProcessPageClone module into a method that can be more widely used where needed. Also updated the ProcessPageClone module to use it. --- wire/core/Page.php | 4 +- wire/core/PageProperties.php | 3 +- wire/modules/PagePermissions.module | 66 +++++++++++++++++++- wire/modules/Process/ProcessPageClone.module | 21 +------ 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/wire/core/Page.php b/wire/core/Page.php index b6de0fc2..0177821f 100644 --- a/wire/core/Page.php +++ b/wire/core/Page.php @@ -8,7 +8,7 @@ * 1. Providing get/set access to the Page's properties * 2. Accessing the related hierarchy of pages (i.e. parents, children, sibling pages) * - * ProcessWire 3.x, Copyright 2022 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * * #pw-summary Class used by all Page objects in ProcessWire. @@ -120,6 +120,7 @@ * @method bool addable($pageToAdd = null) Returns true if the current user can add children to the page, false if not. Optionally specify the page to be added for additional access checking. #pw-group-access * @method bool moveable($newParent = null) Returns true if the current user can move this page. Optionally specify the new parent to check if the page is moveable to that parent. #pw-group-access * @method bool sortable() Returns true if the current user can change the sort order of the current page (within the same parent). #pw-group-access + * @method bool cloneable($recursive = null) Can current user clone this page? Specify false for $recursive argument to ignore whether children are cloneable. @since 3.0.239 #pw-group-access * @property bool $viewable #pw-group-access * @property bool $editable #pw-group-access * @property bool $publishable #pw-group-access @@ -130,6 +131,7 @@ * @property bool $moveable #pw-group-access * @property bool $sortable #pw-group-access * @property bool $listable #pw-group-access + * @property bool $cloneable @since 3.0.239 #pw-group-access * * Methods added by PagePathHistory.module (installed by default) * -------------------------------------------------------------- diff --git a/wire/core/PageProperties.php b/wire/core/PageProperties.php index bb625cfc..81cd7111 100644 --- a/wire/core/PageProperties.php +++ b/wire/core/PageProperties.php @@ -9,7 +9,7 @@ * Except where indicated, please treat these properties as private to the * Page class. * - * ProcessWire 3.x, Copyright 2021 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * */ @@ -75,6 +75,7 @@ abstract class PageProperties { 'addable' => 'm', 'child' => 'm', 'children' => 'm', + 'cloneable' => 'm', 'created' => 's', 'createdStr' => '', 'createdUser' => '', diff --git a/wire/modules/PagePermissions.module b/wire/modules/PagePermissions.module index d708f2e7..3ebca535 100644 --- a/wire/modules/PagePermissions.module +++ b/wire/modules/PagePermissions.module @@ -116,7 +116,8 @@ class PagePermissions extends WireData implements Module { $this->addHook('Page::restorable', $this, 'restorable'); $this->addHook('Page::addable', $this, 'addable'); $this->addHook('Page::moveable', $this, 'moveable'); - $this->addHook('Page::sortable', $this, 'sortable'); + $this->addHook('Page::sortable', $this, 'sortable'); + $this->addHook('Page::cloneable', $this, 'cloneable'); // $this->addHook('Page::fieldViewable', $this, 'hookFieldViewable'); // $this->addHook('Page::fieldEditable', $this, 'hookFieldEditable'); // $this->addHook('Template::createable', $this, 'createable'); @@ -1107,6 +1108,69 @@ class PagePermissions extends WireData implements Module { // if we made it here, then page is not publishable $event->return = false; } + + /** + * Is page cloneable by current user? + * + * Note: If a page has children and current user is not superuser, this method will return + * true only if the user has the `page-clone-tree` permission (from ProcessPageClone module). + * + * ~~~~~ + * if($page->cloneable()) { + * // page is cloneable + * } + * if($page->cloneable(false)) { + * // page is cloneable, ignoring if recursive clone of children is allowed + * } + * ~~~~~ + * + * @param HookEvent $event + * + */ + public function cloneable($event) { + + $page = $event->object; /** @var Page $page */ + $recursive = $event->arguments(0); /** @var bool|null $recursive */ + $user = $this->wire()->user; + $permissions = $this->wire()->permissions; + $parent = $page->parent(); + $parentTemplate = $parent->template; + $pageTemplate = $page->template; + $event->return = false; + + if(!$parentTemplate) return; + + if($page->hasStatus(Page::statusSystem) || $page->hasStatus(Page::statusSystemID)) return; + if($parentTemplate->noChildren) return; + if($pageTemplate->noParents) return; + + if(count($parentTemplate->childTemplates) && !in_array($pageTemplate->id, $parentTemplate->childTemplates)) return; + if(count($pageTemplate->parentTemplates) && !in_array($parentTemplate->id, $pageTemplate->parentTemplates)) return; + + if($user->isSuperuser()) { + $event->return = true; + return; + } + + if(!$user->hasPermission('page-create', $page)) return; + + if(!$parent->addable()) return; + + if($permissions->has('page-clone')) { + if(!$user->hasPermission('page-clone', $page)) return; + } + + if($recursive === false || !$page->numChildren) { + // call indicates they do not intend to clone children + // or there are no children, so no additional checks necessary + } else { + // check that they have page-clone-tree permission IF the page has children + // recursive cloning is only allowed via ProcessPageClone module + if(!$user->hasPermission('page-clone-tree', $page)) return; + } + + $event->return = true; + } /** * Returns true if given user has the optional language permission, or false if not diff --git a/wire/modules/Process/ProcessPageClone.module b/wire/modules/Process/ProcessPageClone.module index 95c0f780..80f9aaa0 100644 --- a/wire/modules/Process/ProcessPageClone.module +++ b/wire/modules/Process/ProcessPageClone.module @@ -150,24 +150,7 @@ class ProcessPageClone extends Process implements ConfigurableModule { * */ public function hasPermission(Page $page) { - $user = $this->wire()->user; - $parent = $page->parent(); - $parentTemplate = $parent->template; - $pageTemplate = $page->template; - - if(!$parentTemplate) return false; - - if($page->hasStatus(Page::statusSystem) || $page->hasStatus(Page::statusSystemID)) return false; - if($parentTemplate->noChildren) return false; - if($pageTemplate->noParents) return false; - - if(count($parentTemplate->childTemplates) && !in_array($pageTemplate->id, $parentTemplate->childTemplates)) return false; - if(count($pageTemplate->parentTemplates) && !in_array($parentTemplate->id, $pageTemplate->parentTemplates)) return false; - - if($user->isSuperuser()) return true; - if($user->hasPermission('page-create', $page) && $user->hasPermission('page-clone', $page) && $parent->addable()) return true; - - return false; + return $page->cloneable(false); } /** @@ -378,7 +361,7 @@ class ProcessPageClone extends Process implements ConfigurableModule { $titleField = $form->get('clone_page_title'); $nameField = $form->get('clone_page_name'); - $cloneTree = $input->post('clone_page_tree') && $this->wire()->user->hasPermission('page-clone-tree', $this->page); + $cloneTree = $input->post('clone_page_tree') && $this->page->cloneable(true); if($input->post('clone_page_unpublished')) { $page->addStatus(Page::statusUnpublished);