mirror of
https://github.com/processwire/processwire.git
synced 2025-08-16 19:54:24 +02:00
Refactoring of the PagesEditor class, primarily addition of a new PagesNames class for handling page names, duplicate names, untitled pages, incrementing page names, etc.
This commit is contained in:
@@ -22,20 +22,12 @@ class PagesEditor extends Wire {
|
||||
*/
|
||||
protected $cloning = 0;
|
||||
|
||||
/**
|
||||
* Name for autogenerated page names when fields to generate name aren't populated
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $untitledPageName = 'untitled';
|
||||
|
||||
/**
|
||||
* @var Pages
|
||||
*
|
||||
*/
|
||||
protected $pages;
|
||||
|
||||
protected $pages;
|
||||
|
||||
public function __construct(Pages $pages) {
|
||||
$this->pages = $pages;
|
||||
|
||||
@@ -283,8 +275,11 @@ class PagesEditor extends Wire {
|
||||
* Auto-populate some fields for a new page that does not yet exist
|
||||
*
|
||||
* Currently it does this:
|
||||
*
|
||||
* - Assigns a parent if one is not already assigned.
|
||||
* - Sets up a unique page->name based on the format or title if one isn't provided already.
|
||||
* - Assigns a 'sort' value'.
|
||||
* - Assigns a sort value.
|
||||
* - Populates any default values for fields.
|
||||
*
|
||||
* @param Page $page
|
||||
*
|
||||
@@ -292,9 +287,9 @@ class PagesEditor extends Wire {
|
||||
public function setupNew(Page $page) {
|
||||
|
||||
$parent = $page->parent();
|
||||
if(!$parent->id) {
|
||||
// auto-assign a parent, if we can find one in family settings
|
||||
|
||||
// assign parent
|
||||
if(!$parent->id) {
|
||||
$parentTemplates = $page->template->parentTemplates;
|
||||
$parent = null;
|
||||
|
||||
@@ -307,13 +302,17 @@ class PagesEditor extends Wire {
|
||||
if($parent->id) $page->parent = $parent;
|
||||
}
|
||||
|
||||
if(!strlen($page->name)) $this->pages->setupPageName($page);
|
||||
// assign page name
|
||||
if(!strlen($page->name)) {
|
||||
$this->pages->setupPageName($page); // call through $pages intended, so it can be hooked
|
||||
}
|
||||
|
||||
// assign sort order
|
||||
if($page->sort < 0) {
|
||||
// auto assign a sort
|
||||
$page->sort = $page->parent->numChildren();
|
||||
}
|
||||
|
||||
// assign any default values for fields
|
||||
foreach($page->template->fieldgroup as $field) {
|
||||
if($page->isLoaded($field->name)) continue; // value already set
|
||||
if(!$page->hasField($field)) continue; // field not valid for page
|
||||
@@ -348,115 +347,7 @@ class PagesEditor extends Wire {
|
||||
*
|
||||
*/
|
||||
public function setupPageName(Page $page, array $options = array()) {
|
||||
|
||||
$defaults = array(
|
||||
'format' => '',
|
||||
);
|
||||
$options = array_merge($defaults, $options);
|
||||
$format = $options['format'];
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
|
||||
if(strlen($page->name)) {
|
||||
// make sure page starts with "untitled" or "untitled-"
|
||||
if($page->name != $this->untitledPageName && strpos($page->name, "$this->untitledPageName-") !== 0) {
|
||||
// page already has a name and it's not a temporary/untitled one
|
||||
// so we do nothing
|
||||
return '';
|
||||
}
|
||||
// page starts with our untitled name, but is it in the exact format we use?
|
||||
if($page->name != $this->untitledPageName) {
|
||||
$parts = explode('-', $page->name);
|
||||
array_shift($parts); // shift off 'untitled';
|
||||
$parts = implode('', $parts); // put remaining back together
|
||||
// if we were left with something other than digits,
|
||||
// this is not an auto-generated name, so leave as-is
|
||||
if(!ctype_digit($parts)) return '';
|
||||
}
|
||||
}
|
||||
|
||||
if(!strlen($format)) {
|
||||
$parent = $page->parent();
|
||||
if($parent && $parent->id) $format = $parent->template->childNameFormat;
|
||||
}
|
||||
|
||||
if(!strlen($format)) {
|
||||
if(strlen($page->title)) {
|
||||
// default format is title
|
||||
$format = 'title';
|
||||
} else {
|
||||
// if page has no title, default format is date
|
||||
$format = 'Y-m-d H:i:s';
|
||||
}
|
||||
}
|
||||
|
||||
$pageName = '';
|
||||
|
||||
if(strlen($format)) {
|
||||
// @todo add option to auto-gen name from any page property/field
|
||||
|
||||
if($format == 'title') {
|
||||
if(strlen($page->title)) $pageName = $page->title;
|
||||
else $pageName = $this->untitledPageName;
|
||||
|
||||
} else if(!ctype_alnum($format) && !preg_match('/^[-_a-zA-Z0-9]+$/', $format)) {
|
||||
// it is a date format
|
||||
$pageName = date($format);
|
||||
} else {
|
||||
|
||||
// predefined format
|
||||
$pageName = $format;
|
||||
}
|
||||
|
||||
} else if(strlen($page->title)) {
|
||||
$pageName = $page->title;
|
||||
|
||||
} else {
|
||||
// no name will be assigned
|
||||
}
|
||||
|
||||
if($pageName == $this->untitledPageName && strpos($page->name, $this->untitledPageName) === 0) {
|
||||
// page already has untitled name, and there's no need to re-assign the untitled name
|
||||
return '';
|
||||
}
|
||||
|
||||
$name = '';
|
||||
if(strlen($pageName)) {
|
||||
// make the name unique
|
||||
|
||||
if($this->wire('config')->pageNameCharset === 'UTF8') {
|
||||
$pageName = $sanitizer->pageNameUTF8($pageName);
|
||||
} else {
|
||||
$pageName = $sanitizer->pageName($pageName, Sanitizer::translate);
|
||||
}
|
||||
$numChildren = $page->parent->numChildren();
|
||||
$n = 0;
|
||||
|
||||
do {
|
||||
$name = $pageName;
|
||||
if($n > 0) {
|
||||
$nStr = "-" . ($numChildren + $n);
|
||||
if($n > 100) {
|
||||
// if we've reached this many dups, start adding a random element to it
|
||||
$nStr = '_' . mt_rand() . $nStr;
|
||||
}
|
||||
if(strlen($name) + strlen($nStr) > Pages::nameMaxLength) {
|
||||
$name = substr($name, 0, Pages::nameMaxLength - strlen($nStr));
|
||||
}
|
||||
$name .= $nStr;
|
||||
}
|
||||
$n++;
|
||||
} while($n < 200 && $this->pages->count("parent=$page->parent, name=" . $sanitizer->selectorValue($name) . ", include=all"));
|
||||
|
||||
if($this->pages->count("parent=$page->parent, name=" . $sanitizer->selectorValue($name) . ", include=all") > 0) {
|
||||
// this is now extremely unlikely
|
||||
throw new WireException("Unable to generate unique name for page $page->id");
|
||||
}
|
||||
|
||||
$page->name = $sanitizer->pageNameUTF8($name);
|
||||
$page->set('_hasAutogenName', true); // for savePageQuery, provides adjustName behavior for new pages
|
||||
}
|
||||
|
||||
return $name;
|
||||
return $this->pages->names()->setupNewPageName($page, isset($options['format']) ? $options['format'] : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,7 +454,9 @@ class PagesEditor extends Wire {
|
||||
}
|
||||
$sql = '';
|
||||
|
||||
if(strpos($page->name, $this->untitledPageName) === 0) $this->pages->setupPageName($page);
|
||||
if($this->pages->names()->isUntitledPageName($page->name)) {
|
||||
$this->pages->setupPageName($page);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'parent_id' => (int) $page->parent_id,
|
||||
@@ -632,53 +525,19 @@ class PagesEditor extends Wire {
|
||||
$query->bindValue(":$column", $value, is_int($value) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$n = 0;
|
||||
$tries = 0;
|
||||
$maxTries = 100;
|
||||
|
||||
do {
|
||||
$result = false;
|
||||
$errorCode = 0;
|
||||
|
||||
$keepTrying = false;
|
||||
try {
|
||||
$result = false;
|
||||
$result = $database->execute($query);
|
||||
|
||||
} catch(\Exception $e) {
|
||||
|
||||
$errorCode = $e->getCode();
|
||||
|
||||
// while setupNew() already attempts to uniqify a page name with an incrementing
|
||||
// number, there is a chance that two processes running at once might end up with
|
||||
// the same number, so we account for the possibility here by re-trying queries
|
||||
// that trigger duplicate-entry exceptions
|
||||
|
||||
if($errorCode == 23000 && ($page->_hasAutogenName || $options['adjustName'])) {
|
||||
// Integrity constraint violation: 1062 Duplicate entry 'background-3552' for key 'name3894_parent_id'
|
||||
// attempt to re-generate page name
|
||||
$nameField = 'name';
|
||||
// account for the duplicate possibly being a multi-language name field
|
||||
if($this->wire('languages') && preg_match('/\b(name\d*)_parent_id\b/', $e->getMessage(), $matches)) $nameField = $matches[1];
|
||||
// get either 'name' or 'name123' (where 123 is language ID)
|
||||
$pageName = $page->$nameField;
|
||||
// determine if current name format already has a trailing number
|
||||
if(preg_match('/^(.+?)-(\d+)$/', $pageName, $matches)) {
|
||||
// page already has a trailing number
|
||||
$n = (int) $matches[2];
|
||||
$pageName = $matches[1];
|
||||
}
|
||||
$nStr = '-' . (++$n);
|
||||
if(strlen($pageName) + strlen($nStr) > Pages::nameMaxLength) $pageName = substr($pageName, 0, Pages::nameMaxLength - strlen($nStr));
|
||||
$page->name = $pageName . $nStr;
|
||||
$query->bindValue(":$nameField", $this->wire('sanitizer')->pageName($page->name, Sanitizer::toAscii));
|
||||
|
||||
} else {
|
||||
// a different exception that we don't catch, so re-throw it
|
||||
throw $e;
|
||||
}
|
||||
$keepTrying = $this->savePageQueryException($page, $query, $e, $options);
|
||||
if(!$keepTrying) throw $e;
|
||||
}
|
||||
|
||||
} while($errorCode == 23000 && (++$tries < $maxTries));
|
||||
} while($keepTrying && (++$tries < $maxTries));
|
||||
|
||||
if($result && ($isNew || !$page->id)) $page->id = $database->lastInsertId();
|
||||
if($options['forceID']) $page->id = (int) $options['forceID'];
|
||||
@@ -686,6 +545,47 @@ class PagesEditor extends Wire {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Exception for savePageQuery()
|
||||
*
|
||||
* While setupNew() already attempts to uniqify a page name with an incrementing
|
||||
* number, there is a chance that two processes running at once might end up with
|
||||
* the same number, so we account for the possibility here by re-trying queries
|
||||
* that trigger duplicate-entry exceptions.
|
||||
*
|
||||
* Example of actual exception text, for reference:
|
||||
* Integrity constraint violation: 1062 Duplicate entry 'background-3552' for key 'name3894_parent_id'
|
||||
*
|
||||
* @param Page $page
|
||||
* @param \PDOStatement $query
|
||||
* @param \PDOException|\Exception $exception
|
||||
* @param array $options
|
||||
* @return bool True if it should give $query another shot, false if not
|
||||
*
|
||||
*/
|
||||
protected function savePageQueryException(Page $page, $query, $exception, array $options) {
|
||||
|
||||
$errorCode = $exception->getCode();
|
||||
if($errorCode != 23000) return false;
|
||||
|
||||
if(!$this->pages->names()->hasAutogenName($page) && !$options['adjustName']) return false;
|
||||
|
||||
// account for the duplicate possibly being a multi-language name field
|
||||
if($this->wire('languages') && preg_match('/\b(name\d*)_parent_id\b/', $exception->getMessage(), $matches)) {
|
||||
$nameField = $matches[1];
|
||||
} else {
|
||||
$nameField = 'name';
|
||||
}
|
||||
|
||||
// get either 'name' or 'name123' (where 123 is language ID)
|
||||
$pageName = $page->get($nameField);
|
||||
$pageName = $this->pages->names()->incrementName($pageName);
|
||||
$page->set($nameField, $pageName);
|
||||
$query->bindValue(":$nameField", $this->wire('sanitizer')->pageName($pageName, Sanitizer::toAscii));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save individual Page fields and supporting actions
|
||||
*
|
||||
|
Reference in New Issue
Block a user