1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 08:17:12 +02:00

Add new $pages moveReady(), restoreReady(), and renameReady() hooks. Add option for callback hook on $pages->save(). Improvements to PagesTrash class. Update $pages class so restored() hook does not ever need to be called manually, and update ProcessPageEdit to reflect that.

This commit is contained in:
Ryan Cramer
2024-01-12 11:49:51 -05:00
parent 98fe7f94a0
commit 8a1f706be9
4 changed files with 118 additions and 24 deletions

View File

@@ -8,7 +8,7 @@
*
* This is the most used object in the ProcessWire API.
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* ProcessWire 3.x, Copyright 2024 by Ryan Cramer
* https://processwire.com
*
* @link http://processwire.com/api/variables/pages/ Offical $pages Documentation
@@ -62,10 +62,12 @@
* @method saveReady(Page $page) Hook called just before a page is saved.
* @method saved(Page $page, array $changes = array(), $values = array()) Hook called after a page is successfully saved.
* @method added(Page $page) Hook called when a new page has been added.
* @method moveReady(Page $page) Hook called when a page is about to be moved to another parent.
* @method moved(Page $page) Hook called when a page has been moved from one parent to another.
* @method templateChanged(Page $page) Hook called when a page template has been changed.
* @method trashReady(Page $page) Hook called when a page is about to be moved to the trash.
* @method trashed(Page $page) Hook called when a page has been moved to the trash.
* @method restoreReady(Page $page) Hook called when a page is about to be restored out of the trash.
* @method restored(Page $page) Hook called when a page has been moved OUT of the trash.
* @method deleteReady(Page $page, array $options) Hook called just before a page is deleted.
* @method deleted(Page $page, array $options) Hook called after a page has been deleted.
@@ -73,6 +75,7 @@
* @method deletedBranch(Page $page, array $options, $numDeleted) Hook called after branch of pages deleted, on initiating page only.
* @method cloneReady(Page $page, Page $copy) Hook called just before a page is cloned.
* @method cloned(Page $page, Page $copy) Hook called after a page has been successfully cloned.
* @method renameReady(Page $page) Hook called when a page is about to be renamed.
* @method renamed(Page $page) Hook called after a page has been successfully renamed.
* @method sorted(Page $page, $children = false, $total = 0) Hook called after $page has been sorted.
* @method statusChangeReady(Page $page) Hook called when a page's status has changed and is about to be saved.
@@ -2198,6 +2201,20 @@ class Pages extends Wire {
$page->setQuietly('_added', true);
}
/**
* Hook called when a page is about to be moved to another parent
*
* Note the previous parent is accessible in the `$page->parentPrevious` property.
*
* #pw-hooker
*
* @param Page $page Page that is about to be moved.
* @since 3.0.235
*
*/
public function ___moveReady(Page $page) {
}
/**
* Hook called when a page has been moved from one parent to another
*
@@ -2259,6 +2276,18 @@ class Pages extends Wire {
$this->log("Trashed page", $page);
}
/**
* Hook called when a page is about to be moved OUT of the trash (restored)
*
* #pw-hooker
*
* @param Page $page Page that is about to be restored
* @since 3.0.235
*
*/
public function ___restoreReady(Page $page) {
}
/**
* Hook called when a page has been moved OUT of the trash (restored)
*
@@ -2388,6 +2417,29 @@ class Pages extends Wire {
$this->log("Cloned page to $copy->path", $page);
}
/**
* Hook called when a page is about to be renamed i.e. had its name field change)
*
* The previous name can be accessed at `$page->namePrevious`.
* The new name can be accessed at `$page->name`.
*
* This hook is only called when a page's name changes. It is not called when
* a page is moved unless the name was changed at the same time.
*
* **Multi-language note:**
* Also note this hook may be called if a page's multi-language name changes.
* In those cases the language-specific name is stored in "name123" while the
* previous value is stored in "-name123" (where 123 is the language ID).
*
* #pw-hooker
*
* @param Page $page The $page that was renamed
* @since 3.0.235
*
*/
public function ___renameReady(Page $page) {
}
/**
* Hook called when a page has been renamed (i.e. had its name field change)
*

View File

@@ -5,7 +5,7 @@
*
* Implements page manipulation methods of the $pages API variable
*
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* ProcessWire 3.x, Copyright 2024 by Ryan Cramer
* https://processwire.com
*
*/
@@ -424,6 +424,9 @@ class PagesEditor extends Wire {
* - `ignoreFamily` (boolean): Bypass check of allowed family/parent settings when saving (default=false)
* - `noHooks` (boolean): Prevent before/after save hooks from being called (default=false)
* - `noFields` (boolean): Bypass saving of custom fields (default=false)
* - `caller` (string): Optional name of calling function (i.e. 'pages.trash'), for internal use (default='') 3.0.235+
* - `callback` (string|callable): Hook method name from $pages or callable to trigger after save.
* It receives a single $page argument. For internal use. (default='') 3.0.235+
* @return bool True on success, false on failure
* @throws WireException
*
@@ -438,6 +441,8 @@ class PagesEditor extends Wire {
'ignoreFamily' => false,
'noHooks' => false,
'noFields' => false,
'caller' => '',
'callback' => '',
);
if(is_string($options)) $options = Selectors::keyValueStringToArray($options);
@@ -445,6 +450,10 @@ class PagesEditor extends Wire {
$user = $this->wire()->user;
$languages = $this->wire()->languages;
$language = null;
$parentPrevious = $page->parentPrevious;
$caller = $options['caller'];
$callback = $options['callback'];
$useHooks = empty($options['noHooks']);
// if language support active, switch to default language so that saved fields and hooks don't need to be aware of language
if($languages && $page->id != $user->id && "$user->language") {
@@ -465,19 +474,35 @@ class PagesEditor extends Wire {
$page->removeStatus(Page::statusUnpublished);
}
if($page->parentPrevious && !$isNew) {
if($page->isTrash() && !$page->parentPrevious->isTrash()) {
$this->pages->trash($page, false);
} else if($page->parentPrevious->isTrash() && !$page->parent->isTrash()) {
$this->pages->restore($page, false);
if($parentPrevious && !$isNew) {
if($useHooks) $this->pages->moveReady($page);
if($caller !== 'pages.trash' && $caller !== 'pages.restore') {
if($page->isTrash() && !$parentPrevious->isTrash()) {
if($this->pages->trash($page, false)) $callback = 'trashed';
} else if($parentPrevious->isTrash() && !$page->parent->isTrash()) {
if($this->pages->restore($page, false)) $callback = 'restored';
}
}
}
if($options['adjustName']) $this->pages->names()->checkNameConflicts($page);
if(!$this->savePageQuery($page, $options)) return false;
$result = $this->savePageFinish($page, $isNew, $options);
if($page->namePrevious && !$isNew && $page->namePrevious != $page->name) {
if($useHooks) $this->pages->renameReady($page);
}
$result = $this->savePageQuery($page, $options);
if($result) $result = $this->savePageFinish($page, $isNew, $options);
if($language) $user->setLanguage($language); // restore language
if($result && !empty($callback) && $useHooks) {
if(is_string($callback) && ctype_alnum($callback)) {
$this->pages->$callback($page); // hook method name in $pages
} else if(is_callable($callback)) {
$callback($page); // user defined callback
}
}
return $result;
}

View File

@@ -5,7 +5,7 @@
*
* Implements page trash/restore/empty methods of the $pages API variable
*
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* ProcessWire 3.x, Copyright 2024 by Ryan Cramer
* https://processwire.com
*
*/
@@ -18,6 +18,14 @@ class PagesTrash extends Wire {
*/
protected $pages;
/**
* Last action, i.e. "restore:1234"
*
* @var int
*
*/
protected $lastAction = '';
/**
* Construct
*
@@ -46,12 +54,13 @@ class PagesTrash extends Wire {
throw new WireException("This page (id=$page->id) may not be placed in the trash");
}
$trash = $this->pages->get($this->config->trashPageID);
$trash = $this->pages->get($this->wire()->config->trashPageID);
if(!$trash->id) {
throw new WireException("Unable to load trash page defined by config::trashPageID");
}
$this->pages->trashReady($page);
if($this->lastAction != "trash:$page") $this->pages->trashReady($page);
$page->addStatus(Page::statusTrash);
@@ -70,10 +79,10 @@ class PagesTrash extends Wire {
// make the name unique when in trash, to avoid namespace collision and maintain parent restore info
$name = $page->id;
if($parentPrevious && $parentPrevious->id) {
$name .= "." . $parentPrevious->id;
$name .= "." . $page->sort;
$sort = $page->get('sortPrevious|sort');
$name .= ".$parentPrevious->id.$sort";
}
$page->name = ($name . "_" . $page->name);
$page->name = ($name . '_' . $page->name);
// do the same for other languages, if present
$languages = $this->wire()->languages;
@@ -87,9 +96,13 @@ class PagesTrash extends Wire {
}
}
if($save) $this->pages->save($page);
$this->lastAction = "trash:$page";
if($save) {
$this->pages->save($page, array('caller' => 'pages.trash', 'callback' => 'trashed'));
}
$this->pages->editor()->savePageStatus($page->id, Page::statusTrash, true, false);
if($save) $this->pages->trashed($page);
$this->pages->debugLog('trash', $page, true);
return true;
@@ -107,24 +120,30 @@ class PagesTrash extends Wire {
*
*/
public function restore(Page $page, $save = true) {
$info = $this->getRestoreInfo($page, true);
if($info['restorable']) {
// we detected original parent
if($save) $page->save();
if($this->lastAction !== "restore:$page") $this->pages->restoreReady($page);
} else if(!$page->parent->isTrash()) {
// page has had new parent already set
if($this->lastAction !== "restore:$page") $this->pages->restoreReady($page);
$page->removeStatus(Page::statusTrash);
if($save) $page->save();
$this->pages->editor()->savePageStatus($page->id, Page::statusTrash, true, true);
if($save) $this->pages->restored($page);
$this->pages->debugLog('restore', $page, true);
} else {
// page is in trash and we cannot detect new parent
return false;
}
$this->lastAction = "restore:$page";
if($save) {
$this->pages->save($page, array('caller' => 'pages.restore', 'callback' => 'restored'));
}
return true;
}
@@ -221,6 +240,7 @@ class PagesTrash extends Wire {
if($populateToPage) {
$page->name = $name;
$page->removeStatus(Page::statusTrash);
if($newParent) {
$page->sort = $sort;
$page->parent = $newParent;

View File

@@ -2157,20 +2157,17 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
}
}
$restored = false;
if($input->post('restore_page') && $page->isTrash() && $page->restorable()) {
if($formErrors) {
$this->warning($this->_('Page cannot be restored while errors are present'));
} else if($pages->restore($page, false)) {
$message = sprintf($this->_('Restored Page: %s'), '{path}') . $numChanges;
$restored = true;
} else {
$this->warning($this->_('Error restoring page'));
}
}
$pages->save($page, $options);
if($restored) $pages->restored($page);
$message = str_replace('{path}', $page->path, $message);
$this->message($message);