From 2b9a7adbcb64f14dae29629e0a02b0c8738c67e8 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Wed, 14 Dec 2016 11:02:32 -0500 Subject: [PATCH] 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. --- wire/config.php | 3 + wire/core/Config.php | 8 +- wire/core/Page.php | 93 +++++- wire/core/PageTraversal.php | 273 +++++++++--------- wire/core/Pages.php | 7 +- wire/core/PagesLoader.php | 53 +++- wire/core/PagesType.php | 22 +- wire/modules/LanguageSupport/Languages.php | 1 + .../ProcessPageLister.module | 36 ++- wire/templates-admin/debug.inc | 28 +- 10 files changed, 343 insertions(+), 181 deletions(-) diff --git a/wire/config.php b/wire/config.php index d15eeda7..a87c5200 100644 --- a/wire/config.php +++ b/wire/config.php @@ -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 diff --git a/wire/core/Config.php b/wire/core/Config.php index 1641075a..dd769ac6 100644 --- a/wire/core/Config.php +++ b/wire/core/Config.php @@ -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 * diff --git a/wire/core/Page.php b/wire/core/Page.php index af8a538b..5384f172 100644 --- a/wire/core/Page.php +++ b/wire/core/Page.php @@ -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; + } } diff --git a/wire/core/PageTraversal.php b/wire/core/PageTraversal.php index 8c5f0869..1ca660ab 100644 --- a/wire/core/PageTraversal.php +++ b/wire/core/PageTraversal.php @@ -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 )); } @@ -447,6 +451,128 @@ class PageTraversal { $options = array_merge($options, $defaults); 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 page’s 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 page’s 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) * diff --git a/wire/core/Pages.php b/wire/core/Pages.php index c3f8e74f..b677d304 100644 --- a/wire/core/Pages.php +++ b/wire/core/Pages.php @@ -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); } /** diff --git a/wire/core/PagesLoader.php b/wire/core/PagesLoader.php index ccb5e59e..e3724f37 100644 --- a/wire/core/PagesLoader.php +++ b/wire/core/PagesLoader.php @@ -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 * @@ -219,6 +227,10 @@ class PagesLoader extends Wire { } else { $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 @@ -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') { - $_template = is_null($template) ? '' : ", $template"; - $_parent_id = is_null($parent_id) ? '' : ", $parent_id"; - $_ids = count($_ids) > 1 ? "[" . implode(',', $_ids) . "]" : implode('', $_ids); + 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']); } } } @@ -1075,5 +1100,15 @@ class PagesLoader extends Wire { if(!is_null($debug)) $this->debug = (bool) $debug; return $value; } + + /** + * Return the total quantity of pages loaded by getById() + * + * @return int + * + */ + public function getTotalPagesLoaded() { + return $this->totalPagesLoaded; + } } \ No newline at end of file diff --git a/wire/core/PagesType.php b/wire/core/PagesType.php index c793d421..25d3c064 100644 --- a/wire/core/PagesType.php +++ b/wire/core/PagesType.php @@ -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,14 +272,18 @@ 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 if(count($this->templates) == 1 && count($this->parents) == 1) { @@ -306,8 +311,11 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { } else { // 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()' + )); } /** diff --git a/wire/modules/LanguageSupport/Languages.php b/wire/modules/LanguageSupport/Languages.php index e5b5c897..039fb239 100644 --- a/wire/modules/LanguageSupport/Languages.php +++ b/wire/modules/LanguageSupport/Languages.php @@ -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; diff --git a/wire/modules/Process/ProcessPageLister/ProcessPageLister.module b/wire/modules/Process/ProcessPageLister/ProcessPageLister.module index 67d3dea6..5f0380ba 100644 --- a/wire/modules/Process/ProcessPageLister/ProcessPageLister.module +++ b/wire/modules/Process/ProcessPageLister/ProcessPageLister.module @@ -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'); @@ -655,6 +662,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] : "
"; 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 = "$v->basename"; } 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; } diff --git a/wire/templates-admin/debug.inc b/wire/templates-admin/debug.inc index bd400722..4916cad7 100644 --- a/wire/templates-admin/debug.inc +++ b/wire/templates-admin/debug.inc @@ -1,5 +1,19 @@ loader()->getTotalPagesLoaded(); foreach($pages->getCache() as $p) { $oc++; $parts = explode('/', trim($p->path, '/')); @@ -30,12 +45,13 @@ $debugToolsLabel = __('Debug Mode Tools', __FILE__); "$p->id" . "$path" . "" . wireClassName($p) . "" . - "$p->_debug_loader" . + ($config->debug === Config::debugVerbose ? "" . $sanitizer->entities($p->_debug_loader) . "" : "") . ""; } + $occ = $oct > $oc ? "$oc/$oct" : $oc; ?>
-

($oc)"; ?>

+

($occ)"; ?>

@@ -43,11 +59,15 @@ $debugToolsLabel = __('Debug Mode Tools', __FILE__); - + debug === Config::debugVerbose) echo ""; ?> - + $oc) echo "

A total of $oct pages were loaded, $oc of which are in memory:

"; + unset($o, $oc, $oct, $occ); + ?>
ID Path TypeLoaderLoader