1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-13 18:24:57 +02:00

Move version support in FieldtypeRepeater to separate class, plus add support for nested repeater versions

This commit is contained in:
Ryan Cramer
2023-12-15 13:34:19 -05:00
parent c62deb7946
commit d82194816f
2 changed files with 328 additions and 128 deletions

View File

@@ -39,7 +39,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
);
}
const devMode = false; // display verbose TD messages
const devMode = true; // display verbose TD messages
const templateNamePrefix = 'repeater_';
const fieldPageNamePrefix = 'for-field-';
@@ -1078,7 +1078,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
$parent = $this->wire()->pages->newPage($repeaterParent->template);
$parent->parent = $repeaterParent;
$parent->name = $parentName;
$parent->title = $page->name . ($version ? " v$version" : "");
$parent->title = $page->name . ($version ? " > v$version" : "");
$parent->addStatus(Page::statusSystem);
// exit early if a field is in the process of being deleted
@@ -2202,13 +2202,40 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
return $this->wire(new FieldtypeRepeaterPorter());
}
/*************************************************************************
* VERSION SUPPORT
*/
protected $versions = null;
/**
* @param Page $page
* @return int
* #pw-internal
*
* @return FieldtypeRepeaterVersions
* @since 3.0.132
*
*
*/
protected function getPageVersionNum(Page $page) {
return (int) ((string) $page->get('_repeater_version|_version'));
public function versions() {
if($this->versions === null) {
require_once(__DIR__ . '/FieldtypeRepeaterVersions.php');
$this->versions = new FieldtypeRepeaterVersions($this);
}
return $this->versions;
}
/**
* Get page version number
*
* #pw-internal
*
* @param Page $page
* @return int
* @since 3.0.132
*
*/
public function getPageVersionNum(Page $page) {
return $this->versions()->getPageVersionNum($page);
}
/**
@@ -2220,17 +2247,11 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
* @param Field $field
* @param int $version
* @return mixed
* @since 3.0.132
*
*/
public function getPageFieldVersion(Page $page, Field $field, $version) {
$page->setQuietly('_repeater_version', $version);
/** @var PagesVersions $pagesVersions */
$pagesVersions = $this->wire('pagesVersions');
$value = $pagesVersions ? $pagesVersions->getPageFieldVersion($page, $field, $version) : null;
// $value['parent_id'] = $this->getRepeaterPageParent($page, $field);
// value = $this->wakeupValue($page, $field, $value);
// $page->__unset('_repeater_version');
return $value;
return $this->versions()->getPageFieldVersion($page, $field, $version);
}
/**
@@ -2242,37 +2263,11 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
* @param Field $field
* @param int $version
* @return bool
* @since 3.0.132
*
*/
public function savePageFieldVersion(Page $page, Field $field, $version) {
$value = $page->get($field->name); /** @var RepeaterPageArray $value */
$page = clone $page;
$page->setQuietly('_repeater_version', $version);
$parent = $this->getRepeaterPageParent($page, $field, false);
if(!$parent || !$parent->id) {
// setup new version
$pages = $this->wire()->pages;
$parent = $this->getRepeaterPageParent($page, $field, true); // create
$cloneOptions = array('uncacheAll' => false);
$valueClass = $value->className(true);
$valueCopy = new $valueClass($parent, $field);
$this->wire($valueCopy);
foreach($value as $item) {
/** @var RepeaterPage $item */
$itemCopy = $pages->clone($item, $parent, false, $cloneOptions);
$this->bd("Cloned repeater item $item->name to parent $parent->name");
$valueCopy->add($itemCopy);
}
$valueCopy->resetTrackChanges();
$page->set($field->name, $valueCopy);
}
$result = $this->savePageField($page, $field);
return $result;
return $this->versions()->savePageFieldVersion($page, $field, $version);
}
/**
@@ -2284,76 +2279,11 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
* @param Field $field
* @param int $version
* @return bool
* @since 3.0.132
*
*/
public function restorePageFieldVersion(Page $page, Field $field, $version) {
if(!$version) return false;
$pages = $this->wire()->pages;
/** @var PagesVersions $pagesVersions */
$pagesVersions = $this->wire('pagesVersions');
if(!$pagesVersions) return false;
$pageVersionNum = $this->getPageVersionNum($page);
if($pageVersionNum) {
$livePage = $pages->getFresh($page->id);
if($pageVersionNum == $version) {
$versionPage = $page;
} else {
$versionPage = $pagesVersions->getPageVersion($page, $version);
}
} else {
$versionPage = $pagesVersions->getPageVersion($page, $version);
$livePage = $page;
}
$versionRepeaterParent = $this->getRepeaterPageParent($versionPage, $field, false);
$liveRepeaterParent = $this->getRepeaterPageParent($livePage, $field, false);
if(!$versionRepeaterParent) {
$this->error(
"Version repeater parent not found for " .
"page $page field $field version $version"
);
return false;
}
if($liveRepeaterParent->id) {
$this->deleteRepeaterPage($liveRepeaterParent, null, true);
}
list($name,) = explode("-v$version", $versionRepeaterParent->name, 2);
$versionRepeaterParent->addStatus(Page::statusSystemOverride);
$versionRepeaterParent->removeStatus(Page::statusSystem);
$versionRepeaterParent->name = $name;
$versionRepeaterParent->save();
$pages->editor()->addStatus($versionRepeaterParent, Page::statusSystem);
$itemIDs = array();
foreach($versionRepeaterParent->children('include=all') as $item) {
/** @var RepeaterPage $item */
if($item->isHidden() || $item->isUnpublished()) continue;
$itemIDs[] = $item->id;
}
$table = $field->getTable();
$sql =
"UPDATE $table SET data=:data, count=:count, parent_id=:parent_id " .
"WHERE pages_id=:pages_id";
$query = $this->wire()->database->prepare($sql);
$query->bindValue(':data', implode(',', $itemIDs));
$query->bindValue(':count', count($itemIDs));
$query->bindValue(':parent_id', $versionRepeaterParent->id, \PDO::PARAM_INT);
$query->bindValue(':pages_id', $versionPage->id, \PDO::PARAM_INT);
$query->execute();
$page->offsetUnset($field->name);
return true;
return $this->versions()->restorePageFieldVersion($page, $field, $version);
}
/**
@@ -2365,27 +2295,11 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule, Fieldty
* @param Field $field
* @param int $version
* @return bool
* @since 3.0.132
*
*/
public function deletePageFieldVersion(Page $page, Field $field, $version) {
if(!$version) return false;
/** @var PagesVersions $pagesVersions */
$pagesVersions = $this->wire('pagesVersions');
if(!$pagesVersions) return false;
if($this->getPageVersionNum($page) == $version) {
$versionPage = $page;
} else {
$versionPage = $pagesVersions->getPageVersion($page, $version);
}
if(!$versionPage->id) return false;
$versionRepeaterParent = $this->getRepeaterPageParent($versionPage, $field, false);
if(!$versionRepeaterParent->id) return false;
return $this->deleteRepeaterPage($versionRepeaterParent, null, true) > 0;
return $this->versions()->deletePageFieldVersion($page, $field, $version);
}
}

View File

@@ -0,0 +1,286 @@
<?php namespace ProcessWire;
/**
* PagesVersions implementation for FieldtypeRepeater
*
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* https://processwire.com
*
*/
class FieldtypeRepeaterVersions extends Wire {
protected $fieldtype;
public function __construct(FieldtypeRepeater $fieldtype) {
$this->fieldtype = $fieldtype;
parent::__construct();
}
/**
* @param Page $page
* @return int
*
*/
public function getPageVersionNum(Page $page) {
return (int) ((string) $page->get('_repeater_version|_version'));
}
/**
* Does given page or page+field have nested repeater fields?
*
* #pw-internal
*
* @param Page|null $page
* @param Field|null $field
* @param bool $verify Specify true to also verify actual nested repeater items are present
* @return bool
* @since 3.0.132
*
*/
public function hasNestedRepeaterFields($page, Field $field = null, $verify = false) {
$has = false;
if($field === null) {
if(!$page instanceof Page) return false;
foreach($page->fieldgroup as $field) {
/** @var Field $field */
if(!$field->type instanceof FieldtypeRepeater) continue;
if($page instanceof RepeaterPage) {
$has = true;
} else {
$has = $this->hasNestedRepeaterFields($page, $field, $verify);
}
if($has) break;
}
return $has;
}
$template = $this->fieldtype->_getRepeaterTemplate($field);
$repeaterNames = array();
foreach($template->fieldgroup as $f) {
if($f->type instanceof FieldtypeRepeater) {
$repeaterNames[] = $f->name;
}
}
if($verify && count($repeaterNames) && $page) {
// page has repeater fields
$items = $this->getRepeaterPageArray($page, $field, $page->get($field->name));
foreach($items as $item) {
foreach($repeaterNames as $name) {
$nestedItems = $item->get($name);
if($nestedItems && $nestedItems->count()) $has = true;
if($has) break;
}
if($has) break;
}
} else {
$has = count($repeaterNames) > 0;
}
return $has;
}
/**
* Get the value for given page, field and version
*
* #pw-internal for FieldtypeDoesVersions interface
*
* @param Page $page
* @param Field $field
* @param int $version
* @return mixed
*
*/
public function getPageFieldVersion(Page $page, Field $field, $version) {
$page->setQuietly('_repeater_version', $version);
/** @var PagesVersions $pagesVersions */
$pagesVersions = $this->wire('pagesVersions');
$value = $pagesVersions ? $pagesVersions->getPageFieldVersion($page, $field, $version) : null;
// $value['parent_id'] = $this->getRepeaterPageParent($page, $field);
// value = $this->wakeupValue($page, $field, $value);
// $page->__unset('_repeater_version');
return $value;
}
/**
* Save version of given page field
*
* #pw-internal for FieldtypeDoesVersions interface
*
* @param Page $page
* @param Field $field
* @param int $version
* @return bool
*
*/
public function savePageFieldVersion(Page $page, Field $field, $version) {
$value = $page->get($field->name); /** @var RepeaterPageArray|FieldsetPage $value */
$value = $this->getRepeaterPageArray($page, $field, $value);
$page = clone $page;
$page->setQuietly('_repeater_version', $version);
$parent = $this->fieldtype->getRepeaterPageParent($page, $field, false);
if(!$parent || !$parent->id) {
// setup new version
$pages = $this->wire()->pages;
$parent = $this->fieldtype->getRepeaterPageParent($page, $field, true); // create
$cloneOptions = array('uncacheAll' => false);
$valueClass = $value->className(true);
$valueCopy = new $valueClass($parent, $field);
$this->wire($valueCopy);
foreach($value as $item) {
/** @var RepeaterPage $item */
$itemCopy = $pages->clone($item, $parent, false, $cloneOptions);
$valueCopy->add($itemCopy);
}
$valueCopy->resetTrackChanges();
$page->set($field->name, $valueCopy);
}
$result = $this->fieldtype->savePageField($page, $field);
return $result;
}
/**
* Restore version of given page field
*
* #pw-internal for FieldtypeDoesVersions interface
*
* @param Page $page
* @param Field $field
* @param int $version
* @return bool
*
*/
public function restorePageFieldVersion(Page $page, Field $field, $version) {
if(!$version) return false;
$pages = $this->wire()->pages;
/** @var PagesVersions $pagesVersions */
$pagesVersions = $this->wire('pagesVersions');
if(!$pagesVersions) return false;
$pageVersionNum = $this->getPageVersionNum($page);
if($pageVersionNum) {
$livePage = $pages->getFresh($page->id);
if($pageVersionNum == $version) {
$versionPage = $page;
} else {
$versionPage = $pagesVersions->getPageVersion($page, $version);
}
} else {
$versionPage = $pagesVersions->getPageVersion($page, $version);
$livePage = $page;
}
$versionRepeaterParent = $this->fieldtype->getRepeaterPageParent($versionPage, $field, false);
$liveRepeaterParent = $this->fieldtype->getRepeaterPageParent($livePage, $field, false);
if(!$versionRepeaterParent || !$versionRepeaterParent->id) {
$this->error(
"Version repeater parent not found for page:$page field:$field v:$version"
);
return false;
}
$itemIDs = array();
if($liveRepeaterParent->id) {
$this->fieldtype->deleteRepeaterPage($liveRepeaterParent, null, true);
}
list($name,) = explode("-v$version", $versionRepeaterParent->name, 2);
$versionRepeaterParent->addStatus(Page::statusSystemOverride);
$versionRepeaterParent->removeStatus(Page::statusSystem);
$versionRepeaterParent->name = $name;
$versionRepeaterParent->title = preg_replace('/> v\d+/', '> 0', $versionRepeaterParent->title);
$versionRepeaterParent->save();
$pages->editor()->addStatus($versionRepeaterParent, Page::statusSystem);
foreach($versionRepeaterParent->children('include=all') as $item) {
/** @var RepeaterPage $item */
if($item->isHidden() && $item->isUnpublished()) continue;
$itemIDs[] = $item->id;
}
$table = $field->getTable();
$sql =
"UPDATE $table SET data=:data, count=:count, parent_id=:parent_id " .
"WHERE pages_id=:pages_id";
$query = $this->wire()->database->prepare($sql);
$query->bindValue(':data', implode(',', $itemIDs));
$query->bindValue(':count', count($itemIDs));
$query->bindValue(':parent_id', $versionRepeaterParent->id, \PDO::PARAM_INT);
$query->bindValue(':pages_id', $versionPage->id, \PDO::PARAM_INT);
$query->execute();
$page->offsetUnset($field->name);
return true;
}
/**
* Delete version
*
* #pw-internal for FieldtypeDoesVersions interface
*
* @param Page $page
* @param Field $field
* @param int $version
* @return bool
*
*/
public function deletePageFieldVersion(Page $page, Field $field, $version) {
if(!$version) return false;
/** @var PagesVersions $pagesVersions */
$pagesVersions = $this->wire('pagesVersions');
if(!$pagesVersions) return false;
if($this->getPageVersionNum($page) == $version) {
$versionPage = $page;
} else {
$versionPage = $pagesVersions->getPageVersion($page, $version);
}
if(!$versionPage->id) return false;
$versionRepeaterParent = $this->fieldtype->getRepeaterPageParent($versionPage, $field, false);
if(!$versionRepeaterParent->id) return false;
return $this->fieldtype->deleteRepeaterPage($versionRepeaterParent, null, true) > 0;
}
/**
* Normalize a value to a RepeaterPageArray
*
* @param Page $page
* @param Field $field
* @param RepeaterPage|RepeaterPageArray $value
* @return PageArray
*
*/
protected function getRepeaterPageArray(Page $page, Field $field, $value) {
$fieldtype = $field->type; /** @var FieldtypeRepeater $fieldtype */
if($value instanceof PageArray) {
// great
} else if($value instanceof RepeaterPage) {
$item = $value;
$value = $fieldtype->getBlankValue($page, $field);
$value->add($item);
} else {
$value = $fieldtype->getBlankValue($page, $field);
}
return $value;
}
}