mirror of
https://github.com/processwire/processwire.git
synced 2025-08-10 08:44:46 +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:
@@ -134,6 +134,8 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
@@ -2785,9 +2787,10 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URL necessary to edit this page
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
@@ -477,6 +478,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);
|
||||
|
||||
@@ -840,7 +925,9 @@ class PageFinder extends Wire {
|
||||
$query = $this->wire(new DatabaseQuerySelect());
|
||||
$query->select($options['returnVerbose'] ? array('pages.id', 'pages.parent_id', 'pages.templates_id') : array('pages.id'));
|
||||
$query->from("pages");
|
||||
$query->groupby("pages.id");
|
||||
$query->groupby("pages.id");
|
||||
|
||||
if(!is_null($this->limit) || !is_null($this->start)) $this->getQueryStartLimit($query);
|
||||
|
||||
foreach($selectors as $selector) {
|
||||
|
||||
@@ -869,9 +956,7 @@ class PageFinder extends Wire {
|
||||
continue;
|
||||
|
||||
} else if($field == 'limit' || $field == 'start') {
|
||||
if(!$startLimit) $this->getQueryStartLimit($query);
|
||||
$startLimit = true;
|
||||
continue;
|
||||
continue;
|
||||
|
||||
} else if($field == 'path' || $field == 'url') {
|
||||
$this->getQueryJoinPath($query, $selector);
|
||||
|
@@ -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()) {
|
||||
|
@@ -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 isn’t useful unless using the “re-build children” option,
|
||||
* which may be helpful if converting a page’s 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
|
||||
*
|
||||
|
@@ -1170,6 +1170,12 @@ class PagesEditor extends Wire {
|
||||
if($recursive && $options['recursionLevel'] === 0) {
|
||||
$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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -1406,22 +1406,32 @@ 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;
|
||||
$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
|
||||
@@ -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) {
|
||||
|
@@ -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 " .
|
||||
"</a>" .
|
||||
"<ul></ul>" .
|
||||
"</li>";
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
|
@@ -74,14 +74,22 @@ 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);
|
||||
@@ -93,27 +101,40 @@ 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);
|
||||
|
||||
|
Reference in New Issue
Block a user