mirror of
https://github.com/processwire/processwire.git
synced 2025-08-10 16:54:44 +02:00
Continued updates to new PagesParents class and update PagesEditor to use it
This commit is contained in:
@@ -807,6 +807,7 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
$this->_meta = null;
|
||||
foreach($this->template->fieldgroup as $field) {
|
||||
$name = $field->name;
|
||||
if(!$field->type) continue;
|
||||
if(!$field->type->isAutoload() && !isset($this->data[$name])) continue; // important for draft loading
|
||||
$value = $this->get($name);
|
||||
// no need to clone non-objects, as they've already been cloned
|
||||
@@ -966,14 +967,14 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
*
|
||||
*/
|
||||
public function setQuietly($key, $value) {
|
||||
$this->quietMode = true;
|
||||
if(isset($this->settings[$key]) && is_int($value)) {
|
||||
// allow integer-only values in $this->settings to be set directly in quiet mode
|
||||
$this->settings[$key] = $value;
|
||||
} else {
|
||||
$this->quietMode = true;
|
||||
parent::setQuietly($key, $value);
|
||||
$this->quietMode = false;
|
||||
}
|
||||
$this->quietMode = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@@ -28,6 +28,12 @@ class PagesEditor extends Wire {
|
||||
*/
|
||||
protected $pages;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param Pages $pages
|
||||
*
|
||||
*/
|
||||
public function __construct(Pages $pages) {
|
||||
$this->pages = $pages;
|
||||
|
||||
@@ -642,14 +648,18 @@ class PagesEditor extends Wire {
|
||||
|
||||
// update children counts for current/previous parent
|
||||
if($isNew) {
|
||||
// new page
|
||||
$page->parent->numChildren++;
|
||||
|
||||
} else if($page->parentPrevious && $page->parentPrevious->id != $page->parent->id) {
|
||||
// parent changed
|
||||
$page->parentPrevious->numChildren--;
|
||||
$page->parent->numChildren++;
|
||||
} else {
|
||||
if($page->parentPrevious && $page->parentPrevious->id != $page->parent->id) {
|
||||
$page->parentPrevious->numChildren--;
|
||||
$page->parent->numChildren++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// save any needed updates to pages_parents table
|
||||
$this->pages->parents()->save($page);
|
||||
|
||||
// if page hasn't changed, don't continue further
|
||||
if(!$page->isChanged() && !$isNew) {
|
||||
$this->pages->debugLog('save', '[not-changed]', true);
|
||||
@@ -731,30 +741,6 @@ class PagesEditor extends Wire {
|
||||
// operations can be access controlled.
|
||||
if($isNew || $page->parentPrevious || $page->templatePrevious) $this->wire(new PagesAccess($page));
|
||||
|
||||
// lastly determine whether the pages_parents table needs to be updated for the find() cache
|
||||
// and call upon $this->saveParents where appropriate.
|
||||
if($page->parentPrevious && $page->numChildren > 0) {
|
||||
// page is moved and it has children
|
||||
$this->saveParents($page->id, $page->numChildren);
|
||||
if($page->parent->numChildren == 1) $this->saveParents($page->parent_id, $page->parent->numChildren);
|
||||
|
||||
} else if(($page->parentPrevious && $page->parent->numChildren == 1) ||
|
||||
($isNew && $page->parent->numChildren == 1) ||
|
||||
($page->_forceSaveParents)) {
|
||||
// page is moved and is the first child of it's new parent
|
||||
// OR page is NEW and is the first child of it's parent
|
||||
// OR $page->_forceSaveParents is set (debug/debug, can be removed later)
|
||||
$this->saveParents($page->parent_id, $page->parent->numChildren);
|
||||
|
||||
} else if($page->parentPrevious && $page->parent->numChildren > 1 && $page->parent->parent_id > 1) {
|
||||
$this->saveParents($page->parent->parent_id, $page->parent->parent->numChildren);
|
||||
}
|
||||
|
||||
if($page->parentPrevious && $page->parentPrevious->numChildren == 0) {
|
||||
// $page was moved and it's previous parent is now left with no children, this ensures the old entries get deleted
|
||||
$this->saveParents($page->parentPrevious->id, 0);
|
||||
}
|
||||
|
||||
// trigger hooks
|
||||
if(empty($options['noHooks'])) {
|
||||
$this->pages->saved($page, $changes, $changesValues);
|
||||
@@ -771,6 +757,47 @@ class PagesEditor extends Wire {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TBD Identify if parent changed and call saveParentsTable() where appropriate
|
||||
*
|
||||
* @param Page $page Page to save parent(s) for
|
||||
* @param bool $isNew If page is newly created during this save this should be true, otherwise false
|
||||
*
|
||||
protected function savePageParent(Page $page, $isNew) {
|
||||
|
||||
if($page->parentPrevious || $page->_forceSaveParents || $isNew) {
|
||||
$this->pages->parents()->rebuild($page);
|
||||
}
|
||||
|
||||
// saveParentsTable option is always true unless manually disabled from a hook
|
||||
if($page->parentPrevious && !$isNew && $page->numChildren > 0) {
|
||||
// existing page was moved and it has children
|
||||
if($page->parent->numChildren == 1) {
|
||||
// first child of new parent
|
||||
$this->pages->parents()->rebuildPage($page->parent);
|
||||
} else {
|
||||
$this->pages->parents()->rebuildPage($page);
|
||||
}
|
||||
|
||||
} else if(($page->parentPrevious && $page->parent->numChildren == 1) ||
|
||||
($isNew && $page->parent->numChildren == 1) ||
|
||||
($page->_forceSaveParents)) {
|
||||
// page is moved and is the first child of its new parent
|
||||
// OR page is NEW and is the first child of its parent
|
||||
// OR $page->_forceSaveParents is set (debug/debug, can be removed later)
|
||||
$this->pages->parents()->rebuildPage($page->parent);
|
||||
|
||||
} else if($page->parentPrevious && $page->parent->numChildren > 1 && $page->parent->parent_id > 1) {
|
||||
$this->pages->parents()->rebuildPage($page->parent->parent);
|
||||
}
|
||||
|
||||
if($page->parentPrevious && $page->parentPrevious->numChildren == 0) {
|
||||
// $page was moved and its previous parent is now left with no children, this ensures the old entries get deleted
|
||||
$this->pages->parents()->rebuild($page->parentPrevious->id);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Save just a field from the given page as used by Page::save($field)
|
||||
*
|
||||
@@ -844,75 +871,6 @@ class PagesEditor extends Wire {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save references to the Page's parents in pages_parents table, as well as any other pages affected by a parent change
|
||||
*
|
||||
* Any pages_id passed into here are assumed to have children
|
||||
*
|
||||
* @param int $pages_id ID of page to save parents from
|
||||
* @param int $numChildren Number of children this Page has
|
||||
* @param int $level Recursion level, for debugging.
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
protected function saveParents($pages_id, $numChildren, $level = 0) {
|
||||
|
||||
$pages_id = (int) $pages_id;
|
||||
if(!$pages_id) return false;
|
||||
$database = $this->wire('database');
|
||||
|
||||
$query = $database->prepare("DELETE FROM pages_parents WHERE pages_id=:pages_id");
|
||||
$query->bindValue(':pages_id', $pages_id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if(!$numChildren) return true;
|
||||
|
||||
$insertSql = '';
|
||||
$id = $pages_id;
|
||||
$cnt = 0;
|
||||
$query = $database->prepare("SELECT parent_id FROM pages WHERE id=:id");
|
||||
|
||||
do {
|
||||
if($id < 2) break; // home has no parent, so no need to do that query
|
||||
$query->bindValue(":id", $id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
list($id) = $query->fetch(\PDO::FETCH_NUM);
|
||||
$id = (int) $id;
|
||||
if($id < 2) break; // no need to record 1 for every page, since it is assumed
|
||||
$insertSql .= "($pages_id, $id),";
|
||||
$cnt++;
|
||||
|
||||
} while(1);
|
||||
|
||||
if($insertSql) {
|
||||
$sql =
|
||||
'INSERT INTO pages_parents (pages_id, parents_id) ' .
|
||||
'VALUES' . rtrim($insertSql, ',') . ' ' .
|
||||
'ON DUPLICATE KEY UPDATE parents_id=VALUES(parents_id)';
|
||||
$database->exec($sql);
|
||||
}
|
||||
|
||||
// find all children of $pages_id that themselves have children
|
||||
$sql =
|
||||
"SELECT pages.id, COUNT(children.id) AS numChildren " .
|
||||
"FROM pages " .
|
||||
"JOIN pages AS children ON children.parent_id=pages.id " .
|
||||
"WHERE pages.parent_id=:pages_id " .
|
||||
"GROUP BY pages.id ";
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
$query->bindValue(':pages_id', $pages_id, \PDO::PARAM_INT);
|
||||
$database->execute($query);
|
||||
|
||||
/** @noinspection PhpAssignmentInConditionInspection */
|
||||
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$this->saveParents($row['id'], $row['numChildren'], $level+1);
|
||||
}
|
||||
$query->closeCursor();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently add status flag to a Page and save
|
||||
*
|
||||
@@ -1137,13 +1095,11 @@ class PagesEditor extends Wire {
|
||||
/** @var PagesAccess $access */
|
||||
$access = $this->wire(new PagesAccess());
|
||||
$access->deletePage($page);
|
||||
|
||||
// delete entirely from pages_parents table
|
||||
$this->pages->parents()->delete($page);
|
||||
|
||||
$database = $this->wire('database');
|
||||
|
||||
$query = $database->prepare("DELETE FROM pages_parents WHERE pages_id=:page_id");
|
||||
$query->bindValue(":page_id", $page->id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $database->prepare("DELETE FROM pages WHERE id=:page_id LIMIT 1"); // QA
|
||||
$query->bindValue(":page_id", $page->id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
@@ -1174,12 +1130,18 @@ class PagesEditor extends Wire {
|
||||
*
|
||||
*/
|
||||
public function _clone(Page $page, Page $parent = null, $recursive = true, $options = array()) {
|
||||
|
||||
$defaults = array(
|
||||
'forceID' => 0,
|
||||
'set' => array(),
|
||||
'recursionLevel' => 0, // recursion level (internal use only)
|
||||
);
|
||||
|
||||
if(is_string($options)) $options = Selectors::keyValueStringToArray($options);
|
||||
if(!isset($options['recursionLevel'])) $options['recursionLevel'] = 0; // recursion level
|
||||
$options = array_merge($defaults, $options);
|
||||
if($parent === null) $parent = $page->parent;
|
||||
|
||||
if(isset($options['set']) && isset($options['set']['name']) && strlen($options['set']['name'])) {
|
||||
if(count($options['set']) && !empty($options['set']['name'])) {
|
||||
$name = $options['set']['name'];
|
||||
} else {
|
||||
$name = $this->pages->names()->uniquePageName(array(
|
||||
@@ -1201,24 +1163,24 @@ class PagesEditor extends Wire {
|
||||
|
||||
// clone in memory
|
||||
$copy = clone $page;
|
||||
$copy->setQuietly('_cloning', $page);
|
||||
$copy->id = isset($options['forceID']) ? (int) $options['forceID'] : 0;
|
||||
$copy->setIsNew(true);
|
||||
$copy->of(false);
|
||||
$copy->setQuietly('_cloning', $page);
|
||||
$copy->setQuietly('id', $options['forceID'] > 1 ? (int) $options['forceID'] : 0);
|
||||
$copy->setQuietly('numChildren', 0);
|
||||
$copy->setQuietly('created', time());
|
||||
$copy->setQuietly('modified', time());
|
||||
$copy->name = $name;
|
||||
$copy->parent = $parent;
|
||||
$copy->of(false);
|
||||
$copy->set('numChildren', 0);
|
||||
$copy->created = time();
|
||||
$copy->modified = time();
|
||||
|
||||
if(!isset($options['quiet']) || $options['quiet']) {
|
||||
$options['quiet'] = true;
|
||||
$copy->created_users_id = $user->id;
|
||||
$copy->modified_users_id = $user->id;
|
||||
$copy->setQuietly('created_users_id', $user->id);
|
||||
$copy->setQuietly('modified_users_id', $user->id);
|
||||
}
|
||||
|
||||
// set any properties indicated in options
|
||||
if(isset($options['set']) && is_array($options['set'])) {
|
||||
if(count($options['set'])) {
|
||||
foreach($options['set'] as $key => $value) {
|
||||
$copy->set($key, $value);
|
||||
// quiet option required for setting modified time or user
|
||||
@@ -1261,32 +1223,39 @@ class PagesEditor extends Wire {
|
||||
if($page->numChildren && $recursive) {
|
||||
$start = 0;
|
||||
$limit = 200;
|
||||
$numChildrenCopied = 0;
|
||||
do {
|
||||
$children = $page->children("include=all, start=$start, limit=$limit");
|
||||
$numChildren = $children->count();
|
||||
foreach($children as $child) {
|
||||
/** @var Page $child */
|
||||
$this->pages->clone($child, $copy, true, array('recursionLevel' => $options['recursionLevel'] + 1));
|
||||
$childCopy = $this->pages->clone($child, $copy, true, array(
|
||||
'recursionLevel' => $options['recursionLevel'] + 1,
|
||||
));
|
||||
if($childCopy->id) $numChildrenCopied++;
|
||||
}
|
||||
$start += $limit;
|
||||
$this->pages->uncacheAll();
|
||||
} while($numChildren);
|
||||
$copy->setQuietly('numChildren', $numChildrenCopied);
|
||||
}
|
||||
|
||||
$copy->parentPrevious = null;
|
||||
$copy->setQuietly('_cloning', null);
|
||||
|
||||
// update pages_parents table, only when at recursionLevel 0 since pagesParents is already recursive
|
||||
if($recursive && $options['recursionLevel'] === 0) {
|
||||
$this->saveParents($copy->id, $copy->numChildren);
|
||||
}
|
||||
|
||||
if($options['recursionLevel'] === 0) {
|
||||
// update pages_parents table, only when at recursionLevel 0 since parents()->rebuild() already descends
|
||||
if($copy->numChildren) {
|
||||
$copy->setIsNew(true);
|
||||
$this->pages->parents()->rebuild($copy);
|
||||
$copy->setIsNew(false);
|
||||
}
|
||||
// update sort
|
||||
if($copy->parent()->sortfield() == 'sort') {
|
||||
$this->sortPage($copy, $copy->sort, true);
|
||||
}
|
||||
}
|
||||
|
||||
$copy->setQuietly('_cloning', null);
|
||||
$copy->of($of);
|
||||
$page->of($of);
|
||||
$page->meta()->copyTo($copy->id);
|
||||
|
@@ -12,7 +12,7 @@
|
||||
*
|
||||
* ~~~~~~
|
||||
* // Rebuild the entire pages_parents table
|
||||
* $numRows = $pages->parents()->rebuild();
|
||||
* $numRows = $pages->parents()->rebuildAll();
|
||||
* ~~~~~~
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
|
||||
@@ -38,16 +38,6 @@ class PagesParents extends Wire {
|
||||
*/
|
||||
protected $debug = false;
|
||||
|
||||
/**
|
||||
* Page parent IDs excluded from pages_parents table
|
||||
*
|
||||
* Set via $config->parentsTableExcludeIDs
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $excludeIDs = array();
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
@@ -57,13 +47,6 @@ class PagesParents extends Wire {
|
||||
public function __construct(Pages $pages) {
|
||||
$this->pages = $pages;
|
||||
$this->debug = $pages->debug();
|
||||
|
||||
$excludeIDs = $pages->wire('config')->parentsTableExcludeIDs;
|
||||
if(is_array($excludeIDs)) {
|
||||
foreach($excludeIDs as $id) {
|
||||
$this->excludeIDs[$id] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -492,45 +475,134 @@ class PagesParents extends Wire {
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild pages_parents index for given page (and any children)
|
||||
/**
|
||||
* Check if saved page needs any pages_parents updates and perform them when applicable
|
||||
*
|
||||
* @param Page $page
|
||||
* @return int Number of rows updated
|
||||
*
|
||||
*/
|
||||
public function save(Page $page) {
|
||||
|
||||
$numRows = 0;
|
||||
|
||||
// homepage not maintained in pages_parents table
|
||||
// pages being cloned are not maintained till clone operation finishes
|
||||
if($page->id < 2 || $page->_cloning || !$page->parent) return 0;
|
||||
|
||||
// first check if page parents need any updates
|
||||
if($page->isNew()) {
|
||||
// newly added page
|
||||
if($page->parent->numChildren === 1) {
|
||||
// first time parent gets added to pages_parents
|
||||
$numRows += $this->rebuild($page->parent);
|
||||
}
|
||||
} else if($page->parentPrevious && $page->parentPrevious->id != $page->parent->id) {
|
||||
// existing page with parent changed
|
||||
if($page->parentPrevious->numChildren === 0) {
|
||||
// parent no longer has children and doesn’t need entry
|
||||
$numRows += $this->delete($page->parentPrevious);
|
||||
}
|
||||
if($page->parent->numChildren === 1) {
|
||||
// first time parent gets added to pages_parents
|
||||
$numRows += $this->rebuild($page->parent);
|
||||
}
|
||||
}
|
||||
|
||||
return $numRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild pages_parents table for given page
|
||||
*
|
||||
* This descends into both parents, and children that are themselves parents,
|
||||
* and this method already calls the rebuildBranch() method when appropriate.
|
||||
*
|
||||
* @param Page $page
|
||||
* @return int
|
||||
* @since 3.0.156
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function rebuildPage(Page $page) {
|
||||
|
||||
$pages_id = (int) $page->id;
|
||||
public function rebuild(Page $page) {
|
||||
|
||||
$pages_id = (int) $page->id;
|
||||
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
|
||||
$inserts = array();
|
||||
$rowCount = 0;
|
||||
$exclude = false;
|
||||
|
||||
if($page->id < 2) return 0;
|
||||
if(!$page->_cloning && !$page->isNew()) $this->clearPage($page);
|
||||
|
||||
if(!$page->isNew()) $this->clear($page);
|
||||
|
||||
// if page has no children it does not need pages_parents entries
|
||||
if(!$page->numChildren) return 0;
|
||||
|
||||
|
||||
// identify parents to store for $page
|
||||
foreach($page->parents() as $parent) {
|
||||
$parents_id = (int) $parent->id;
|
||||
if($parents_id < 2) break;
|
||||
$inserts[] = "$pages_id,$parents_id";
|
||||
$exclude = isset($this->excludeIDs[$parents_id]);
|
||||
if($exclude) break;
|
||||
}
|
||||
|
||||
if($exclude) return 0;
|
||||
|
||||
if(count($inserts)) {
|
||||
// if parents found to insert, rebuild parents of $page
|
||||
$inserts = implode('),(', $inserts);
|
||||
$query = $database->prepare("INSERT INTO pages_parents (pages_id, parents_id) VALUES($inserts)");
|
||||
$query->execute();
|
||||
$rowCount += $query->rowCount();
|
||||
}
|
||||
|
||||
// rebuild parents within page’s children
|
||||
$rowCount += $this->rebuildBranch($page->id);
|
||||
|
||||
return $rowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild pages_parents branch starting at $fromParent and into all descendents
|
||||
*
|
||||
* @param Page|int $fromParent From parent Page or ID
|
||||
* @return int Number of rows inserted
|
||||
*
|
||||
*/
|
||||
public function rebuildBranch($fromParent) {
|
||||
return $this->rebuildAll($fromParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild pages_parents table entirely or from branch starting with a parent branch
|
||||
*
|
||||
* @param int|Page $fromParent Specify parent ID or page to rebuild from that parent, or omit to rebuild all
|
||||
* @return int Number of rows inserted
|
||||
* @since 3.0.156
|
||||
*
|
||||
*/
|
||||
public function rebuildAll($fromParent = null) {
|
||||
|
||||
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
|
||||
$inserts = array();
|
||||
$parents = $this->findParentIDs($fromParent ? $fromParent : -2); // find parents within children
|
||||
$rowCount = 0;
|
||||
|
||||
foreach($parents as $pages_id => $parents_id) {
|
||||
if(isset($this->excludeIDs[$parents_id])) continue;
|
||||
$inserts[] = "$pages_id,$parents_id";
|
||||
while(isset($parents[$parents_id])) {
|
||||
$parents_id = $parents[$parents_id];
|
||||
$inserts[] = "$pages_id,$parents_id";
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($this->excludeIDs[$page->id])) {
|
||||
$rowCount += $this->rebuild($page->id);
|
||||
if(count($parents)) {
|
||||
$where = $fromParent ? 'WHERE pages_id IN(' . implode(',', array_keys($parents)) . ')' : '';
|
||||
$sql = "DELETE FROM pages_parents $where";
|
||||
$database->exec($sql);
|
||||
}
|
||||
|
||||
if(count($inserts)) {
|
||||
$inserts = array_unique($inserts);
|
||||
$inserts = implode('),(', $inserts);
|
||||
$query = $database->prepare("INSERT INTO pages_parents (pages_id, parents_id) VALUES($inserts)");
|
||||
$query->execute();
|
||||
$rowCount = $query->rowCount();
|
||||
}
|
||||
|
||||
return $rowCount;
|
||||
@@ -538,13 +610,13 @@ class PagesParents extends Wire {
|
||||
|
||||
/**
|
||||
* Clear page from pages_parents index
|
||||
*
|
||||
*
|
||||
* @param Page|int $page
|
||||
* @return int
|
||||
* @since 3.0.156
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function clearPage($page) {
|
||||
public function clear($page) {
|
||||
$pages_id = (int) "$page";
|
||||
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
|
||||
$query = $database->prepare("DELETE FROM pages_parents WHERE pages_id=:id");
|
||||
@@ -556,160 +628,128 @@ class PagesParents extends Wire {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild pages_parents table entirely or from branch starting with a parent
|
||||
*
|
||||
* @param int|Page $fromParent Specify parent ID or page to rebuild from that parent, or omit to rebuild all
|
||||
* @return int Number of rows inserted
|
||||
* Delete page entirely from pages_parents table (both as page and parent)
|
||||
*
|
||||
* @param Page|int $page
|
||||
* @return int
|
||||
* @since 3.0.156
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function rebuild($fromParent = null) {
|
||||
|
||||
public function delete($page) {
|
||||
$pages_id = (int) "$page";
|
||||
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
|
||||
$inserts = array();
|
||||
$parents = $this->findParentIDs($fromParent ? $fromParent : -2);
|
||||
|
||||
foreach($parents as $pages_id => $parents_id) {
|
||||
if(isset($this->excludeIDs[$parents_id])) continue;
|
||||
$inserts[] = "$pages_id,$parents_id";
|
||||
while(isset($parents[$parents_id])) {
|
||||
$parents_id = $parents[$parents_id];
|
||||
$inserts[] = "$pages_id,$parents_id";
|
||||
}
|
||||
}
|
||||
|
||||
$inserts = array_unique($inserts);
|
||||
$where = $fromParent ? 'WHERE pages_id IN(' . implode(',', array_keys($parents)) . ')' : '';
|
||||
$database->exec("DELETE FROM pages_parents $where");
|
||||
|
||||
$inserts = implode('),(', $inserts);
|
||||
$query = $database->prepare("INSERT INTO pages_parents (pages_id, parents_id) VALUES($inserts)");
|
||||
$sql = "DELETE FROM pages_parents WHERE pages_id=:pages_id OR parents_id=:parents_id";
|
||||
$query = $database->prepare($sql);
|
||||
$query->bindValue(':pages_id', $pages_id, \PDO::PARAM_INT);
|
||||
$query->bindValue(':parents_id', $pages_id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$rowCount = $query->rowCount();
|
||||
|
||||
return $rowCount;
|
||||
$cnt = $query->rowCount();
|
||||
$query->closeCursor();
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild pages_parents table (deprecated original version replaced by rebuild method, here for reference only)
|
||||
*
|
||||
* Save references to the Page's parents in pages_parents table, as well as any other pages affected by a parent change
|
||||
* Tests a page and returns verbose details about what was found
|
||||
*
|
||||
* For debugging/development purposes to make sure pages_parents
|
||||
* is working as intended.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param int|Page $pages_id ID of page to save parents from
|
||||
* @param int|bool $hasChildren Does this page have children? Specify true or quantity of children.
|
||||
* @param array $options
|
||||
* - `debug` (bool): Return debug info array? (default=false)
|
||||
* - `skipIDs` (array): if pages_id has a parent matching any of these then do not save pages_parents table data for it.
|
||||
* - `level` (int): Recursion level (internal use)
|
||||
* @return int|array Returns number of records inserted or array when debug option specified
|
||||
* @deprecated
|
||||
*
|
||||
*
|
||||
* @param Page $page
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
private function saveParents($pages_id, $hasChildren, array $options = array()) {
|
||||
|
||||
$defaults = array(
|
||||
'level' => 0, // recursion level
|
||||
'skipIDs' => array(),
|
||||
'parentsIDs' => array(), // internal recursive use only
|
||||
'debug' => false,
|
||||
);
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
$pages_id = (int) "$pages_id";
|
||||
$config = $this->wire('config'); /** @var Config $config */
|
||||
public function pageTests(Page $page) {
|
||||
|
||||
$id = (int) "$page";
|
||||
$path = $page->path;
|
||||
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
|
||||
$debug = $options['debug'] ? array() : false;
|
||||
$parentsIDs = empty($options['parentsIDs']) ? array() : $options['parentsIDs'];
|
||||
if($debug !== false && !$options['level']) $debug = array('debug' => "saveParentsTable($pages_id)");
|
||||
|
||||
if(!$pages_id) return $debug === false ? 0 : $debug;
|
||||
|
||||
$skipIDs = $config->parentsTableExcludeIDs;
|
||||
$skipIDs = is_array($skipIDs) ? array_merge($skipIDs, $options['skipIDs']) : $options['skipIDs'];
|
||||
$skipIDs = empty($skipIDs) ? array() : array_flip($skipIDs); // flip for isset() use
|
||||
|
||||
$sql = "DELETE FROM pages_parents WHERE pages_id=:pages_id ";
|
||||
|
||||
if(!$options['level'] && count($skipIDs)) {
|
||||
$parentsIDs = array();
|
||||
foreach($skipIDs as $id) $parentsIDs[] = (int) $id;
|
||||
$sql .= 'OR (pages_id>0 AND parents_id IN(' . implode(',', $parentsIDs) . '))';
|
||||
}
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
$query->bindValue(':pages_id', $pages_id, \PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$query->closeCursor();
|
||||
|
||||
// if page has no children, then there is nothing further to do
|
||||
if(!$hasChildren) return $debug === false ? 0 : $debug;
|
||||
|
||||
$skip = false;
|
||||
$inserts = array();
|
||||
|
||||
if(empty($parentsIDs)) {
|
||||
$parentsIDs = $this->getParents($pages_id, array(
|
||||
'column' => 'id', // get value of id column only
|
||||
'noHome' => true // exclude homepage
|
||||
));
|
||||
}
|
||||
|
||||
foreach($parentsIDs as $parent_id) {
|
||||
$parent_id = (int) $parent_id;
|
||||
$skip = isset($skipIDs[$parent_id]);
|
||||
if($skip) break;
|
||||
$inserts[] = "$pages_id,$parent_id";
|
||||
if($debug !== false) $debug[] = $parent_id;
|
||||
}
|
||||
|
||||
if($skip) return $debug === false ? 0 : $debug;
|
||||
|
||||
$numInserts = count($inserts);
|
||||
|
||||
if($numInserts) {
|
||||
$sql =
|
||||
'INSERT INTO pages_parents (pages_id, parents_id) ' .
|
||||
'VALUES(' . implode('),(', $inserts) . ') ' .
|
||||
'ON DUPLICATE KEY UPDATE parents_id=VALUES(parents_id)';
|
||||
$database->exec($sql);
|
||||
}
|
||||
|
||||
// find all children of $pages_id that themselves have children
|
||||
$sql =
|
||||
"SELECT pages.id, pages.name, COUNT(children.id) AS numChildren " .
|
||||
"FROM pages " .
|
||||
"JOIN pages AS children ON children.parent_id=pages.id " .
|
||||
"WHERE pages.parent_id=:pages_id " .
|
||||
"GROUP BY pages.id ";
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
$query->bindValue(':pages_id', $pages_id, \PDO::PARAM_INT);
|
||||
$database->execute($query);
|
||||
$rows = array();
|
||||
|
||||
/** @noinspection PhpAssignmentInConditionInspection */
|
||||
while($row = $query->fetch(\PDO::FETCH_ASSOC)) $rows[] = $row;
|
||||
$query->closeCursor();
|
||||
|
||||
if(count($rows)) {
|
||||
array_unshift($parentsIDs, $pages_id);
|
||||
$options['parentsIDs'] = $parentsIDs;
|
||||
$options['level']++;
|
||||
foreach($rows as $row) {
|
||||
$result = $this->saveParents($row['id'], $row['numChildren'], $options);
|
||||
if($debug === false) {
|
||||
$numInserts += $result;
|
||||
} else {
|
||||
$debug["parents-of-$row[id]"] = $result;
|
||||
|
||||
$tests = array(
|
||||
'query-for-parents-of-page' => array(
|
||||
'notes' => "Query DB for parents of $path. Result will exclude homepage.",
|
||||
'query' => "SELECT parents_id FROM pages_parents WHERE pages_id=$id",
|
||||
'timer' => '',
|
||||
'count' => 0,
|
||||
'pages' => array(),
|
||||
'type' => 'database.query',
|
||||
),
|
||||
'query-for-pages-having-parent' => array(
|
||||
'notes' => "Query DB for pages having parent $path. Result will exclude pages that are not themselves parents.",
|
||||
'query' => "SELECT pages_id FROM pages_parents WHERE parents_id=$id",
|
||||
'timer' => '',
|
||||
'count' => 0,
|
||||
'pages' => array(),
|
||||
'type' => 'database.query',
|
||||
),
|
||||
'pages-find-descendents' => array(
|
||||
'notes' => "Use \$pages->find() with selector to find all descendents of $path with no exclusions.",
|
||||
'query' => "has_parent=$id, sort=parent_id, sort=id, include=all",
|
||||
'timer' => '',
|
||||
'count' => 0,
|
||||
'pages' => array(),
|
||||
'type' => 'pages.find',
|
||||
),
|
||||
'page-children-descendents' => array(
|
||||
'notes' => "Use recursive \$page->children() to manually reproduce result from previous test (the two should match)",
|
||||
'query' => "include=all, sort=id",
|
||||
'timer' => '',
|
||||
'count' => 0,
|
||||
'pages' => array(),
|
||||
'type' => 'descendents',
|
||||
),
|
||||
);
|
||||
|
||||
foreach($tests as $key => $test) {
|
||||
$timer = Debug::timer();
|
||||
if($test['type'] === 'database.query') {
|
||||
$query = $database->prepare($test['query']);
|
||||
$query->execute();
|
||||
$test['count'] = $query->rowCount();
|
||||
while($value = $query->fetchColumn()) {
|
||||
$test['pages'][] = "$value: " . $this->pages->getPath($value);
|
||||
}
|
||||
$query->closeCursor();
|
||||
|
||||
} else if($test['type'] === 'pages.find') {
|
||||
$this->pages->uncacheAll();
|
||||
$items = $this->pages->find($test['query']);
|
||||
$test['count'] = $items->count();
|
||||
$test['pages'] = $items->explode("{id}: {path}");
|
||||
|
||||
} else if($test['type'] === 'descendents') {
|
||||
$this->pages->uncacheAll();
|
||||
$items = $this->descendents($page, $test['query']);
|
||||
$test['count'] = $items->count();
|
||||
$test['pages'] = $items->explode("{id}: {path}");
|
||||
}
|
||||
$options['level']--;
|
||||
|
||||
$test['timer'] = Debug::timer($timer);
|
||||
$tests[$key] = $test;
|
||||
}
|
||||
|
||||
return $debug === false ? $numInserts : $debug;
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find descendents of $page by going recursive rather than using pages_parents table (for testing)
|
||||
*
|
||||
* @param Page $page
|
||||
* @param string $selector
|
||||
* @return PageArray
|
||||
*
|
||||
*/
|
||||
protected function descendents(Page $page, $selector = 'include=all') {
|
||||
$children = new PageArray();
|
||||
foreach($page->children($selector) as $child) {
|
||||
$children->add($child);
|
||||
if(!$child->numChildren) continue;
|
||||
foreach($this->descendents($child, $selector) as $item) {
|
||||
$children->add($item);
|
||||
}
|
||||
}
|
||||
return $children;
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user