mirror of
https://github.com/processwire/processwire.git
synced 2025-08-06 14:57:10 +02:00
Refactoring and optimizations to PagesLoader class, plus addition of a new $pages->has('selector') API method that enables you to quickly check if a page exists matching a selector. This is an alternative to using the $pages->count() method which is what you would have previously used for this. But the $pages->has() method can do it more quickly than $pages->count(), if all you need to know is if a page exists. As a bonus, it returns the first found page ID, so this method essentially does the same thing as a $pages->getID() method (if such a method existed).
This commit is contained in:
@@ -217,15 +217,15 @@ class Pages extends Wire {
|
||||
*
|
||||
* @param string|int|array|Selectors $selector Specify selector (standard usage), but can also accept page ID or array of page IDs.
|
||||
* @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).
|
||||
* - `findOne` (bool): Apply optimizations for finding a single page (default=false).
|
||||
* - `findAll` (bool): Find all pages with no exclusions, same as "include=all" option (default=false).
|
||||
* - `findIDs` (bool|int): 1 to get array of page IDs, true to return verbose array, 2 to return verbose array with all cols in 3.0.153+. (default=false).
|
||||
* - `getTotal` (bool): Whether to set returning PageArray's "total" property (default=true, except when findOne=true).
|
||||
* - `loadPages` (bool): 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
|
||||
* the PageArray: getTotal(), getStart(), getLimit, etc. This is intended as an optimization for $pages->count().
|
||||
* Does not apply if $selector argument is an array.
|
||||
* - `cache` (boolean): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache].
|
||||
* - `cache` (bool): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache].
|
||||
* - `allowCustom` (boolean): Allow use of _custom="another selector" in given $selector? For specific uses. (default=false)
|
||||
* - `caller` (string): Optional name of calling function, for debugging purposes, i.e. "pages.count" (default=blank).
|
||||
* - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. (default=none). Typically you would specify this
|
||||
@@ -342,7 +342,7 @@ class Pages extends Wire {
|
||||
* @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 associative arrays, each with verbose info.
|
||||
* - The verbose option above can also be specified by providing boolean true as the $options argument.
|
||||
* - The verbose option above can also be specified as alternative to the $options argument.
|
||||
* - See `Pages::find()` $options argument for additional options.
|
||||
* @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
|
||||
@@ -350,13 +350,20 @@ class Pages extends Wire {
|
||||
*/
|
||||
public function findIDs($selector, $options = array()) {
|
||||
$verbose = false;
|
||||
if($options === true) $verbose = true;
|
||||
if(!is_array($options)) $options = array();
|
||||
if(!is_array($options)) {
|
||||
// verbose option specified in $options array
|
||||
$verbose = $options;
|
||||
$options = array();
|
||||
}
|
||||
if(isset($options['verbose'])) {
|
||||
$verbose = $options['verbose'];
|
||||
unset($options['verbose']);
|
||||
}
|
||||
$options['findIDs'] = $verbose ? true : 1;
|
||||
if($verbose === 2 || $verbose === '*') {
|
||||
$options['findIDs'] = 2;
|
||||
} else {
|
||||
$options['findIDs'] = $verbose ? true : 1;
|
||||
}
|
||||
/** @var array $ids */
|
||||
$ids = $this->find($selector, $options);
|
||||
return $ids;
|
||||
@@ -389,7 +396,29 @@ class Pages extends Wire {
|
||||
public function get($selector, $options = array()) {
|
||||
return $this->loader->get($selector, $options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is there any page that matches the given $selector in the system? (with no exclusions)
|
||||
*
|
||||
* - This can be used as an “exists” or “getID” type of method.
|
||||
* - Returns ID of first matching page if any exist, or 0 if none exist (returns array if `$verbose` is true).
|
||||
* - Like with the `get()` method, no pages are excluded, so an `include=all` is not necessary in selector.
|
||||
* - If you need to quickly check if something exists, this method is preferable to using a count() or get().
|
||||
*
|
||||
* When `$verbose` option is used, an array is returned instead. Verbose return array includes page `id`,
|
||||
* `parent_id` and `templates_id` indexes.
|
||||
*
|
||||
* @param string|int|array|Selectors $selector
|
||||
* @param bool $verbose Return verbose array with page id, parent_id, templates_id rather than just page id? (default=false)
|
||||
* @return array|int
|
||||
* @since 3.0.153
|
||||
* @see Pages::count(), Pages::get()
|
||||
*
|
||||
*/
|
||||
public function has($selector, $verbose = false) {
|
||||
return $this->loader->has($selector, $verbose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a page object and its fields to database.
|
||||
*
|
||||
|
@@ -109,62 +109,44 @@ class PagesLoader extends Wire {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for find() method to attempt to shortcut the find when possible
|
||||
* Normalize a selector string
|
||||
*
|
||||
* @param string|array|Selectors $selector
|
||||
* @param array $options
|
||||
* @param array $loadOptions
|
||||
* @return bool|Page|PageArray Returns boolean false when no shortcut available
|
||||
* @param string $selector
|
||||
* @param bool $convertIDs Normalize to integer ID or array of integer IDs when possible (default=true)
|
||||
* @return array|int|string
|
||||
*
|
||||
*/
|
||||
protected function findShortcut(&$selector, $options, $loadOptions) {
|
||||
protected function normalizeSelectorString($selector, $convertIDs = true) {
|
||||
|
||||
if(empty($selector)) return $this->pages->newPageArray($loadOptions);
|
||||
if(!empty($options['lazy'])) return false;
|
||||
|
||||
$value = false;
|
||||
$filter = empty($options['findAll']);
|
||||
|
||||
if(is_string($selector)) {
|
||||
$selector = trim($selector, ', ');
|
||||
if(ctype_digit($selector)) {
|
||||
// normalize to page ID (int)
|
||||
$selector = (int) $selector;
|
||||
} else if($selector === '/' || $selector === 'path=/') {
|
||||
// normalize selectors that indicate homepage to just be ID 1
|
||||
$selector = (int) $this->wire('config')->rootPageID;
|
||||
} else if($selector[0] == '/') {
|
||||
// if selector begins with a slash, it is referring to a path
|
||||
$selector = "path=$selector";
|
||||
}
|
||||
}
|
||||
$selector = trim($selector, ', ');
|
||||
|
||||
if(is_array($selector)) {
|
||||
// array that is .... not associative .................. not selector array ........ consists of only numbers
|
||||
if(ctype_digit(implode('', array_keys($selector))) && !is_array(reset($selector)) && ctype_digit(implode('', $selector))) {
|
||||
// regular array of page IDs, we delegate that to getById() method, but with access/visibility control
|
||||
foreach($selector as $k => $v) $selector[$k] = (int) $v;
|
||||
$value = $this->getById($selector, $loadOptions);
|
||||
$filter = true;
|
||||
}
|
||||
if(ctype_digit($selector)) {
|
||||
// normalize to page ID (int)
|
||||
$selector = (int) $selector;
|
||||
|
||||
} else if(is_int($selector)) {
|
||||
// page ID integer
|
||||
$value = $this->getById(array($selector), $loadOptions);
|
||||
} else if($selector === '/' || $selector === 'path=/') {
|
||||
// normalize selectors that indicate homepage to just be ID 1
|
||||
$selector = (int) $this->wire('config')->rootPageID;
|
||||
|
||||
} else if($selector[0] === '/') {
|
||||
// if selector begins with a slash, it is referring to a path
|
||||
$selector = "path=$selector";
|
||||
|
||||
} else if(is_string($selector) && strpos($selector, ',') === false) {
|
||||
// there is just one “key=value” or “value” selector
|
||||
if(strpos($selector, 'id=') === 0) {
|
||||
// string like id=123 or id=123|456|789
|
||||
$s = substr($selector, 3); // skip over 'id='
|
||||
if(ctype_digit($s)) {
|
||||
// id=123
|
||||
$value = $this->getById((int) $s, $loadOptions);
|
||||
} else if(strpos($selector, '|') && ctype_digit(str_replace('|', '', $s))) {
|
||||
// id=123|456|789
|
||||
$a = explode('|', $s);
|
||||
foreach($a as $k => $v) $a[$k] = (int) $v;
|
||||
$value = $this->getById($a, $loadOptions);
|
||||
} else if(strpos($selector, ',') === false) {
|
||||
// there is just one “key=value” or “value” selector that needs further processing
|
||||
if(strpos($selector, 'id=')) {
|
||||
if($convertIDs) {
|
||||
// string like id=123 or id=123|456|789 converted to int or int-array
|
||||
$s = substr($selector, 3); // skip over 'id='
|
||||
if(ctype_digit($s)) {
|
||||
// id=123
|
||||
$selector = (int) $s;
|
||||
} else if(strpos($selector, '|') && ctype_digit(str_replace('|', '', $s))) {
|
||||
// id=123|456|789
|
||||
$a = explode('|', $s);
|
||||
foreach($a as $k => $v) $a[$k] = (int) $v;
|
||||
$selector = $a;
|
||||
}
|
||||
}
|
||||
} else if(!Selectors::stringHasOperator($selector)) {
|
||||
// no operator indicates this is just referring to a page name
|
||||
@@ -176,6 +158,91 @@ class PagesLoader extends Wire {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(is_int($selector) || ctype_digit("$selector")) {
|
||||
// page ID integer
|
||||
if($convertIDs) {
|
||||
$selector = (int) $selector;
|
||||
} else {
|
||||
$selector = "id=$selector";
|
||||
}
|
||||
}
|
||||
|
||||
return $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a selector
|
||||
*
|
||||
* @param string|int|array $selector
|
||||
* @param bool $convertIDs Convert ID-only selectors to integers or arrays of integers?
|
||||
* @return array|int|string
|
||||
*
|
||||
*/
|
||||
protected function normalizeSelector($selector, $convertIDs = true) {
|
||||
|
||||
if(empty($selector)) return '';
|
||||
|
||||
if(is_int($selector)) {
|
||||
if(!$convertIDs) $selector = "id=$selector";
|
||||
} else if(is_string($selector)) {
|
||||
$selector = $this->normalizeSelectorString($selector, $convertIDs);
|
||||
} else if(is_array($selector)) {
|
||||
// array that is not associative, not selector array, and consists of only numbers
|
||||
if($this->isIdArray($selector)) {
|
||||
if(!$convertIDs) $selector = 'id=' . implode('|', $selector);
|
||||
}
|
||||
}
|
||||
|
||||
return $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an array of IDs? Also sanitizes to all integers when true
|
||||
*
|
||||
* @param array $a
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
protected function isIdArray(array &$a) {
|
||||
if(ctype_digit(implode('', array_keys($a))) && !is_array(reset($a)) && ctype_digit(implode('', $a))) {
|
||||
// regular array of page IDs, we delegate that to getById() method, but with access/visibility control
|
||||
foreach($a as $k => $v) $a[$k] = (int) $v;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for find() method to attempt to shortcut the find when possible
|
||||
*
|
||||
* @param string|array|Selectors $selector
|
||||
* @param array $options
|
||||
* @param array $loadOptions
|
||||
* @return bool|Page|PageArray Returns boolean false when no shortcut available
|
||||
*
|
||||
*/
|
||||
protected function findShortcut($selector, $options, $loadOptions) {
|
||||
|
||||
if(empty($selector)) {
|
||||
return $this->pages->newPageArray($loadOptions);
|
||||
}
|
||||
|
||||
$value = false;
|
||||
$filter = empty($options['findAll']);
|
||||
$selector = $this->normalizeSelector($selector, true);
|
||||
|
||||
if(is_array($selector)) {
|
||||
if($this->isIdArray($selector)) {
|
||||
$value = $this->getById($selector, $loadOptions);
|
||||
$filter = true;
|
||||
}
|
||||
|
||||
} else if(is_int($selector)) {
|
||||
// page ID integer
|
||||
$value = $this->getById(array($selector), $loadOptions);
|
||||
}
|
||||
|
||||
if($value) {
|
||||
if($filter) {
|
||||
@@ -200,7 +267,7 @@ class PagesLoader extends Wire {
|
||||
* @param array|string $options Optional one or more options that can modify certain behaviors. May be assoc array or key=value string.
|
||||
* - findOne: boolean - apply optimizations for finding a single page
|
||||
* - findAll: boolean - find all pages with no exclusions (same as include=all option)
|
||||
* - findIDs: boolean|int - true=return array of [id, template_id, parent_id], or 1=return just page IDs.
|
||||
* - findIDs: boolean|int - true=return array of [id, template_id, parent_id], or 1=return just page IDs, 2=return all columns (3.0.153+).
|
||||
* - getTotal: boolean - whether to set returning PageArray's "total" property (default: true except when findOne=true)
|
||||
* - cache: boolean - Allow caching of selectors and pages loaded (default=true). Also sets loadOptions[cache].
|
||||
* - allowCustom: boolean - Whether to allow use of "_custom=new selector" in selectors (default=false).
|
||||
@@ -239,6 +306,7 @@ class PagesLoader extends Wire {
|
||||
if($selector instanceof Selectors) {
|
||||
$selectors = $selector;
|
||||
} else {
|
||||
$selector = $this->normalizeSelector($selector, false);
|
||||
$selectors = $this->wire(new Selectors());
|
||||
$selectors->init($selector);
|
||||
}
|
||||
@@ -273,7 +341,10 @@ class PagesLoader extends Wire {
|
||||
$pagesIDs = $pageFinder->findTemplateIDs($selectors, $options);
|
||||
} else if($findIDs === 1) {
|
||||
// [ pageID ]
|
||||
$pagesIDs = $pageFinder->findIDs($selectors, $options);
|
||||
$pagesIDs = $pageFinder->findIDs($selectors, $options);
|
||||
} else if($findIDs === 2) {
|
||||
// [ pageID => [ all pages columns ] ]
|
||||
$pagesInfo = $pageFinder->findVerboseIDs($selectors, $options);
|
||||
} else {
|
||||
// [ [ 'id' => 3, 'templates_id' => 2, 'parent_id' => 1 ]
|
||||
$pagesInfo = $pageFinder->find($selectors, $options);
|
||||
@@ -504,6 +575,52 @@ class PagesLoader extends Wire {
|
||||
if(!$page) $page = $this->pages->newNullPage();
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there any page that matches the given $selector in the system? (with no exclusions)
|
||||
*
|
||||
* - This can be used as an “exists” or “getID” type of method.
|
||||
* - Returns ID of first matching page if any exist, or 0 if none exist (returns array if `$verbose` is true).
|
||||
* - Like with the `get()` method, no pages are excluded, so an `include=all` is not necessary in selector.
|
||||
* - If you need to quickly check if something exists, this method is preferable to using a count() or get().
|
||||
*
|
||||
* When `$verbose` option is used, an array is returned instead. Verbose return array includes page `id`,
|
||||
* `parent_id` and `templates_id` indexes.
|
||||
*
|
||||
* @param string|int|array|Selectors $selector
|
||||
* @param bool $verbose Return verbose array with all pages columns rather than just page id? (default=false)
|
||||
* @return array|int
|
||||
* @since 3.0.153
|
||||
*
|
||||
*/
|
||||
public function has($selector, $verbose = false) {
|
||||
|
||||
$options = array(
|
||||
'findOne' => true, // find only one page
|
||||
'findAll' => true, // no exclusions
|
||||
'findIDs' => $verbose ? 2 : 1, // 1=find IDs, true=find verbose all cols
|
||||
'getTotal' => false, // don't count totals
|
||||
'caller' => 'pages.has',
|
||||
);
|
||||
|
||||
if(empty($selector)) return $verbose ? array() : 0;
|
||||
|
||||
if((is_string($selector) || is_int($selector)) && !$verbose) {
|
||||
// see if any matching page is already in the cache
|
||||
$page = $this->pages->getCache($selector);
|
||||
if($page) return $page->id;
|
||||
}
|
||||
|
||||
$items = $this->pages->find($selector, $options);
|
||||
|
||||
if($verbose) {
|
||||
$value = count($items) ? reset($items) : array();
|
||||
} else {
|
||||
$value = count($items) ? (int) reset($items) : 0;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array or CSV string of Page IDs, return a PageArray
|
||||
|
Reference in New Issue
Block a user