1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-10 16:54:44 +02:00

Add new $pages API methods: findIDs(), sort(), insertAfter, insertBefore(). Add support for negative "limit" and "start" values in selectors, where negative values reference the end of the set rather than the beginning. Add support for "eq=n" (or alias "index=n") selectors (both WireArray and PageFinder) for pulling a specific n'th item, can also specify "first" or "last" for "n". Update $page->editUrl(true) method to force return of edit URL with scheme and hostname.

This commit is contained in:
Ryan Cramer
2016-12-23 12:18:58 -05:00
parent 0844bf2e47
commit e14d52f722
8 changed files with 479 additions and 41 deletions

View File

@@ -135,6 +135,8 @@ class Page extends WireData implements \Countable, WireMatchable {
*
* 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.
*
*/
/**
@@ -1886,7 +1888,7 @@ class Page extends WireData implements \Countable, WireMatchable {
*
* @param string $selector Selector to use, or omit to return all children.
* @param array $options Optional options to modify behavior, the same as those provided to Pages::find.
* @return PageArray Children that matched the selector, or all children (if no selector given)
* @return PageArray|array Returns PageArray for most cases. Returns regular PHP array if using the findIDs option.
* @see Page::child(), Page::find(), Page::numChildren(), Page::hasChildren()
*
*/
@@ -1969,7 +1971,7 @@ class Page extends WireData implements \Countable, WireMatchable {
* #pw-group-common
* #pw-group-traversal
*
* @param string|array $selector Selector to use, or blank to return the first child.
* @param string|array|int $selector Selector to use, or blank to return the first child.
* @param array $options Optional options per Pages::find
* @return Page|NullPage
* @see Page::children()
@@ -2788,6 +2790,7 @@ class Page extends WireData implements \Countable, WireMatchable {
* Return the URL necessary to edit this page
*
* - We recommend checking that the page is editable before outputting the editUrl().
* - If user opens URL in their browser and is not logged in, they must login to account with edit permission.
* - This method can also be accessed by property at `$page->editUrl` (without parenthesis).
*
* ~~~~~~
@@ -2798,14 +2801,21 @@ class Page extends WireData implements \Countable, WireMatchable {
*
* #pw-group-advanced
*
* @param array|bool $options Specify boolean true to force URL to include scheme and hostname, or use $options array:
* - `http` (bool): True to force scheme and hostname in URL (default=auto detect).
* @return string URL for editing this page
*
*/
public function editUrl() {
public function editUrl($options = array()) {
$adminTemplate = $this->wire('templates')->get('admin');
$https = $adminTemplate && ($adminTemplate->https > 0);
$url = ($https && !$this->wire('config')->https) ? 'https://' . $this->wire('config')->httpHost : '';
$url .= $this->wire('config')->urls->admin . "page/edit/?id=$this->id";
if($options === true || (is_array($options) && !empty($options['http']))) {
if(strpos($url, '://') === false) {
$url = ($https ? 'https://' : 'http://') . $this->wire('config')->httpHost . $url;
}
}
return $url;
}

View File

@@ -157,6 +157,7 @@ class PageFinder extends Wire {
protected $lastOptions = array();
protected $extraOrSelectors = array(); // one from each field must match
protected $sortsAfter = array(); // apply these sorts after pages loaded
protected $reverseAfter = false; // reverse order after load?
// protected $extraSubSelectors = array(); // subselectors that are added in after getQuery()
// protected $extraJoins = array();
@@ -478,6 +479,8 @@ class PageFinder extends Wire {
$this->lastOptions = $options;
if($this->reverseAfter) $matches = array_reverse($matches);
return $matches;
}
@@ -503,9 +506,11 @@ class PageFinder extends Wire {
*/
protected function preProcessSelectors(Selectors $selectors, $options = array()) {
$sortAfterSelectors = array();
$sortSelectors = array();
$start = null;
$limit = null;
$eq = null;
foreach($selectors as $selector) {
$field = $selector->field;
@@ -519,8 +524,9 @@ class PageFinder extends Wire {
}
} else if($field === 'sort') {
$sortSelectors[] = $selector;
if(!empty($options['useSortsAfter']) && $selector->operator == '=' && strpos($selector->value, '.') === false) {
$sortSelectors[] = $selector;
$sortAfterSelectors[] = $selector;
}
} else if($field === 'limit') {
@@ -528,15 +534,40 @@ class PageFinder extends Wire {
} else if($field === 'start') {
$start = (int) $selector->value;
} else if($field == 'eq' || $field == 'index') {
if($this->wire('fields')->get($field)) continue;
$value = $selector->value;
if($value === 'first') {
$eq = 0;
} else if($value === 'last') {
$eq = -1;
} else {
$eq = (int) $value;
}
$selectors->remove($selector);
}
}
if(!$limit && !$start && count($sortSelectors)
if(!is_null($eq)) {
if($eq === -1) {
$limit = -1;
$start = null;
} else if($eq === 0) {
$start = 0;
$limit = 1;
} else {
$start = $eq;
$limit = 1;
}
}
if(!$limit && !$start && count($sortAfterSelectors)
&& $options['returnVerbose'] && !empty($options['useSortsAfter'])
&& empty($options['startAfterID']) && empty($options['stopBeforeID'])) {
// the `useSortsAfter` option is enabled and potentially applicable
$sortsAfter = array();
foreach($sortSelectors as $n => $selector) {
foreach($sortAfterSelectors as $n => $selector) {
if(!$n && $this->wire('pages')->loader()->isNativeColumn($selector->value)) {
// first iteration only, see if it's a native column and prevent sortsAfter if so
break;
@@ -556,6 +587,61 @@ class PageFinder extends Wire {
$this->sortsAfter = $sortsAfter;
}
if($limit !== null && $limit < 0) {
// negative limit value means we pull results from end rather than start
if($start !== null && $start < 0) {
// we don't support a double negative, so double negative makes a positive
$start = abs($start);
$limit = abs($limit);
} else if($start > 0) {
$start = $start - abs($limit);
$limit = abs($limit);
} else {
$this->reverseAfter = true;
$limit = abs($limit);
}
}
if($start !== null && $start < 0) {
// negative start value means we start from a value from the end rather than the start
if($limit) {
// determine how many pages total and subtract from that to get start
$o = $options;
$o['getTotal'] = true;
$o['loadPages'] = false;
$o['returnVerbose'] = false;
$sel = clone $selectors;
foreach($sel as $s) {
if($s->field == 'limit' || $s->field == 'start') $sel->remove($s);
}
$sel->add(new SelectorEqual('limit', 1));
$finder = new PageFinder();
$this->wire($finder);
$finder->find($sel);
$total = $finder->getTotal();
$start = abs($start);
$start = $total - $start;
if($start < 0) $start = 0;
} else {
// same as negative limit
$this->reverseAfter = true;
$limit = abs($start);
$start = null;
}
}
if($this->reverseAfter) {
// reverse the sorts
foreach($sortSelectors as $s) {
if($s->operator != '=' || ctype_digit($s->value)) continue;
if(strpos($s->value, '-') === 0) {
$s->value = ltrim($s->value, '-');
} else {
$s->value = '-' . $s->value;
}
}
}
$this->limit = $limit;
$this->start = $start;
}
@@ -832,7 +918,6 @@ class PageFinder extends Wire {
$sortSelectors = array(); // selector containing 'sort=', which gets added last
$joins = array();
// $this->extraJoins = array();
$startLimit = false; // true when the start/limit part of the query generation is done
$database = $this->wire('database');
$this->preProcessSelectors($selectors, $options);
@@ -842,6 +927,8 @@ class PageFinder extends Wire {
$query->from("pages");
$query->groupby("pages.id");
if(!is_null($this->limit) || !is_null($this->start)) $this->getQueryStartLimit($query);
foreach($selectors as $selector) {
/** @var Selector $selector */
@@ -869,8 +956,6 @@ class PageFinder extends Wire {
continue;
} else if($field == 'limit' || $field == 'start') {
if(!$startLimit) $this->getQueryStartLimit($query);
$startLimit = true;
continue;
} else if($field == 'path' || $field == 'url') {

View File

@@ -65,7 +65,7 @@ class PageTraversal {
* @param Page $page
* @param string|array $selector Selector to use, or blank to return all children
* @param array $options
* @return PageArray
* @return PageArray|array
*
*/
public function children(Page $page, $selector = '', $options = array()) {

View File

@@ -24,17 +24,20 @@
*
* HOOKABLE METHODS
* ================
* @method PageArray find() find($selectorString, array $options = array()) Find and return all pages matching the given selector string. Returns a PageArray. #pw-group-retrieval
* @method bool save() save(Page $page) Save any changes made to the given $page. Same as : $page->save() Returns true on success. #pw-group-manipulation
* @method bool saveField() saveField(Page $page, $field, array $options = array()) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation
* @method bool trash() trash(Page $page, $save = true) Move a page to the trash. If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. #pw-group-manipulation
* @method PageArray find($selectorString, array $options = array()) Find and return all pages matching the given selector string. Returns a PageArray. #pw-group-retrieval
* @method bool save(Page $page, $options = array()) Save any changes made to the given $page. Same as : $page->save() Returns true on success. #pw-group-manipulation
* @method bool saveField(Page $page, $field, array $options = array()) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation
* @method bool trash(Page $page, $save = true) Move a page to the trash. If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. #pw-group-manipulation
* @method bool restore(Page $page, $save = true) Restore a trashed page to its original location. #pw-group-manipulation
* @method int emptyTrash() Empty the trash and return number of pages deleted. #pw-group-manipulation
* @method bool delete() delete(Page $page, $recursive = false) Permanently delete a page and it's fields. Unlike trash(), pages deleted here are not restorable. If you attempt to delete a page with children, and don't specifically set the $recursive param to True, then this method will throw an exception. If a recursive delete fails for any reason, an exception will be thrown. #pw-group-manipulation
* @method bool delete(Page $page, $recursive = false, array $options = array()) Permanently delete a page and it's fields. Unlike trash(), pages deleted here are not restorable. If you attempt to delete a page with children, and don't specifically set the $recursive param to True, then this method will throw an exception. If a recursive delete fails for any reason, an exception will be thrown. #pw-group-manipulation
* @method Page|NullPage clone(Page $page, Page $parent = null, $recursive = true, $options = array()) Clone an entire page, it's assets and children and return it. #pw-group-manipulation
* @method Page|NullPage add($template, $parent, $name = '', array $values = array()) #pw-group-manipulation
* @method int sort(Page $page, $value = false) Set the “sort” value for given $page while adjusting siblings, or re-build sort for its children. #pw-group-manipulation
* @method setupNew(Page $page) Setup new page that does not yet exist by populating some fields to it. #pw-internal
* @method string setupPageName(Page $page, array $options = []) Determine and populate a name for the given page. #pw-internal
* @method void insertBefore(Page $page, Page $beforePage) Insert one page as a sibling before another. #pw-advanced
* @method void insertAfter(Page $page, Page $afterPage) Insert one page as a sibling after another. #pw-advanced
*
* METHODS PURELY FOR HOOKS
* ========================
@@ -53,6 +56,7 @@
* @method cloneReady(Page $page, Page $copy) Hook called just before a page is cloned.
* @method cloned(Page $page, Page $copy) Hook called after a page has been successfully cloned.
* @method renamed(Page $page) Hook called after a page has been successfully renamed.
* @method sorted(Page $page, $children = false, $total = 0) Hook called after $page has been sorted.
* @method statusChangeReady(Page $page) Hook called when a page's status has changed and is about to be saved.
* @method statusChanged(Page $page) Hook called after a page status has been changed and saved.
* @method publishReady(Page $page) Hook called just before an unpublished page is published.
@@ -201,6 +205,7 @@ class Pages extends Wire {
* @param array|string $options One or more options that can modify certain behaviors. May be associative array or "key=value" selector string.
* - `findOne` (boolean): Apply optimizations for finding a single page (default=false).
* - `findAll` (boolean): Find all pages with no exclusions, same as "include=all" option (default=false).
* - `findIDs` (boolean|int): Specify 1 to return array of only page IDs, or true to return verbose array (default=false).
* - `getTotal` (boolean): Whether to set returning PageArray's "total" property (default=true, except when findOne=true).
* - `loadPages` (boolean): Whether to populate the returned PageArray with found pages (default=true).
* The only reason why you'd want to change this to false would be if you only needed the count details from
@@ -215,7 +220,7 @@ class Pages extends Wire {
* - `startAfterID` (int): Start loading pages once page matching this ID is found (default=0).
* - `lazy` (bool): Specify true to force lazy loading. This is the same as using the Pages::findMany() method (default=false).
* - `loadOptions` (array): Optional associative array of options to pass to getById() load options.
* @return PageArray Pages that matched the given selector.
* @return PageArray|array PageArray of that matched the given selector, or array of page IDs (if using findIDs option).
*
* Non-visible pages are excluded unless an "include=x" mode is specified in the selector
* (where "x" is "hidden", "unpublished" or "all"). If "all" is specified, then non-accessible
@@ -301,16 +306,31 @@ class Pages extends Wire {
/**
* Like $pages->find() except returns array of IDs rather than Page objects.
*
* This is a faster method to use when you only need to know the matching page IDs.
* - This is a faster method to use when you only need to know the matching page IDs.
* - The default behavior is to simply return a regular PHP array of matching page IDs in order.
* - The alternate behavior (verbose) returns more information for each match, as outlined below.
*
* **Verbose option:**
* When specifying boolean true for the `$options` argument (or using the `verbose` option),
* the return value is an array of associative arrays, with each of those associative arrays
* containing `id`, `parent_id` and `templates_id` keys for each page.
*
* ~~~~~
* // returns array of page IDs (integers) like [ 1234, 1235, 1236 ]
* $a = $pages->findIDs("foo=bar");
*
* // verbose option: returns array of associative arrays, each with id, parent_id and templates_id
* $a = $pages->findIDs("foo=bar", true);
* ~~~~~
*
* #pw-group-retrieval
*
* @param string|array|Selectors $selector Selector to find page IDs.
* @param array|bool $options Options to modify behavior.
* - `verbose` (bool): Specify true to make return value array of arrays with [ id, parent_id, templates_id ] for each page.
* - `verbose` (bool): Specify true to make return value array of associative arrays, each with verbose info.
* - The verbose option above can also be specified by providing boolean true as the $options argument.
* - See `Pages::find()` $options argument for additional options.
* @return array Array of page IDs, or in verbose mode: array of arrays with id, parent_id and templates_id.
* @return array Array of page IDs, or in verbose mode: array of arrays, each with id, parent_id and templates_id keys.
* @since 3.0.46
*
*/
@@ -818,6 +838,83 @@ class Pages extends Wire {
return $this->editor()->touch($pages, $modified);
}
/**
* Set the “sort” value for given $page while adjusting siblings, or re-build sort for its children
*
* *This method is primarily applicable to manually sorted pages. If pages are automatically
* sorted by some other field, this method isnt useful unless using the “re-build children” option,
* which may be helpful if converting a pages children from auto-sort to manual sort.*
*
* The default behavior of this method is to set the “sort” value for the given $page, and adjust the
* sort value of sibling pages having the same or greater sort value, to ensure all are unique and in
* order without gaps.
*
* The alternate behavior of this method is to re-build the sort values of all children of the given $page.
* This is done by specifying boolean true for the $value argument. When used, duplicate sort values and
* gaps are removed from all children.
*
* **Do you need this method?**
* If you are wondering whether you need to use this method for something, chances are that you do not.
* This method is mostly applicable for internal core use, as ProcessWire manages Page sort values on its own
* internally for the most part.
*
* ~~~~~
* // set $page to have sort=5, moving any 5+ sort pages ahead
* $pages->sort($page, 5);
*
* // same as above using alternate syntax
* $page->sort = 5;
* $pages->sort($page);
*
* // re-build sort values for children of $page, removing duplicates and gaps
* $pages->sort($page, true);
* ~~~~~
*
* #pw-advanced
*
* @param Page $page Page to sort (or parent of pages to sort, if using $value=true option)
* @param int|bool $value Specify one of the following:
* - Omit to set and use sort value from given $page.
* - Specify sort value (integer) to save that value.
* - Specify boolean true to instead rebuild sort for all of $page children.
* @return int Number of pages that had sort values adjusted
* @throws WireException
*
*/
public function ___sort(Page $page, $value = false) {
if($value === false) $value = $page->sort;
if($value === true) return $this->editor()->sortRebuild($page);
return $this->editor()->sortPage($page, $value);
}
/**
* Sort/move one page above another (for manually sorted pages)
*
* #pw-advanced
*
* @param Page $page Page you want to move/sort
* @param Page $beforePage Page you want to insert before
* @throws WireException
*
*/
public function ___insertBefore(Page $page, Page $beforePage) {
$this->editor()->insertBefore($page, $beforePage);
}
/**
* Sort/move one page after another (for manually sorted pages)
*
* #pw-advanced
*
* @param Page $page Page you want to move/sort
* @param Page $afterPage Page you want to insert after
* @throws WireException
*
*/
public function ___insertAfter(Page $page, Page $afterPage) {
$this->editor()->insertBefore($page, $afterPage, true);
}
/**
* Is the given page in a state where it can be saved from the API?
*
@@ -1465,7 +1562,7 @@ class Pages extends Wire {
}
/**
* Hook called when a page has been renamed (i.e. had it's name field change)
* Hook called when a page has been renamed (i.e. had its name field change)
*
* The previous name can be accessed at `$page->namePrevious`.
* The new name can be accessed at `$page->name`.
@@ -1489,6 +1586,18 @@ class Pages extends Wire {
}
}
/**
* Hook called after a page has been sorted, or had its children re-sorted
*
* @param Page $page Page given to have sort adjusted
* @param bool $children If true, children of $page have been all been re-sorted
* @param int $total Total number of pages that had sort adjusted as a result
*
*/
public function ___sorted(Page $page, $children = false, $total = 0) {
if($page && $children && $total) {}
}
/**
* Hook called when a page status has been changed and saved
*

View File

@@ -1171,6 +1171,12 @@ class PagesEditor extends Wire {
$this->saveParents($copy->id, $copy->numChildren);
}
if($options['recursionLevel'] === 0) {
if($copy->parent()->sortfield() == 'sort') {
$this->sortPage($copy, $copy->sort, true);
}
}
$copy->resetTrackChanges();
$this->pages->cloned($page, $copy);
$this->pages->debugLog('clone', "page=$page, parent=$parent", $copy);
@@ -1225,4 +1231,157 @@ class PagesEditor extends Wire {
return $this->wire('database')->execute($query);
}
/**
* Set page $sort value and increment siblings having same or greater sort value
*
* - This method is primarily applicable if configured sortfield is manual “sort” (or “none”).
* - This is typically used after a move, sort, clone or delete operation.
*
* @param Page $page Page that you want to set the sort value for
* @param int|null $sort New sort value for page or null to pull from $page->sort
* @param bool $after If another page already has the sort, make $page go after it rather than before it? (default=false)
* @throws WireException if given invalid arguments
* @return int Number of sibling pages that had to have sort adjusted
*
*/
public function sortPage(Page $page, $sort = null, $after = false) {
$database = $this->wire('database');
// reorder siblings having same or greater sort value, when necessary
if($page->id <= 1) return 0;
if(is_null($sort)) $sort = $page->sort;
// determine if any other siblings have same sort value
$sql = 'SELECT id FROM pages WHERE parent_id=:parent_id AND sort=:sort AND id!=:id';
$query = $database->prepare($sql);
$query->bindValue(':parent_id', $page->parent_id, \PDO::PARAM_INT);
$query->bindValue(':sort', $sort, \PDO::PARAM_INT);
$query->bindValue(':id', $page->id, \PDO::PARAM_INT);
$query->execute();
$rowCount = $query->rowCount();
$query->closeCursor();
// move sort to after if requested
if($after && $rowCount) $sort += $rowCount;
// update $page->sort property if needed
if($page->sort != $sort) $page->sort = $sort;
// make sure that $page has the sort value indicated
$sql = 'UPDATE pages SET sort=:sort WHERE id=:id';
$query = $database->prepare($sql);
$query->bindValue(':sort', $sort, \PDO::PARAM_INT);
$query->bindValue(':id', $page->id, \PDO::PARAM_INT);
$query->execute();
$sortCnt = $query->rowCount();
// no need for $page to have 'sort' indicated as a change, since we just updated it above
$page->untrackChange('sort');
if($rowCount) {
// update order of all siblings
$sql = 'UPDATE pages SET sort=sort+1 WHERE parent_id=:parent_id AND sort>=:sort AND id!=:id';
$query = $database->prepare($sql);
$query->bindValue(':parent_id', $page->parent_id, \PDO::PARAM_INT);
$query->bindValue(':sort', $sort, \PDO::PARAM_INT);
$query->bindValue(':id', $page->id, \PDO::PARAM_INT);
$query->execute();
$sortCnt += $query->rowCount();
}
// call the sorted hook
$this->pages->sorted($page, false, $sortCnt);
return $sortCnt;
}
/**
* Sort one page before another (for pages using manual sort)
*
* Note that if given $sibling parent is different from `$page` parent, then the `$pages->save()`
* method will also be called to perform that movement.
*
* @param Page $page Page to move/sort
* @param Page $sibling Sibling that page will be moved/sorted before
* @param bool $after Specify true to make $page move after $sibling instead of before (default=false)
* @throws WireException When conditions don't allow page insertions
*
*/
public function insertBefore(Page $page, Page $sibling, $after = false) {
$sortfield = $sibling->parent()->sortfield();
if($sortfield != 'sort') {
throw new WireException('Insert before/after operations can only be used with manually sorted pages');
}
if(!$sibling->id || !$page->id) {
throw new WireException('New pages must be saved before using insert before/after operations');
}
if($sibling->id == 1 || $page->id == 1) {
throw new WireException('Insert before/after operations cannot involve homepage');
}
$page->sort = $sibling->sort;
if($page->parent_id != $sibling->parent_id) {
// page needs to be moved first
$page->parent = $sibling->parent;
$page->save();
}
$this->sortPage($page, $page->sort, $after);
}
/**
* Rebuild the “sort” values for all children of the given $parent page, fixing duplicates and gaps
*
* If used on a $parent not currently sorted by by “sort” then it will update the “sort” index to be
* consistent with whatever the pages are sorted by.
*
* @param Page $parent
* @return int
*
*/
public function sortRebuild(Page $parent) {
if(!$parent->id || !$parent->numChildren) return 0;
$database = $this->wire('database');
$sorts = array();
$sort = 0;
if($parent->sortfield() == 'sort') {
// pages are manually sorted, so we can find IDs directly from the database
$sql = 'SELECT id FROM pages WHERE parent_id=:parent_id ORDER BY sort, created';
$query = $database->prepare($sql);
$query->bindValue(':parent_id', $parent->id, \PDO::PARAM_INT);
$query->execute();
// establish new sort values
do {
$id = (int) $query->fetch(\PDO::FETCH_COLUMN);
if(!$id) break;
$sorts[] = "($id,$sort)";
} while(++$sort);
$query->closeCursor();
} else {
// children of $parent don't currently use "sort" as sort property
// so we will update the "sort" of children to be consistent with that
// of whatever sort property is in use.
$o = array('findIDs' => 1, 'cache' => false);
foreach($parent->children('include=all', $o) as $id) {
$id = (int) $id;
$sorts[] = "($id,$sort)";
$sort++;
}
}
// update sort values
$query = $database->prepare(
'INSERT INTO pages (id,sort) VALUES ' . implode(',', $sorts) . ' ' .
'ON DUPLICATE KEY UPDATE sort=VALUES(sort)'
);
$query->execute();
return count($sorts);
}
}

View File

@@ -1406,23 +1406,33 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
$sort = array();
$start = 0;
$limit = null;
$eq = null;
// leave sort, limit and start away from filtering selectors
foreach($selectors as $selector) {
$remove = true;
$field = $selector->field;
if($selector->field === 'sort') {
if($field === 'sort') {
// use all sort selectors
$sort[] = $selector->value;
} else if($selector->field === 'start') {
} else if($field === 'start') {
// use only the last start selector
$start = (int) $selector->value;
} else if($selector->field === 'limit') {
} else if($field === 'limit') {
// use only the last limit selector
$limit = (int) $selector->value;
} else if(($field === 'index' || $field == 'eq') && !$this->wire('fields')->get($field)) {
// eq or index properties
switch($selector->value) {
case 'first': $eq = 0; break;
case 'last': $eq = -1; break;
default: $eq = (int) $selector->value;
}
} else {
// everything else is to be saved for filtering
$remove = false;
@@ -1447,6 +1457,38 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
}
}
if(!is_null($eq)) {
if($eq === -1) {
$limit = -1;
$start = null;
} else if($eq === 0) {
$start = 0;
$limit = 1;
} else {
$start = $eq;
$limit = 1;
}
}
if($limit < 0 && $start < 0) {
// we don't support double negative, so double negative makes a positive
$start = abs($start);
$limit = abs($limit);
} else {
if($limit < 0) {
if($start) {
$start = $start - abs($limit);
$limit = abs($limit);
} else {
$start = count($this->data) - abs($limit);
$limit = count($this->data);
}
}
if($start < 0) {
$start = count($this->data) - abs($start);
}
}
// if $limit has been given, tell sort the amount of rows that will be used
if(count($sort)) $this->_sort($sort, $limit ? $start+$limit : null);
if($start || $limit) {

View File

@@ -288,7 +288,12 @@ class AdminThemeDefaultHelpers extends WireData {
if(!$level && count($children)) {
$out .= "<a href='$p->url' id='topnav-page-$p' data-from='topnav-page-{$p->parent}' class='page-$p- pw-dropdown-toggle'>$title</a>";
$out .= "<a href='$p->url' " .
"id='topnav-page-$p' " .
"data-from='topnav-page-{$p->parent}' " .
"class='page-$p- pw-dropdown-toggle'>" .
"$title</a>";
$my = 'left-1 top';
if(in_array($p->name, array('access', 'page', 'module'))) $my = 'left top';
$out .= "<ul class='pw-dropdown-menu topnav' data-my='$my' data-at='left bottom'>";
@@ -399,8 +404,15 @@ class AdminThemeDefaultHelpers extends WireData {
$out .= "<li><a href='{$p->url}$item[url]'>$icon$label</a></li>";
} else {
$out .=
"<li><a class='pw-has-items pw-has-ajax-items' data-from='topnav-page-$p' data-json='{$p->url}$item[navJSON]' " .
"href='{$p->url}$item[url]'>$icon$label</a><ul></ul></li>";
"<li>" .
"<a class='pw-has-items pw-has-ajax-items' " .
"data-from='topnav-page-$p' " .
"data-json='{$p->url}$item[navJSON]' " .
"href='{$p->url}$item[url]'>" .
"$icon$label&nbsp;&nbsp;&nbsp;" .
"</a>" .
"<ul></ul>" .
"</li>";
}
}
return $out;

View File

@@ -75,13 +75,21 @@ class ProcessPageSort extends Process {
/**
* Saves a page that has had it's parent_id changed
*
* @param Page $page
* @param Page $parent
* @throws WirePermissionException
*
*/
protected function movePage(Page $page, Page $parent) {
if($page->parent_id == $parent->id) return;
if(!$page->moveable($parent))
throw new WirePermissionException($this->_("You do not have permission to move pages using this parent") . " - {$parent->path}");
if(!$page->moveable($parent)) {
throw new WirePermissionException(
$this->_("You do not have permission to move pages using this parent") . " - " .
$parent->path()
);
}
$page->setOutputFormatting(false);
$page->resetTrackChanges(true);
@@ -94,26 +102,39 @@ class ProcessPageSort extends Process {
/**
* Updates the sortfield for all pages having the same parent
*
* @param Page $page
* @param Page $parent
* @throws WirePermissionException|WireException
*
*/
protected function sortPages(Page $page, Page $parent) {
if(!$page->sortable()) {
if(!$this->isMoved) throw new WirePermissionException($this->_("You do not have permission to sort pages using this parent") . " - {$parent->path}");
if(!$this->isMoved) {
throw new WirePermissionException(
$this->_("You do not have permission to sort pages using this parent") . " - " .
$parent->path()
);
}
return;
}
if($parent->sortfield && $parent->sortfield != 'sort') {
$sortfield = $parent->sortfield();
if($sortfield && $sortfield != 'sort') {
$msg = sprintf($this->_("Your sort was not saved because these pages are automatically sorted by %s."), $parent->sortfield);
if(!$this->isMoved) throw new WireException($msg);
else $this->message($msg);
if(!$this->isMoved) {
throw new WireException($msg);
} else {
$this->message($msg);
}
return;
}
$changes = 0;
$sortStart = 0;
$database = $this->wire('database');
// locate the 'sort' value of the current first sorted item, to use as our starting point (in case sorting in a pagination other than 1)
// locate the 'sort' value of the current first sorted item, to use as our starting point
// (in case sorting in a pagination other than 1)
$sql = "SELECT sort FROM pages WHERE parent_id=:parent_id AND id IN(";
foreach($this->ids as $id) $sql .= ((int) $id) . ",";
$sql = rtrim($sql, ",") . ") ";
@@ -127,7 +148,6 @@ class ProcessPageSort extends Process {
$query = $database->prepare("UPDATE pages SET sort=:sort1 WHERE id=:id AND parent_id=:parent_id AND sort!=:sort2");
$query->bindValue(":parent_id", $parent->id, \PDO::PARAM_INT);
$id = 0;
$pageSort = 0;
foreach($this->ids as $sort => $id) {
@@ -146,6 +166,7 @@ class ProcessPageSort extends Process {
$page->save();
$parent->trackChange('children');
$parent->save();
$this->wire('pages')->sorted($page, false, $changes);
if($changes) $this->message("Updated sort for $changes pages", Notice::log);