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__);
"
ID | Path | Type | -Loader | + debug === Config::debugVerbose) echo "Loader | "; ?> - + $oc) echo "
---|