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

Add PagesVersions module to core, which provides an API for managing page versions

This commit is contained in:
Ryan Cramer
2023-12-15 14:17:15 -05:00
parent 99a1d0f81d
commit 7e7a760b88
3 changed files with 1840 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
<?php namespace ProcessWire;
/**
* Page Version Info
*
* For pages that are a version, this class represents
* the `_version` property of the page.
*
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* https://processwire.com
*
* @property int $version
* @property string $description
* @property int $created
* @property int $modified
* @property int $pages_id
* @property Page $page
* @property int $created_users_id
* @property int $modified_users_id
* @property-read User|NullPage $createdUser
* @property-read User|NullPage $modifiedUser
* @property-read string $createdStr
* @property-read string $modifiedStr
* @property string $action
*
*/
class PageVersionInfo extends WireData {
/**
* Value for $action property when restoring a page
*
*/
const actionRestore = 'restore';
/**
* @var Page
*
*/
protected $page;
/**
* @param array $data
*
*/
public function __construct(array $data = []) {
parent::__construct();
$defaults = [
'version' => 0,
'description' => '',
'created' => 0,
'modified' => 0,
'created_users_id' => 0,
'modified_users_id' => 0,
'pages_id' => 0,
'action' => '',
];
parent::setArray(array_merge($defaults, $data));
}
/**
* Set property
*
* @param string $key
* @param string|int|Page $value
* @return self
*
*/
public function set($key, $value) {
if($key === 'version' || $key === 'pages_id') {
$value = (int) $value;
} else if($key === 'created' || $key === 'modified') {
if($value) {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else {
$value = 0;
}
} else if($key === 'created_users_id' || $key === 'modified_users_id') {
$value = (int) $value;
} else if($key === 'page') {
$this->setPage($value);
} else if($key === 'description') {
$value = (string) $value;
}
return parent::set($key, $value);
}
/**
* Get property
*
* @param string $key
* @return mixed|NullPage|Page|User|null
*
*/
public function get($key) {
switch($key) {
case 'page': return $this->getPage();
case 'createdUser': return $this->getCreatedUser();
case 'modifiedUser': return $this->getModifiedUser();
case 'createdStr': return $this->created > 0 ? date('Y-m-d H:i:s', $this->created) : '';
case 'modifiedStr': return $this->modified > 0 ? date('Y-m-d H:i:s', $this->modified) : '';
}
return parent::get($key);
}
/**
* Get page that this version is for
*
* @return NullPage|Page
*
*/
public function getPage() {
if($this->page) return $this->page;
if($this->pages_id) {
$this->page = $this->wire()->pages->get($this->pages_id);
return $this->page;
}
return new NullPage();
}
/**
* Set page that this version is for
*
* @param Page $page
*
*/
public function setPage(Page $page) {
$this->page = $page;
if($page->id) parent::set('pages_id', $page->id);
$page->wire($this);
}
/**
* Get user that created this version
*
* @return NullPage|User
*
*/
public function getCreatedUser() {
return $this->wire()->users->get($this->created_users_id);
}
/**
* Get user that last modified this version
*
* @return NullPage|User
*
*/
public function getModifiedUser() {
$id = $this->modified_users_id;
return $id ? $this->wire()->users->get($id) : $this->getCreatedUser();
}
/**
* Set action for PagesVersions
*
* #pw-internal
*
* @param string $action
*
*/
public function setAction($action) {
parent::set('action', $action);
}
/**
* Get action for PagesVersions
*
* #pw-internal
*
* @return string
*
*/
public function getAction() {
return parent::get('action');
}
/**
* String value is version number as a string
*
* @return string
*
*/
public function __toString() {
return (string) $this->version;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
<?php namespace ProcessWire;
/**
* File management for PagesVersions
*
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* https://processwire.com
*
*/
class PagesVersionsFiles extends Wire {
const dirPrefix = 'v'; // directory prefix for version files
/**
* @var PagesVersions
*
*/
protected $pagesVersions;
/**
* Are file hooks disabled?
*
* @var bool
*
*/
protected $disableFileHooks = false;
/**
* Value for last mkdir() call
*
* @var string
*
*/
protected $lastMkdir = '';
/**
* Construct
*
*/
public function __construct(PagesVersions $pagesVersions) {
$this->pagesVersions = $pagesVersions;
parent::__construct();
$pagesVersions->wire($this);
$this->addHookAfter('PagefilesManager::path, PagefilesManager::url', $this, 'hookPagefilesManagerPath');
$this->addHookAfter('Pages::savePageOrFieldReady', $this, 'hookPagesSaveReady');
}
/********************************************************************************
* API SUPPORT METHODS
*
*/
/**
* Copy files for given $page into version directory
*
* @param Page $page
* @param $version
* @return bool|int
*
*/
public function copyPageVersionFiles(Page $page, $version) {
if(!$page->hasFilesPath()) return 0;
$files = $this->wire()->files;
$filesManager = $page->filesManager();
$sourcePath = $filesManager->path();
$targetPath = $this->versionFilesPath($filesManager->___path(), $version);
if($sourcePath === $targetPath) {
// skipping copy
$qty = 0;
} else {
$qty = $files->copy($sourcePath, $targetPath, [ 'recursive' => false ]);
}
return $qty;
}
/**
* Delete files for given version
*
* @param Page $page
* @param int $version
* @return bool
*
*/
public function deletePageVersionFiles(Page $page, $version) {
if(!$page->hasFilesPath()) return true;
$path = $this->versionFilesPath($page->filesManager()->___path(), $version);
if(!is_dir($path)) return true;
return $this->wire()->files->rmdir($path, true);
}
/**
* Restore files from version into live $page
*
* @param Page $page
* @param int $version
* @return int
*
*/
public function restorePageVersionFiles(Page $page, $version) {
if(!$page->hasFilesPath()) return 0;
$filesManager = $page->filesManager();
$livePath = $filesManager->___path();
$versionPath = $this->versionFilesPath($livePath, $version);
if(!is_dir($versionPath)) return 0;
$this->disableFileHooks = true;
$filesManager->emptyPath(false, false);
$qty = $filesManager->importFiles($versionPath);
$this->disableFileHooks = false;
return $qty;
}
/********************************************************************************
* UTILITIES
*
*/
/**
* Update given files path for version
*
* #pw-internal
*
* @param string $path
* @param int $version
* @return string
*
*/
public function versionFilesPath($path, $version) {
if($path instanceof Page) $path = $path->filesManager()->___path();
$version = (int) $version;
return $path . self::dirPrefix . $version . '/';
}
/**
* Get the total size of all files in given version
*
* #pw-internal
*
* @param Page $page
* @param int $version
* @return int
*
*/
public function getTotalVersionSize(Page $page, $version = 0) {
if(!$page->hasFilesPath()) return 0;
if(!$version) $version = $this->pagesVersions->pageVersionNumber($page);
if($version) {
$path = $this->versionFilesPath($page, $version);
} else {
$path = $page->filesPath();
}
$size = 0;
foreach(new \DirectoryIterator($path) as $file) {
if($file->isDir() || $file->isDot()) continue;
$size += $file->getSize();
}
return $size;
}
/********************************************************************************
* HOOKS
*
*/
/**
* Hook to PagefilesManager::path to update for version directories
*
* #pw-internal
*
* @param HookEvent $event
*
*/
public function hookPagefilesManagerPath(HookEvent $event) {
if($this->disableFileHooks) return;
$manager = $event->object; /** @var PagefilesManager $manager */
$page = $manager->page;
if(!$page->get(PagesVersions::pageProperty)) return;
$version = $this->pagesVersions->pageVersionNumber($page);
if(!$version) return;
$versionDir = self::dirPrefix . "$version/";
$event->return .= $versionDir;
if($event->method == 'path') {
$path = $event->return;
if($this->lastMkdir != $path && !is_dir($path)) {
$this->wire()->files->mkdir($path, true);
}
}
}
/**
* Hook Pages::saveReady to restore version files when $action === 'restore'
*
* #pw-internal
*
* @param HookEvent $event
*
*/
public function hookPagesSaveReady(HookEvent $event) {
$page = $event->arguments(0);
$info = $this->pagesVersions->pageVersionInfo($page);
if($info->version && $info->getAction() === PageVersionInfo::actionRestore) {
$this->restorePageVersionFiles($page, $info->version);
}
}
/**
* Hook before Pages::save or Pages::saveField to prevent save on a version page unless $action === 'restore'
*
* #pw-internal
*
* @param Page $page
*
*/
public function hookBeforePagesSave(Page $page) {
if(PagefilesManager::hasPath($page)) {
// ensures files flagged for deletion get deleted
// this hook doesn't get called by $pages since we replaced the call
$page->filesManager->save();
}
}
}