1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 16:26:59 +02:00

Update to allow for Page objects to load pages (children, parent, etc.) that don't get cached when their load option specified cache=false. Also makes the 'parent' page lazy loading for Page objects, so that it doesn't load the parent Page until a $page->parent() call is requested. Updates for @apeisa / Avoine request.

This commit is contained in:
Ryan Cramer
2016-12-14 11:02:32 -05:00
parent 3821dc2be3
commit 2b9a7adbcb
10 changed files with 343 additions and 181 deletions

View File

@@ -46,6 +46,9 @@ if(!defined("PROCESSWIRE")) die();
* always have this disabled for live/production sites since it reveals more information
* than is advisible for security.
*
* You may also set this to the constant `Config::debugVerbose` to enable verbose debug mode,
* which uses more memory and time.
*
* #notes This enables debug mode for ALL requests. See the debugIf option for an alternative.
*
* @var bool

View File

@@ -79,7 +79,7 @@
*
* @property bool $advanced Special mode for ProcessWire system development. Not recommended for regular site development or production use. #pw-group-system
* @property bool $demo Special mode for demonstration use that causes POST requests to be disabled. Applies to core, but may not be safe with 3rd party modules. #pw-group-system
* @property bool $debug Special mode for use when debugging or developing a site. Recommended TRUE when site is in development and FALSE when not. #pw-group-system
* @property bool|int $debug Special mode for use when debugging or developing a site. Recommended TRUE when site is in development and FALSE when not. Or set to Config::debugVerbose for verbose debug mode. #pw-group-system
* @property string $debugIf Enable debug mode if condition is met #pw-group-system
* @property array $debugTools Tools, and their order, to show in debug mode (admin) #pw-group-system
*
@@ -156,6 +156,12 @@
*/
class Config extends WireData {
/**
* Constant for verbose debug mode (uses more memory)
*
*/
const debugVerbose = 2;
/**
* Get URL for requested resource or module
*

View File

@@ -72,6 +72,7 @@
* @property string $editUrl URL that this page can be edited at. #pw-group-advanced
* @property string $editURL Alias of $editUrl. #pw-internal
* @property PageRender $render May be used for field markup rendering like $page->render->title. #pw-advanced
* @property bool $loaderCache Whether or not pages loaded as a result of this one may be cached by PagesLoaderCache. #pw-internal
*
* @property bool|null $_hasAutogenName Internal runtime use, set by Pages class when page as auto-generated name. #pw-internal
* @property bool|null $_forceSaveParents Internal runtime/debugging use, force a page to refresh its pages_parents DB entries on save(). #pw-internal
@@ -278,7 +279,15 @@ class Page extends WireData implements \Countable, WireMatchable {
* @var Page|null
*
*/
protected $parent = null;
protected $_parent = null;
/**
* Parent ID for lazy loading purposes
*
* @var int
*
*/
protected $_parent_id = 0;
/**
* The previous parent used by the page, if it was changed during runtime.
@@ -368,6 +377,14 @@ class Page extends WireData implements \Countable, WireMatchable {
*/
protected $lazyLoad = false;
/**
* Whether or not pages loaded by this one are allowed to be cached by PagesLoaderCache class
*
* @var bool
*
*/
protected $loaderCache = true;
/**
* Is this page allowing it's output to be formatted?
*
@@ -698,9 +715,18 @@ class Page extends WireData implements \Countable, WireMatchable {
break;
case 'parent':
case 'parent_id':
if(($key == 'parent_id' || is_int($value)) && $value) $value = $this->wire('pages')->get((int)$value);
else if(is_string($value)) $value = $this->wire('pages')->get($value);
if($value) $this->setParent($value);
if(is_object($value) && $value instanceof Page) {
// ok
$this->setParent($value);
} else if($value && !$this->_parent &&
($key == 'parent_id' || is_int($value) || (is_string($value) && ctype_digit("$value")))) {
// store only parent ID so that parent is lazy loaded,
// but only if parent hasn't already been previously loaded
$this->_parent_id = (int) $value;
} else if($value && (is_string($value) || is_int($value))) {
$value = $this->_pages('get', $value);
$this->setParent($value);
}
break;
case 'parentPrevious':
if(is_null($value) || $value instanceof Page) $this->parentPrevious = $value;
@@ -748,6 +774,9 @@ class Page extends WireData implements \Countable, WireMatchable {
$this->instanceID = $value;
self::$instanceIDs[$value] = $this->settings['id'];
break;
case 'loaderCache':
$this->loaderCache = (bool) $value;
break;
default:
if(strpos($key, 'name') === 0 && ctype_digit(substr($key, 5)) && $this->wire('languages')) {
// i.e. name1234
@@ -953,8 +982,12 @@ class Page extends WireData implements \Countable, WireMatchable {
}
switch($key) {
case 'parent':
$value = $this->_parent ? $this->_parent : $this->parent();
break;
case 'parent_id':
$value = $this->parent ? $this->parent->id : 0;
$value = $this->_parent ? $this->_parent->id : 0;
if(!$value) $value = $this->_parent_id;
break;
case 'templates_id':
$value = $this->template ? $this->template->id : 0;
@@ -994,6 +1027,9 @@ class Page extends WireData implements \Countable, WireMatchable {
$value = $this->wire('modules')->get('PageRender');
$value->setPropertyPage($this);
break;
case 'loaderCache':
$value = $this->loaderCache;
break;
default:
if($key && isset($this->settings[(string)$key])) return $this->settings[$key];
@@ -1725,20 +1761,21 @@ class Page extends WireData implements \Countable, WireMatchable {
*
*/
public function setParent(Page $parent) {
if($this->parent && $this->parent->id == $parent->id) return $this;
if($this->_parent && $this->_parent->id == $parent->id) return $this;
if($parent->id && $this->id == $parent->id || $parent->parents->has($this)) {
throw new WireException("Page cannot be its own parent");
}
if($this->isLoaded) {
$this->trackChange('parent', $this->parent, $parent);
if(($this->parent && $this->parent->id) && $this->parent->id != $parent->id) {
$this->trackChange('parent', $this->_parent, $parent);
if(($this->_parent && $this->_parent->id) && $this->_parent->id != $parent->id) {
if($this->settings['status'] & Page::statusSystem) {
throw new WireException("Parent changes are disallowed on this page");
}
if(is_null($this->parentPrevious)) $this->parentPrevious = $this->parent;
if(is_null($this->parentPrevious)) $this->parentPrevious = $this->_parent;
}
}
$this->parent = $parent;
$this->_parent = $parent;
$this->_parent_id = $parent->id;
return $this;
}
@@ -1809,7 +1846,7 @@ class Page extends WireData implements \Countable, WireMatchable {
} else if(is_array($selector)) {
$selector["has_parent"] = $this->id;
}
return $this->wire('pages')->find($selector, $options);
return $this->_pages('find', $selector, $options);
}
/**
@@ -1950,10 +1987,16 @@ class Page extends WireData implements \Countable, WireMatchable {
*
*/
public function parent($selector = '') {
if(!$this->parent) return $this->wire('pages')->newNullPage();
if(empty($selector)) return $this->parent;
if($this->parent->matches($selector)) return $this->parent;
if($this->parent->parent_id) return $this->parent->parent($selector); // recursive, in a way
if(!$this->_parent) {
if($this->_parent_id) {
$this->_parent = $this->_pages('get', (int) $this->_parent_id);
} else {
return $this->wire('pages')->newNullPage();
}
}
if(empty($selector)) return $this->_parent;
if($this->_parent->matches($selector)) return $this->_parent;
if($this->_parent->parent_id) return $this->_parent->parent($selector); // recursive, in a way
return $this->wire('pages')->newNullPage();
}
@@ -3709,5 +3752,25 @@ class Page extends WireData implements \Countable, WireMatchable {
throw new WireException("Invalid arguments to Page::lazy()");
}
}
/**
* Handles get/find loads specific to this Page from the $pages API variable
*
* #pw-internal
*
* @param string $method The $pages API method to call (get, find, findOne, or count)
* @param string|int $selector The selector argument of the $pages call
* @param array $options Any additional options (see Pages::find for options).
* @return Pages|Page|PageArray|NullPage
* @throws WireException
*
*/
public function _pages($method = '', $selector = '', $options = array()) {
if(empty($method)) return $this->wire('pages');
if(!isset($options['cache'])) $options['cache'] = $this->loaderCache;
if(!isset($options['caller'])) $options['caller'] = "page._pages.$method";
$result = $this->wire('pages')->$method($selector, $options);
return $result;
}
}

View File

@@ -34,15 +34,17 @@ class PageTraversal {
// onlyVisible takes the place of selector
$onlyVisible = $selector;
if(!$onlyVisible) return $page->get('numChildren');
return $page->wire('pages')->count("parent_id=$page->id");
return $page->_pages('count', "parent_id=$page->id");
} else if($selector === 1) {
// viewable pages only
$numChildren = $page->get('numChildren');
if(!$numChildren) return 0;
if($page->wire('user')->isSuperuser()) return $numChildren;
if($page->wire('user')->hasPermission('page-edit')) return $page->wire('pages')->count("parent_id=$page->id, include=unpublished");
return $page->wire('pages')->count("parent_id=$page->id, include=hidden");
if($page->wire('user')->hasPermission('page-edit')) {
return $page->_pages('count', "parent_id=$page->id, include=unpublished");
}
return $page->_pages('count', "parent_id=$page->id, include=hidden");
} else if(empty($selector) || (!is_string($selector) && !is_array($selector))) {
return $page->get('numChildren');
@@ -53,7 +55,7 @@ class PageTraversal {
} else if(is_array($selector)) {
$selector["parent_id"] = $page->id;
}
return $page->wire('pages')->count($selector);
return $page->_pages('count', $selector);
}
}
@@ -67,7 +69,7 @@ class PageTraversal {
*
*/
public function children(Page $page, $selector = '', $options = array()) {
if(!$page->numChildren) return $page->wire('pages')->newPageArray();
if(!$page->numChildren) return $page->_pages()->newPageArray();
$defaults = array('caller' => 'page.children');
$options = array_merge($defaults, $options);
$sortfield = $page->sortfield();
@@ -81,7 +83,7 @@ class PageTraversal {
$selector = trim("parent_id=$page->id, $selector", ", ");
if(strpos($selector, 'sort=') === false) $selector .= ", sort=$sortfield";
}
return $page->wire('pages')->find($selector, $options);
return $page->_pages('find', $selector, $options);
}
/**
@@ -96,7 +98,7 @@ class PageTraversal {
*
*/
public function child(Page $page, $selector = '', $options = array()) {
if(!$page->numChildren) return $page->wire('pages')->newNullPage();
if(!$page->numChildren) return $page->_pages()->newNullPage();
$defaults = array('getTotal' => false, 'caller' => 'page.child');
$options = array_merge($defaults, $options);
if(is_array($selector)) {
@@ -107,7 +109,7 @@ class PageTraversal {
if(strpos($selector, 'start=') === false) $selector .= ", start=0"; // prevent pagination
}
$children = $this->children($page, $selector, $options);
return count($children) ? $children->first() : $page->wire('pages')->newNullPage();
return count($children) ? $children->first() : $page->_pages()->newNullPage();
}
/**
@@ -211,7 +213,7 @@ class PageTraversal {
$selector = trim($selector, ", ");
}
$options = array('caller' => 'page.siblings');
return $page->wire('pages')->find($selector, $options);
return $page->_pages('find', $selector, $options);
}
/**
@@ -247,18 +249,18 @@ class PageTraversal {
} else if(strpos($until, '/') === 0) {
// page path
$stopPage = $pages->get($until);
$stopPage = $page->_pages('get', $until);
} else if(is_array($selector) || is_array($options['until'])) {
// either selector or until is an array
$s = new Selectors($options['until']);
foreach(new Selectors($selector) as $item) $s->add($item);
$s->add(new SelectorEqual('limit', 1));
$stopPage = $page->wire('pages')->find($s)->first();
$stopPage = $page->_pages('find', $s)->first();
} else {
// selector string
$stopPage = $page->wire('pages')->find("$selector, limit=1, $until")->first();
$stopPage = $page->_pages('find', "$selector, limit=1, $until")->first();
}
if($stopPage && $stopPage->id) {
@@ -324,6 +326,7 @@ class PageTraversal {
} else if($options['all']) {
$result = $pages->getById($rows, array(
'parent_id' => $parent->id,
'cache' => $page->loaderCache
));
if($options['all'] && $options['prev']) $result = $result->reverse();
@@ -332,7 +335,8 @@ class PageTraversal {
$result = $pages->getById(array($row['id']), array(
'template' => $page->wire('templates')->get($row['templates_id']),
'parent_id' => $row['parent_id'],
'getOne' => true
'getOne' => true,
'cache' => $page->loaderCache
));
}
@@ -448,6 +452,128 @@ class PageTraversal {
return $this->_next($page, $filter, $options);
}
/**
* Returns the URL to the page with $options
*
* You can specify an `$options` argument to this method with any of the following:
*
* - `pageNum` (int|string): Specify pagination number, or "+" for next pagination, or "-" for previous pagination.
* - `urlSegmentStr` (string): Specify a URL segment string to append.
* - `urlSegments` (array): Specify array of URL segments to append (may be used instead of urlSegmentStr).
* - `data` (array): Array of key=value variables to form a query string.
* - `http` (bool): Specify true to make URL include scheme and hostname (default=false).
* - `language` (Language): Specify Language object to return URL in that Language.
*
* You can also specify any of the following for `$options` as shortcuts:
*
* - If you specify an `int` for options it is assumed to be the `pageNum` option.
* - If you specify `+` or `-` for options it is assumed to be the `pageNum` “next/previous pagination” option.
* - If you specify any other `string` for options it is assumed to be the `urlSegmentStr` option.
* - If you specify a `boolean` (true) for options it is assumed to be the `http` option.
*
* Please also note regarding `$options`:
*
* - This method honors template slash settings for page, URL segments and page numbers.
* - Any passed in URL segments are automatically sanitized with `Sanitizer::pageNameUTF8()`.
* - If using the `pageNum` or URL segment options please also make sure these are enabled on the pages template.
* - The query string generated by any `data` variables is entity encoded when output formatting is on.
* - The `language` option requires that the `LanguageSupportPageNames` module is installed.
* - The prefix for page numbers honors `$config->pageNumUrlPrefix` and multi-language prefixes as well.
*
* @param Page $page
* @param array|int|string|bool|Language $options Optionally specify options to modify default behavior (see method description).
* @return string Returns page URL, for example: `/my-site/about/contact/`
* @see Page::path(), Page::httpUrl(), Page::editUrl(), Page::localUrl()
*
*/
public function urlOptions(Page $page, $options = array()) {
$config = $page->wire('config');
$defaults = array(
'http' => is_bool($options) ? $options : false,
'pageNum' => is_int($options) || (is_string($options) && in_array($options, array('+', '-'))) ? $options : 1,
'data' => array(),
'urlSegmentStr' => is_string($options) ? $options : '',
'urlSegments' => array(),
'language' => is_object($options) && $options instanceof Page && $options->className() === 'Language' ? $options : null,
);
if(empty($options)) return rtrim($config->urls->root, '/') . $page->path();
$options = is_array($options) ? array_merge($defaults, $options) : $defaults;
$template = $page->template;
$sanitizer = $page->wire('sanitizer');
$language = null;
$url = null;
if(count($options['urlSegments'])) {
$options['urlSegmentStr'] = implode('/', $options['urlSegments']);
}
if($options['language'] && $page->wire('modules')->isInstalled('LanguageSupportPageNames')) {
if(!is_object($options['language'])) {
$options['language'] = null;
} else if(!$options['language'] instanceof Page) {
$options['language'] = null;
} else if(strpos($options['language']->className(), 'Language') === false) {
$options['language'] = null;
}
if($options['language']) {
/** @var Language $language */
$language = $options['language'];
// localUrl method provided as hook by LanguageSupportPageNames
$url = $page->localUrl($language);
}
}
if(is_null($url)) {
$url = rtrim($config->urls->root, '/') . $page->path();
if($template->slashUrls === 0 && $page->id > 1) $url = rtrim($url, '/');
}
if(is_string($options['urlSegmentStr']) && strlen($options['urlSegmentStr'])) {
$url = rtrim($url, '/') . '/' . $sanitizer->pagePathNameUTF8(trim($options['urlSegmentStr'], '/'));
if($template->slashUrlSegments === '' || $template->slashUrlSegments) $url .= '/';
}
if($options['pageNum']) {
if($options['pageNum'] === '+') {
$options['pageNum'] = $page->wire('input')->pageNum + 1;
} else if($options['pageNum'] === '-' || $options['pageNum'] === -1) {
$options['pageNum'] = $page->wire('input')->pageNum - 1;
}
if((int) $options['pageNum'] > 1) {
$prefix = '';
if($language) {
$lsp = $page->wire('modules')->get('LanguageSupportPageNames');
$prefix = $lsp ? $lsp->get("pageNumUrlPrefix$language") : '';
}
if(!strlen($prefix)) $prefix = $config->pageNumUrlPrefix;
$url = rtrim($url, '/') . '/' . $prefix . ((int) $options['pageNum']);
if($template->slashPageNum) $url .= '/';
}
}
if(count($options['data'])) {
$query = http_build_query($options['data']);
if($page->of()) $query = $sanitizer->entities($query);
$url .= '?' . $query;
}
if($options['http']) {
switch($template->https) {
case -1: $scheme = 'http'; break;
case 1: $scheme = 'https'; break;
default: $scheme = $config->https ? 'https' : 'http';
}
$url = "$scheme://" . $page->wire('config')->httpHost . $url;
}
return $url;
}
/******************************************************************************************************************
* LEGACY METHODS
*
@@ -709,127 +835,6 @@ class PageTraversal {
return $all;
}
/**
* Returns the URL to the page with $options
*
* You can specify an `$options` argument to this method with any of the following:
*
* - `pageNum` (int|string): Specify pagination number, or "+" for next pagination, or "-" for previous pagination.
* - `urlSegmentStr` (string): Specify a URL segment string to append.
* - `urlSegments` (array): Specify array of URL segments to append (may be used instead of urlSegmentStr).
* - `data` (array): Array of key=value variables to form a query string.
* - `http` (bool): Specify true to make URL include scheme and hostname (default=false).
* - `language` (Language): Specify Language object to return URL in that Language.
*
* You can also specify any of the following for `$options` as shortcuts:
*
* - If you specify an `int` for options it is assumed to be the `pageNum` option.
* - If you specify `+` or `-` for options it is assumed to be the `pageNum` “next/previous pagination” option.
* - If you specify any other `string` for options it is assumed to be the `urlSegmentStr` option.
* - If you specify a `boolean` (true) for options it is assumed to be the `http` option.
*
* Please also note regarding `$options`:
*
* - This method honors template slash settings for page, URL segments and page numbers.
* - Any passed in URL segments are automatically sanitized with `Sanitizer::pageNameUTF8()`.
* - If using the `pageNum` or URL segment options please also make sure these are enabled on the pages template.
* - The query string generated by any `data` variables is entity encoded when output formatting is on.
* - The `language` option requires that the `LanguageSupportPageNames` module is installed.
* - The prefix for page numbers honors `$config->pageNumUrlPrefix` and multi-language prefixes as well.
*
* @param Page $page
* @param array|int|string|bool|Language $options Optionally specify options to modify default behavior (see method description).
* @return string Returns page URL, for example: `/my-site/about/contact/`
* @see Page::path(), Page::httpUrl(), Page::editUrl(), Page::localUrl()
*
*/
public function urlOptions(Page $page, $options = array()) {
$config = $page->wire('config');
$defaults = array(
'http' => is_bool($options) ? $options : false,
'pageNum' => is_int($options) || (is_string($options) && in_array($options, array('+', '-'))) ? $options : 1,
'data' => array(),
'urlSegmentStr' => is_string($options) ? $options : '',
'urlSegments' => array(),
'language' => is_object($options) && $options instanceof Page && $options->className() === 'Language' ? $options : null,
);
if(empty($options)) return rtrim($config->urls->root, '/') . $page->path();
$options = is_array($options) ? array_merge($defaults, $options) : $defaults;
$template = $page->template;
$sanitizer = $page->wire('sanitizer');
$language = null;
$url = null;
if(count($options['urlSegments'])) {
$options['urlSegmentStr'] = implode('/', $options['urlSegments']);
}
if($options['language'] && $page->wire('modules')->isInstalled('LanguageSupportPageNames')) {
if(!is_object($options['language'])) {
$options['language'] = null;
} else if(!$options['language'] instanceof Page) {
$options['language'] = null;
} else if(strpos($options['language']->className(), 'Language') === false) {
$options['language'] = null;
}
if($options['language']) {
/** @var Language $language */
$language = $options['language'];
// localUrl method provided as hook by LanguageSupportPageNames
$url = $page->localUrl($language);
}
}
if(is_null($url)) {
$url = rtrim($config->urls->root, '/') . $page->path();
if($template->slashUrls === 0 && $page->id > 1) $url = rtrim($url, '/');
}
if(is_string($options['urlSegmentStr']) && strlen($options['urlSegmentStr'])) {
$url = rtrim($url, '/') . '/' . $sanitizer->pagePathNameUTF8(trim($options['urlSegmentStr'], '/'));
if($template->slashUrlSegments === '' || $template->slashUrlSegments) $url .= '/';
}
if($options['pageNum']) {
if($options['pageNum'] === '+') {
$options['pageNum'] = $page->wire('input')->pageNum + 1;
} else if($options['pageNum'] === '-' || $options['pageNum'] === -1) {
$options['pageNum'] = $page->wire('input')->pageNum - 1;
}
if((int) $options['pageNum'] > 1) {
$prefix = '';
if($language) {
$lsp = $page->wire('modules')->get('LanguageSupportPageNames');
$prefix = $lsp ? $lsp->get("pageNumUrlPrefix$language") : '';
}
if(!strlen($prefix)) $prefix = $config->pageNumUrlPrefix;
$url = rtrim($url, '/') . '/' . $prefix . ((int) $options['pageNum']);
if($template->slashPageNum) $url .= '/';
}
}
if(count($options['data'])) {
$query = http_build_query($options['data']);
if($page->of()) $query = $sanitizer->entities($query);
$url .= '?' . $query;
}
if($options['http']) {
switch($template->https) {
case -1: $scheme = 'http'; break;
case 1: $scheme = 'https'; break;
default: $scheme = $config->https ? 'https' : 'http';
}
$url = "$scheme://" . $page->wire('config')->httpHost . $url;
}
return $url;
}
/**
* Return the next or previous sibling page (new fast version)
*

View File

@@ -138,12 +138,12 @@ class Pages extends Wire {
*/
public function __construct(ProcessWire $wire) {
$this->setWire($wire);
$this->debug = $wire->config->debug === Config::debugVerbose ? true : false;
$this->sortfields = $this->wire(new PagesSortfields());
$this->loader = $this->wire(new PagesLoader($this));
$this->cacher = $this->wire(new PagesLoaderCache($this));
$this->trasher = null;
$this->editor = null;
$this->debug = $wire->config->debug;
}
/**
@@ -316,12 +316,13 @@ class Pages extends Wire {
* #pw-group-retrieval
*
* @param string|array|Selectors|int $selector Selector string, array or Selectors object. May also be page path or ID.
* @param array $options See `Pages::find()` for extra options that may be specified.
* @return Page|NullPage Always returns a Page object, but will return NullPage (with id=0) when no match found.
* @see Pages::findOne(), Pages::find()
*
*/
public function get($selector) {
return $this->loader->get($selector);
public function get($selector, $options = array()) {
return $this->loader->get($selector, $options);
}
/**

View File

@@ -40,6 +40,14 @@ class PagesLoader extends Wire {
*/
protected $nativeColumns = array();
/**
* Total number of pages loaded by getById()
*
* @var int
*
*/
protected $totalPagesLoaded = 0;
/**
* Debug mode for pages class
*
@@ -220,6 +228,10 @@ class PagesLoader extends Wire {
$pagesInfo = $pageFinder->find($selectors, $options);
}
if($this->debug && empty($loadOptions['caller'])) {
$loadOptions['caller'] = "$caller($selectorString)";
}
// note that we save this pagination state here and set it at the end of this method
// because it's possible that more find operations could be executed as the pages are loaded
$total = $pageFinder->getTotal();
@@ -309,10 +321,11 @@ class PagesLoader extends Wire {
$note .= ": " . $pages->first()->path;
if($count > 1) $note .= " ... " . $pages->last()->path;
}
Debug::saveTimer("$caller($selectorString)", $note);
if(substr($caller, -1) !== ')') $caller .= "($selectorString)";
Debug::saveTimer($caller, $note);
foreach($pages as $item) {
if($item->_debug_loaded) continue;
$item->setQuietly('_debug_loader', "$caller($selectorString)");
if($item->_debug_loader) continue;
$item->setQuietly('_debug_loader', $caller);
}
}
@@ -358,21 +371,23 @@ class PagesLoader extends Wire {
* Returns the first page matching the given selector with no exclusions
*
* @param string|int|array|Selectors $selector
* @param array $options See Pages::find method for options
* @return Page|NullPage Always returns a Page object, but will return NullPage (with id=0) when no match found
*
*/
public function get($selector) {
public function get($selector, $options = array()) {
if(empty($selector)) return $this->pages->newNullPage();
if(is_string($selector) || is_int($selector)) {
$page = $this->pages->getCache($selector);
if($page) return $page;
}
$options = array(
$defaults = array(
'findOne' => true, // find only one page
'findAll' => true, // no exclusions
'getTotal' => false, // don't count totals
'caller' => 'pages.get'
);
$options = count($options) ? array_merge($defaults, $options) : $defaults;
$page = $this->pages->find($selector, $options)->first();
if(!$page) $page = $this->pages->newNullPage();
return $page;
@@ -398,6 +413,7 @@ class PagesLoader extends Wire {
* - pageClass: string, default=auto-detect. Class to instantiate Page objects with. Leave blank to determine from template.
* - pageArrayClass: string, default=PageArray. PageArray-derived class to store pages in (when 'getOne' is false).
* - page (Page|null): Existing Page object to populate (also requires the getOne option to be true). (default=null)
* - caller (string): Name of calling function, for debugging purposes (default=blank).
*
* Use the $options array for potential speed optimizations:
* - Specify a 'template' with your call, when possible, so that this method doesn't have to determine it separately.
@@ -434,6 +450,7 @@ class PagesLoader extends Wire {
'page' => null,
'pageClass' => '', // blank = auto detect
'pageArrayClass' => 'PageArray',
'caller' => '',
);
if(is_array($template)) {
@@ -651,6 +668,7 @@ class PagesLoader extends Wire {
$page->setOutputFormatting($this->outputFormatting);
$loaded[$page->id] = $page;
if($options['cache']) $this->pages->cache($page);
$this->totalPagesLoaded++;
}
} catch(\Exception $e) {
$error = $e->getMessage() . " [pageClass=$class, template=$template]";
@@ -674,11 +692,18 @@ class PagesLoader extends Wire {
if($this->debug) {
$page = $this->wire('page');
if($page && $page->template == 'admin') {
if(empty($options['caller'])) {
$_template = is_null($template) ? '' : ", $template";
$_parent_id = is_null($parent_id) ? '' : ", $parent_id";
if(count($_ids) > 10) {
$_ids = '[' . reset($_ids) . '…' . end($_ids) . ', ' . count($_ids) . ' pages]';
} else {
$_ids = count($_ids) > 1 ? "[" . implode(',', $_ids) . "]" : implode('', $_ids);
}
$options['caller'] = "pages.getById($_ids$_template$_parent_id)";
}
foreach($pages as $item) {
$item->setQuietly('_debug_loader', "getByID($_ids$_template$_parent_id)");
$item->setQuietly('_debug_loader', $options['caller']);
}
}
}
@@ -1076,4 +1101,14 @@ class PagesLoader extends Wire {
return $value;
}
/**
* Return the total quantity of pages loaded by getById()
*
* @return int
*
*/
public function getTotalPagesLoaded() {
return $this->totalPagesLoaded;
}
}

View File

@@ -256,6 +256,7 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable {
if(!isset($options['findAll'])) $options['findAll'] = true;
if(!isset($options['loadOptions'])) $options['loadOptions'] = array();
$options['loadOptions'] = $this->getLoadOptions($options['loadOptions']);
if(empty($options['caller'])) $options['caller'] = $this->className() . ".find($selectorString)";
$pages = $this->wire('pages')->find($this->selectorString($selectorString), $options);
/** @var PageArray $pages */
foreach($pages as $page) {
@@ -271,13 +272,17 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable {
/**
* Get the first match of your selector string
*
* @param string $selectorString
* @return Page|null
* @param string|int $selectorString
* @return Page|NullPage|null
*
*/
public function get($selectorString) {
$options = $this->getLoadOptions(array('getOne' => true));
if(empty($options['caller'])) {
$caller = $this->className() . ".get($selectorString)";
$options['caller'] = $caller;
}
if(ctype_digit("$selectorString")) {
// selector string contains a page ID
@@ -307,7 +312,10 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable {
// selector string with operators, can pass through
}
$page = $this->pages->get($this->selectorString($selectorString), array('loadOptions' => $options));
$page = $this->pages->get($this->selectorString($selectorString), array(
'caller' => $caller,
'loadOptions' => $options
));
if($page->id && !$this->isValid($page)) $page = $this->wire('pages')->newNullPage();
if($page->id) $this->loaded($page);
@@ -392,7 +400,9 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable {
*
*/
public function getIterator() {
return $this->find("id>0, sort=name");
return $this->find("id>0, sort=name", array(
'caller' => $this->className() . '.getIterator()'
));
}
/**

View File

@@ -168,6 +168,7 @@ class Languages extends PagesType {
$selector = "parent_id=$parent_id, template=$template, include=all, sort=sort";
$languagesAll = $this->wire('pages')->find($selector, array(
'loadOptions' => $this->getLoadOptions(),
'caller' => $this->className() . '.getAll()'
)
);
if(count($languagesAll)) $this->languagesAll = $languagesAll;

View File

@@ -61,6 +61,12 @@
* @property int $editOption Edit option setting, used only by ListerPro
* @property bool $responsiveTable Whether or not Lister table should be responsive rather than horiz scroll
*
* @method string buildListerTableColActions(Page $p, $value)
* @method string renderResults($selector = null)
* @method string getSelector($limit = null)
* @method PageArray findResults($selector)
* @method string executeReset()
*
*
* @todo make system fields hookable for output like markupValue is for custom fields
* @todo show bookmark title somewhere on page so users know what they are looking at (+ browser title?)
@@ -484,7 +490,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*
* @param string $key
* @param mixed $value
* @return this
* @return ProcessPageLister|Process
*
*/
public function set($key, $value) {
@@ -568,7 +574,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
if(!is_null($sort)) {
$sort = $this->sanitizer->name($sort);
$this->sessionSet("sort", $sort);
$this->sort = $sort;
$this->set('sort', $sort);
}
}
@@ -581,6 +587,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$fields = $this->template ? $this->template->fieldgroup : $this->wire('fields');
$options = $this->getSystemColumns();
/** @var InputfieldAsmSelect $f */
$f = $this->modules->get('InputfieldAsmSelect');
$f->attr('name', 'columns');
$f->label = $this->_('Default columns');
@@ -656,6 +663,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
/**
* Whether or not to allow the given $field as a column, for buildColumnsField
*
* @param Field $field
* @return bool
*
*/
protected function allowColumnField(Field $field) {
@@ -696,6 +706,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/
protected function buildColumnsForm() {
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'ProcessListerColumnsForm');
$form->method = 'get';
@@ -721,6 +732,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/
protected function buildFiltersForm() {
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'ProcessListerFiltersForm');
$form->method = 'get';
@@ -734,6 +746,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
if(in_array('noNewFilters', $this->toggles)) $f->allowAddRemove = false;
$form->add($f);
/** @var InputfieldHidden $f */
$f = $this->modules->get('InputfieldHidden');
$f->attr('name', 'sort');
$f->attr('id', 'lister_sort');
@@ -1033,6 +1046,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/
protected function buildListerTable(PageArray $results) {
/** @var MarkupAdminDataTable $table */
$table = $this->modules->get('MarkupAdminDataTable');
$table->setSortable(false);
$table->setResizable(true);
@@ -1141,8 +1155,8 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$delimiter = isset($this->delimiters[$fullname]) ? $this->delimiters[$fullname] : "<br />";
if(!$subname && ($value instanceof Page || $value instanceof PageArray)) {
if($field && $field->labelFieldName) {
$subname = $field->labelFieldName;
if($field && $field->get('labelFieldName')) {
$subname = $field->get('labelFieldName');
if($this->wire('sanitizer')->fieldName($subname) == $subname) $subname .= "|name"; // fallback
} else {
$subname = 'title|name';
@@ -1219,6 +1233,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
if($subname == 'data') $v = (string) $v;
if($subname && is_object($v)) $v = $v->$subname;
if($v instanceof Pageimage) {
/** @var Pageimage $v */
$vfull = $v;
if($this->imageWidth || $this->imageHeight) $v = $v->size($this->imageWidth, $this->imageHeight);
$alt = $vfull->basename . ($vfull->description ? ' - ' . $this->sanitizer->entities1($vfull->description) : "");
@@ -1226,6 +1241,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$isImage = true;
} else if($v instanceof Pagefile) {
/** @var Pageimage $v */
$v = "<a target='_blank' href='$v->url'>$v->basename</a>";
} else {
$v = $this->wire('sanitizer')->entities($v);
@@ -1291,7 +1307,6 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/
protected function ___buildListerTableColActions(Page $p, $value) {
$class = '';
$icon = '';
$statusIcon = '';
$isTrash = false;
if(!strlen($value)) $value = $this->blankLabel;
@@ -1548,13 +1563,13 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$total = $results->getTotal();
$end = $start+$count;
$pagerOut = '';
$errorOut = '';
if(count($results)) {
$table = $this->buildListerTable($results);
$tableOut = $table->render();
$headline = sprintf($this->_('%1$d to %2$d of %3$d'), $start+1, $end, $total);
if($total > $limit) {
/** @var MarkupPagerNav $pager */
$pager = $this->modules->get('MarkupPagerNav');
$pagerOut = $pager->render($results);
$pageURL = $this->wire('page')->url;
@@ -1650,7 +1665,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*
*/
public function ___execute() {
if(!$this->wire('page')) return;
if(!$this->wire('page')) return '';
$modules = $this->wire('modules');
$this->setupOpenPageIDs();
@@ -1803,7 +1818,6 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$loadedFilesAdd = array();
$loadedJSConfig = $this->sessionGet('loadedJSConfig');
$config = $this->wire('config');
$out = '';
foreach($config->scripts as $file) {
if(strpos($file, 'ProcessPageLister')) continue;
@@ -1875,7 +1889,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
* Implementation for ./edit-bookmark/ URL segment
*
* @return string
* @throws WirePermissionException
* @throws WirePermissionException|WireException
*
*/
public function ___executeEditBookmark() {
@@ -1886,6 +1900,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
/**
* Output JSON list of navigation items for this module's bookmarks
*
* @param array $options
* @return string|array
*
*/
public function ___executeNavJSON(array $options = array()) {
@@ -1956,6 +1973,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
}
public static function getModuleConfigInputfields(array $data) {
if($data) {} // ignore
$inputfields = new InputfieldWrapper();
return $inputfields;
}

View File

@@ -1,5 +1,19 @@
<?php namespace ProcessWire;
/** @var Config $config */
/** @var Pages $pages */
/** @var Sanitizer $sanitizer */
/** @var Session $session */
/** @var WireInput $input */
/** @var Fuel $fuel */
/** @var Modules $modules */
/** @var WireDatabasePDO $database */
/** @var DatabaseMysqli $db */
/** @var User $user */
/** @var Page $page */
/** @var WireCache $cache */
/** @var WireClassLoader $classLoader */
if(!defined("PROCESSWIRE")) die();
$debugToolsLabel = __('Debug Mode Tools', __FILE__);
@@ -19,6 +33,7 @@ $debugToolsLabel = __('Debug Mode Tools', __FILE__);
<?php
$o = '';
$oc = 0;
$oct = $pages->loader()->getTotalPagesLoaded();
foreach($pages->getCache() as $p) {
$oc++;
$parts = explode('/', trim($p->path, '/'));
@@ -30,12 +45,13 @@ $debugToolsLabel = __('Debug Mode Tools', __FILE__);
"<td>$p->id</td>" .
"<td>$path</td>" .
"<td>" . wireClassName($p) . "</td>" .
"<td>$p->_debug_loader</td>" .
($config->debug === Config::debugVerbose ? "<td>" . $sanitizer->entities($p->_debug_loader) . "</td>" : "") .
"</tr>";
}
$occ = $oct > $oc ? "$oc/$oct" : $oc;
?>
<div class="container">
<h3><a href='#'><?php echo __('Pages Loaded', __FILE__) . " <span class='ui-priority-secondary'>($oc)</span>"; ?></a></h3>
<h3><a href='#'><?php echo __('Pages Loaded', __FILE__) . " <span class='ui-priority-secondary'>($occ)</span>"; ?></a></h3>
<div>
<table>
<thead>
@@ -43,11 +59,15 @@ $debugToolsLabel = __('Debug Mode Tools', __FILE__);
<th>ID</th>
<th>Path</th>
<th>Type</th>
<th>Loader</th>
<?php if($config->debug === Config::debugVerbose) echo "<th>Loader</th>"; ?>
</tr>
</thead>
<tbody>
<?php echo $o; unset($o, $oc); ?>
<?php
echo $o;
if($oct > $oc) echo "<p>A total of <strong>$oct</strong> pages were loaded, <strong>$oc</strong> of which are in memory:</p>";
unset($o, $oc, $oct, $occ);
?>
</tbody>
</table>
</div>