From af0afe9f95555e4a987ec2a1668139708283afae Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 7 Dec 2018 09:57:07 -0500 Subject: [PATCH] Additional updates for processwire/processwire-issues#751 plus some enhancements to PageFinder --- wire/core/Page.php | 7 ++-- wire/core/PageFinder.php | 65 ++++++++++++++++++++++++++++++++----- wire/core/PageTraversal.php | 59 +++++++++++++++++++++++---------- 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/wire/core/Page.php b/wire/core/Page.php index 27e90da9..cae1d5c9 100644 --- a/wire/core/Page.php +++ b/wire/core/Page.php @@ -3149,9 +3149,10 @@ class Page extends WireData implements \Countable, WireMatchable { /** * Return the index/position of this page relative to siblings. * - * If given a hidden or unpublished page, that page would not usually be part of the group of siblings, - * unless specifically including hidden and/or unpublished pages. As a result, such pages will return -1 - * for this method (as of 3.0.121), indicating they are not part of the default index. + * If given a hidden or unpublished page, that page would not usually be part of the group of siblings. + * As a result, such pages will return what the value would be if they were visible (as of 3.0.121). This + * may overlap with the index of other pages, since indexes are relative to visible pages, unless you + * specify an include mode (see next paragraph). * * If you want this method to include hidden/unpublished pages as part of the index numbers, then * specify boolean true for the $selector argument (which implies "include=all") OR specify a diff --git a/wire/core/PageFinder.php b/wire/core/PageFinder.php index ce1879a8..e189ffe8 100644 --- a/wire/core/PageFinder.php +++ b/wire/core/PageFinder.php @@ -43,7 +43,7 @@ class PageFinder extends Wire { * */ 'findUnpublished' => false, - + /** * Specify that it's okay for hidden AND unpublished AND trashed pages to be included in the results * @@ -56,6 +56,12 @@ class PageFinder extends Wire { */ 'findAll' => false, + /** + * Always allow these page IDs to be included regardless of findHidden, findUnpublished, findTrash, findAll settings + * + */ + 'alwaysAllowIDs' => array(), + /** * This is an optimization used by the Pages::find method, but we observe it here as we may be able * to apply some additional optimizations in certain cases. For instance, if loadPages=false, then @@ -326,7 +332,7 @@ class PageFinder extends Wire { /** * Return all pages matching the given selector. * - * @param Selectors|string|array $selectors Selectors object or selector string + * @param Selectors|string|array $selectors Selectors object, selector string or selector array * @param array $options * - `findOne` (bool): Specify that you only want to find 1 page and don't need info for pagination (default=false). * - `findHidden` (bool): Specify that it's okay for hidden pages to be included in the results (default=false). @@ -345,7 +351,7 @@ class PageFinder extends Wire { * - `returnQuery` (bool): When true, only the DatabaseQuery object is returned by find(), for internal use. (default=false) * - `loadPages` (bool): This is an optimization used by the Pages::find() method, but we observe it here as we * may be able to apply some additional optimizations in certain cases. For instance, if loadPages=false, then - * we can skip retrieval of IDs and omit sort fields. (default=true) + * we can skip retrieval of IDs and omit sort fields. (default=true) * - `stopBeforeID` (int): Stop loading pages once a page matching this ID is found. Page having this ID will be * excluded as well (default=0). * - `startAfterID` (int): Start loading pages once a page matching this ID is found. Page having this ID will be @@ -365,7 +371,7 @@ class PageFinder extends Wire { if(is_string($selectors) || is_array($selectors)) { $selectors = new Selectors($selectors); } else if(!$selectors instanceof Selectors) { - throw new PageFinderException("find() requires Selectors object or string"); + throw new PageFinderException("find() requires Selectors object, string or array"); } $this->fieldgroups = $this->wire('fieldgroups'); @@ -487,16 +493,48 @@ class PageFinder extends Wire { /** * Same as find() but returns just a simple array of page IDs without any other info * - * @param Selectors $selectors + * @param Selectors|string|array $selectors Selectors object, selector string or selector array * @param array $options * @return array of page IDs * */ - public function findIDs(Selectors $selectors, $options = array()) { + public function findIDs($selectors, $options = array()) { $options['returnVerbose'] = false; return $this->find($selectors, $options); } + /** + * Return a count of pages that match + * + * @param Selectors|string|array $selectors Selectors object, selector string or selector array + * @param array $options + * @return int + * @since 3.0.121 + * + */ + public function count($selectors, $options = array()) { + + $defaults = array( + 'getTotal' => true, + 'getTotalType' => 'count', + 'loadPages' => false, + 'returnVerbose' => false + ); + + $options = array_merge($defaults, $options); + + if(!empty($options['startBeforeID']) || !empty($options['stopAfterID'])) { + $options['loadPages'] = true; + $options['getTotalType'] = 'calc'; + $count = count($this->find($selectors, $options)); + } else { + $this->find($selectors, $options); + $count = $this->total; + } + + return $count; + } + /** * Pre-process given Selectors object * @@ -737,7 +775,7 @@ class PageFinder extends Wire { if($findExtends) { if($foundTypes === null) { - $foundTypes = isset($this->pageArrayData['extends']) ? $this->pageDataArray['extends'] : array(); + $foundTypes = isset($this->pageArrayData['extends']) ? $this->pageArrayData['extends'] : array(); } $fType = $f->type->className(); if(isset($foundTypes[$fType])) { @@ -1130,7 +1168,7 @@ class PageFinder extends Wire { continue; } else if($this->wire('fields')->isNative($field) || strpos($fieldsStr, ':parent.') !== false) { - $this->getQueryNativeField($query, $selector, $fields); + $this->getQueryNativeField($query, $selector, $fields, $options); continue; } @@ -1944,10 +1982,11 @@ class PageFinder extends Wire { * @param DatabaseQuerySelect $query * @param Selector $selector * @param array $fields + * @param array $options * @throws PageFinderSyntaxException * */ - protected function getQueryNativeField(DatabaseQuerySelect $query, $selector, $fields) { + protected function getQueryNativeField(DatabaseQuerySelect $query, $selector, $fields, array $options) { $values = $selector->values(true); $SQL = ''; @@ -2077,6 +2116,14 @@ class PageFinder extends Wire { if($isName) $value = $this->wire('sanitizer')->pageName($value, Sanitizer::toAscii); $value = $database->escapeStr($value); $s = "$table." . $field . $operator . ((ctype_digit("$value") && $field != 'name') ? ((int) $value) : "'$value'"); + + if($field === 'status' && strpos($operator, '<') === 0 && $value >= Page::statusHidden && count($options['alwaysAllowIDs'])) { + // support the 'alwaysAllowIDs' option for specific page IDs when requested but would + // not otherwise appear in the results due to hidden or unpublished status + $allowIDs = array(); + foreach($options['alwaysAllowIDs'] as $id) $allowIDs[] = (int) $id; + $s = "($s OR $table.id IN(" . implode(',', $allowIDs) . '))'; + } } if($selector->not) $s = "NOT ($s)"; diff --git a/wire/core/PageTraversal.php b/wire/core/PageTraversal.php index fe41f7b9..484d15bc 100644 --- a/wire/core/PageTraversal.php +++ b/wire/core/PageTraversal.php @@ -282,11 +282,26 @@ class PageTraversal { return $page->_pages('find', $selector, $options); } + /** + * Get include mode specified in selector or blank if none + * + * @param string|array|Selectors $selector + * @return string + * + */ + protected function _getIncludeMode($selector) { + if(is_string($selector) && strpos($selector, 'include=') === false) return ''; + if(is_array($selector)) return isset($selector['include']) ? $selector['include'] : ''; + $selector = $selector instanceof Selectors ? $selector : new Selectors($selector); + $include = $selector->getSelectorByField('include'); + return $include ? $include->value() : ''; + } + /** * Builds the PageFinder options for the _next() method * * @param Page $page - * @param string|array $selector + * @param string|array|Selectors $selector * @param array $options * @return array * @@ -298,7 +313,16 @@ class PageTraversal { 'startAfterID' => $options['prev'] ? 0 : $page->id, 'stopBeforeID' => $options['prev'] ? $page->id : 0, 'returnVerbose' => $options['all'] ? false : true, + 'alwaysAllowIDs' => array(), ); + + if($page->isUnpublished() || $page->isHidden()) { + // allow next() to still move forward even though it is hidden or unpublished + $includeMode = $this->_getIncludeMode($selector); + if(!$includeMode || ($includeMode === 'hidden' && $page->isUnpublished())) { + $fo['alwaysAllowIDs'][] = $page->id; + } + } if(!$options['until']) return $fo; @@ -346,7 +370,7 @@ class PageTraversal { * Provides the core logic for next, prev, nextAll, prevAll, nextUntil, prevUntil * * @param Page $page - * @param string|array $selector Optional selector. When specified, will find nearest sibling(s) that match. + * @param string|array|Selectors $selector Optional selector. When specified, will find nearest sibling(s) that match. * @param array $options Options to modify behavior * - `prev` (bool): When true, previous siblings will be returned rather than next siblings. * - `all` (bool): If true, returns all nextAll or prevAll rather than just single sibling (default=false). @@ -378,8 +402,12 @@ class PageTraversal { if(is_array($selector)) { $selector['parent_id'] = $parent->id; - } else { + } else if(is_string($selector)) { $selector = trim("parent_id=$parent->id, $selector", ", "); + } else if($selector instanceof Selectors) { + $selector->add(new SelectorEqual('parent_id', $parent->id)); + } else { + throw new WireException('Selector must be string, array or Selectors object'); } $pageFinder = $pages->getPageFinder(); @@ -416,15 +444,16 @@ class PageTraversal { * Return the index/position of the given page relative to its siblings * * If given a hidden or unpublished page, that page would not usually be part of the group of siblings. - * As a result, such pages will return -1 for this method (as of 3.0.121), indicating they are not part - * of the default index. + * As a result, such pages will return what the value would be if they were visible (as of 3.0.121). This + * may overlap with the index of other pages, since indexes are relative to visible pages, unless you + * specify an include mode (see next paragraph). * * If you want this method to include hidden/unpublished pages as part of the index numbers, then * specify boolean true for the $selector argument (which implies "include=all") OR specify a * selector of "include=hidden", "include=unpublished" or "include=all". * * @param Page $page - * @param string|array|bool $selector Selector to apply or boolean true for "include=all" (since 3.0.121). + * @param string|array|bool|Selectors $selector Selector to apply or boolean true for "include=all" (since 3.0.121). * - Boolean true to include hidden and unpublished pages as part of the index numbers (same as "include=all"). * - An "include=hidden", "include=unpublished" or "include=all" selector to include them in the index numbers. * - A string selector or selector array to filter the criteria for the returned index number. @@ -432,12 +461,8 @@ class PageTraversal { * */ public function index(Page $page, $selector = '') { - if($selector === true) { - $selector = "include=all"; - } else if(empty($selector) && ($page->isHidden() || $page->isUnpublished())) { - return -1; - } - $index = $this->_next($page, $selector, array('prev' => true, 'all' => true, 'qty' => true)); + if($selector === true) $selector = "include=all"; + $index = $this->_next($page, $selector, array('prev' => true, 'all' => true, 'qty' => 'index')); return $index; } @@ -445,7 +470,7 @@ class PageTraversal { * Return the next sibling page * * @param Page $page - * @param string $selector Optional selector. When specified, will find nearest next sibling that matches. + * @param string|array|Selectors $selector Optional selector. When specified, will find nearest next sibling that matches. * @return Page|NullPage Returns the next sibling page, or a NullPage if none found. * */ @@ -457,7 +482,7 @@ class PageTraversal { * Return the previous sibling page * * @param Page $page - * @param string $selector Optional selector. When specified, will find nearest previous sibling that matches. + * @param string|array|Selectors $selector Optional selector. When specified, will find nearest previous sibling that matches. * @return Page|NullPage Returns the previous sibling page, or a NullPage if none found. * */ @@ -470,7 +495,7 @@ class PageTraversal { * Return all sibling pages after this one, optionally matching a selector * * @param Page $page - * @param string $selector Optional selector. When specified, will filter the found siblings. + * @param string|array|Selectors $selector Optional selector. When specified, will filter the found siblings. * @param array $options Options to pass to the _next() method * @return PageArray Returns all matching pages after this one. * @@ -485,7 +510,7 @@ class PageTraversal { * Return all sibling pages prior to this one, optionally matching a selector * * @param Page $page - * @param string $selector Optional selector. When specified, will filter the found siblings. + * @param string|array|Selectors $selector Optional selector. When specified, will filter the found siblings. * @param array $options Options to pass to the _next() method * @return PageArray Returns all matching pages after this one. * @@ -503,7 +528,7 @@ class PageTraversal { * Return all sibling pages after this one until matching the one specified * * @param Page $page - * @param string|Page|array $selector May either be a selector or Page to stop at. Results will not include this. + * @param string|Page|array|Selectors $selector May either be a selector or Page to stop at. Results will not include this. * @param string|array $filter Optional selector to filter matched pages by * @param array $options Options to pass to the _next() method * @return PageArray