1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-16 03:34:33 +02:00

Add support for previously unimplemented status Page::statusUnique, which adds support for globally unique page names. This commit also changes the the previous commit Page::statusIncomplete to value 128 because it turns out 256 was used in a 3rd party module and it seemed safer to use 128, which was occuped by Page::statusVersions, which has never been used. I've also changed the name of Page::statusIncomplete to Page::statusFlagged since the status indicates an error occurred during last interactive save rather than specifically incomplete.

This commit is contained in:
Ryan Cramer
2019-02-27 11:55:08 -05:00
parent 8b82f48c4d
commit 2f908e44e7
10 changed files with 191 additions and 51 deletions

View File

@@ -168,24 +168,21 @@ class Page extends WireData implements \Countable, WireMatchable {
* Status levels 1024 and above are excluded from search by the core. Status levels 16384 and above are runtime only and not
* stored in the DB unless for logging or page history.
*
* If the under 1024 status flags are expanded in the future, it must be ensured that the combined value of the searchable flags
* never exceeds 1024, otherwise issues in Pages::find() will need to be considered.
*
* The status levels 16384 and above can safely be changed as needed as they are runtime only.
*
* Please note that statuses 2, 32, 256, and 4096 are reserved for future use.
* Please note that all other statuses are reserved for future use.
*
*/
/**
* Base status for pages in use (assigned automatically)
* Base status for pages, represents boolean true (1) or false (0) as flag with other statuses, for internal use purposes only
* #pw-internal
*
*/
const statusOn = 1;
/**
* Reserved status
* Reserved status (internal use)
* #pw-internal
*
*/
@@ -226,18 +223,30 @@ class Page extends WireData implements \Countable, WireMatchable {
const statusDraft = 64;
/**
* Page has version data available (name: "versions").
* Page is flagged as incomplete, needing review, or having some issue
* ProcessPageEdit uses this status to indicate an error message occurred during last internactive save
* #pw-internal
* @since 3.0.127
*
*/
const statusVersions = 128;
const statusFlagged = 128;
const statusIncomplete = 128; // alias of statusFlagged
/**
* Page might have incomplete data because there were errors when last saved interactively or may be missing required fields
* Deprecated, was never used, but kept in case any modules referenced it
* #pw-internal
* @deprecated
*
*/
const statusIncomplete = 256;
const statusVersions = 128;
/**
* Reserved for internal use
* #pw-internal
* @since 3.0.127
*
*/
const statusInternal = 256;
/**
* Page is temporary. 1+ day old unpublished pages with this status may be automatically deleted (name: "temp").
@@ -309,8 +318,8 @@ class Page extends WireData implements \Countable, WireMatchable {
'system' => self::statusSystem,
'unique' => self::statusUnique,
'draft' => self::statusDraft,
'versions' => self::statusVersions,
'incomplete' => self::statusIncomplete,
'flagged' => self::statusFlagged,
'internal' => self::statusInternal,
'temp' => self::statusTemp,
'hidden' => self::statusHidden,
'unpublished' => self::statusUnpublished,
@@ -318,6 +327,8 @@ class Page extends WireData implements \Countable, WireMatchable {
'deleted' => self::statusDeleted,
'systemOverride' => self::statusSystemOverride,
'corrupted' => self::statusCorrupted,
'max' => self::statusMax,
'on' => self::statusOn,
);
/**
@@ -1884,9 +1895,11 @@ class Page extends WireData implements \Countable, WireMatchable {
if($this->settings['status'] & Page::statusSystemID) $value = $value | Page::statusSystemID;
if($this->settings['status'] & Page::statusSystem) $value = $value | Page::statusSystem;
}
if($this->settings['status'] != $value) {
if($this->settings['status'] != $value && $this->isLoaded) {
$this->trackChange('status', $this->settings['status'], $value);
$this->statusPrevious = $this->settings['status'];
if($this->statusPrevious === null) {
$this->statusPrevious = $this->settings['status'];
}
}
$this->settings['status'] = $value;
if($value & Page::statusDeleted) {
@@ -3744,6 +3757,7 @@ class Page extends WireData implements \Countable, WireMatchable {
$names = array();
$remainder = $status;
foreach(self::$statuses as $name => $value) {
if($value <= self::statusOn || $value >= self::statusMax) continue;
if($status & $value) {
$names[$value] = $name;
$remainder = $remainder & ~$value;

View File

@@ -209,14 +209,8 @@ class PageFinder extends Wire {
$value = $selector->value;
if(!ctype_digit("$value")) {
// allow use of some predefined labels for Page statuses
if($value == 'hidden') $selector->value = Page::statusHidden;
else if($value == 'unpublished') $selector->value = Page::statusUnpublished;
else if($value == 'draft') $selector->value = Page::statusDraft;
else if($value == 'versions') $selector->value = Page::statusVersions;
else if($value == 'locked') $selector->value = Page::statusLocked;
else if($value == 'trash') $selector->value = Page::statusTrash;
else if($value == 'max') $selector->value = Page::statusMax;
else $selector->value = 1;
$statuses = Page::getStatuses();
$selector->value = isset($statuses[$value]) ? $statuses[$value] : 1;
}
$not = false;
if(($selector->operator == '!=' && !$selector->not) || ($selector->not && $selector->operator == '=')) {
@@ -229,8 +223,8 @@ class PageFinder extends Wire {
$selectors[$key] = $this->wire(new SelectorBitwiseAnd('status', $selector->value));
} else {
$not = $selector->not;
// some other operator like: >, <, >=, <=
$not = $selector->not;
}
if(!$not && (is_null($maxStatus) || $selector->value > $maxStatus)) $maxStatus = (int) $selector->value;

View File

@@ -436,6 +436,7 @@ class PagesEditor extends Wire {
}
}
$this->pages->names()->checkNameConflicts($page);
if(!$this->savePageQuery($page, $options)) return false;
$result = $this->savePageFinish($page, $isNew, $options);
if($language) $user->language = $language; // restore language

View File

@@ -3,9 +3,9 @@
/**
* ProcessWire Pages Names
*
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* https://processwire.com
*
*
*/
class PagesNames extends Wire {
@@ -69,7 +69,6 @@ class PagesNames extends Wire {
* @param Page $page
* @param string $format
* @return string Returns page name that was assigned
* @throws WireException
*
*/
public function setupNewPageName(Page $page, $format = '') {
@@ -551,6 +550,7 @@ class PagesNames extends Wire {
$wheres[] = 'parent_id=:parent_id';
$binds[':parent_id'] = $parentID;
}
if($pageID) {
$wheres[] = 'id!=:id';
$binds[':id'] = $pageID;
@@ -654,4 +654,104 @@ class PagesNames extends Wire {
return $this->untitledPageName;
}
/**
* Does given page have a name that has a conflict/collision?
*
* In multi-language environment this applies to default language only.
*
* @param Page $page Page to check
* @return string|bool Returns string with conflict reason or boolean false if no conflict
* @throws WireException If given invalid $options argument
* @since 3.0.127
*
*/
public function pageNameHasConflict(Page $page) {
$reason = '';
$sql = "SELECT id, status, parent_id FROM pages WHERE name=:name AND id!=:id";
$query = $this->wire('database')->prepare($sql);
$query->bindValue(':name', $page->name);
$query->bindValue(':id', $page->id, \PDO::PARAM_INT);
$query->execute();
if(!$query->rowCount()) {
$query->closeCursor();
return false;
}
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
if($row['status'] & Page::statusUnique) {
// name is already required to be unique globally
$reason = sprintf($this->_("Another page is using name “%s” and requires it to be globally unique"), $page->name);
}
if((int) $row['parent_id'] === $page->parent_id) {
// name already consumed by another page with same parent
$reason = sprintf($this->_('Another page with same parent is already using name “%s”'), $page->name);
}
if($reason) break;
}
// page requires that it be the only one with this name, so if others have it, then disallow
if(!$reason && $page->hasStatus(Page::statusUnique)) {
$reason = sprintf($this->_('Cannot use name “%s” as globally unique because it is already used by other page(s)'), $page->name);
}
$query->closeCursor();
return $reason ? $reason : false;
}
/**
* Check given pages name for conflicts and increment as needed while also triggering a warning notice
*
* @param Page $page
* @since 3.0.127
*
*/
public function checkNameConflicts(Page $page) {
$checkName = false;
$checkStatus = false;
$namePrevious = $page->namePrevious;
$statusPrevious = $page->statusPrevious;
$isNew = $page->isNew();
$nameChanged = !$isNew && $namePrevious !== null && $namePrevious !== $page->name;
if($isNew || $nameChanged) {
// new page or changed name
$checkName = true;
} else if($statusPrevious !== null && $page->hasStatus(Page::statusUnique) && !($statusPrevious & Page::statusUnique)) {
// page just received 'unique' status
$checkStatus = true;
}
if(!$checkName && !$checkStatus) return;
do {
$conflict = $this->pageNameHasConflict($page);
if(!$conflict) break;
$this->warning($conflict);
if($checkName) {
if($nameChanged) {
// restore previous name
$page->name = $page->namePrevious;
$nameChanged = false;
} else {
// increment name
$page->name = $this->incrementName($page->name);
}
} else if($checkStatus) {
// remove 'unique' status
$page->removeStatus(Page::statusUnique);
break;
}
} while($conflict);
}
}