diff --git a/wire/core/Page.php b/wire/core/Page.php index 6141e9c1..2781bcf3 100644 --- a/wire/core/Page.php +++ b/wire/core/Page.php @@ -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; } diff --git a/wire/core/PageFinder.php b/wire/core/PageFinder.php index f1b527a5..2a233cfa 100644 --- a/wire/core/PageFinder.php +++ b/wire/core/PageFinder.php @@ -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); diff --git a/wire/core/PageTraversal.php b/wire/core/PageTraversal.php index 1ca660ab..b6633521 100644 --- a/wire/core/PageTraversal.php +++ b/wire/core/PageTraversal.php @@ -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()) { diff --git a/wire/core/Pages.php b/wire/core/Pages.php index 78980608..e41f5694 100644 --- a/wire/core/Pages.php +++ b/wire/core/Pages.php @@ -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 * diff --git a/wire/core/PagesEditor.php b/wire/core/PagesEditor.php index 53380e09..504586fc 100644 --- a/wire/core/PagesEditor.php +++ b/wire/core/PagesEditor.php @@ -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); } -} \ No newline at end of file + + /** + * 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); + } +} diff --git a/wire/core/WireArray.php b/wire/core/WireArray.php index 7bc17820..78bac195 100644 --- a/wire/core/WireArray.php +++ b/wire/core/WireArray.php @@ -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) { diff --git a/wire/modules/AdminTheme/AdminThemeDefault/AdminThemeDefaultHelpers.php b/wire/modules/AdminTheme/AdminThemeDefault/AdminThemeDefaultHelpers.php index b15ae8c9..e178dacb 100644 --- a/wire/modules/AdminTheme/AdminThemeDefault/AdminThemeDefaultHelpers.php +++ b/wire/modules/AdminTheme/AdminThemeDefault/AdminThemeDefaultHelpers.php @@ -288,7 +288,12 @@ class AdminThemeDefaultHelpers extends WireData { if(!$level && count($children)) { - $out .= "$title"; + $out .= "" . + "$title"; + $my = 'left-1 top'; if(in_array($p->name, array('access', 'page', 'module'))) $my = 'left top'; $out .= "