2022-03-08 15:55:41 +01:00
|
|
|
<?php namespace ProcessWire;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ProcessWire Pages Loader Cache
|
|
|
|
*
|
|
|
|
* Implements page caching of loaded pages and PageArrays for $pages API variable
|
|
|
|
*
|
2022-11-05 18:32:48 +01:00
|
|
|
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
2022-03-08 15:55:41 +01:00
|
|
|
* https://processwire.com
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
class PagesLoaderCache extends Wire {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pages that have been cached, indexed by ID
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $pageIdCache = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cached selector strings and the PageArray that was found.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $pageSelectorCache = array();
|
|
|
|
|
2022-11-05 18:32:48 +01:00
|
|
|
/**
|
|
|
|
* [ 'cache group name' => [ page IDs ] ]
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $cacheGroups = array();
|
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
/**
|
|
|
|
* @var Pages
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $pages;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct
|
|
|
|
*
|
|
|
|
* @param Pages $pages
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __construct(Pages $pages) {
|
|
|
|
$this->pages = $pages;
|
|
|
|
}
|
2022-11-05 18:32:48 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get cache status
|
|
|
|
*
|
|
|
|
* Returns count of each cache type, or contents of each cache type of verbose option is specified.
|
|
|
|
*
|
|
|
|
* @param bool|null $verbose Specify true to get contents of cache, false to get string counts, or omit for array of counts
|
|
|
|
* @return array|string
|
|
|
|
* @since 3.0.198
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getCacheStatus($verbose = null) {
|
|
|
|
$a = array(
|
|
|
|
'pages' => ($verbose ? $this->pageIdCache : count($this->pageIdCache)),
|
|
|
|
'selectors' => ($verbose ? $this->pageSelectorCache : count($this->pageSelectorCache)),
|
|
|
|
'groups' => ($verbose ? $this->cacheGroups : count($this->cacheGroups)),
|
|
|
|
);
|
|
|
|
return ($verbose === false ? "pages=$a[pages], selectors=$a[selectors], groups=$a[groups]" : $a);
|
|
|
|
}
|
2022-03-08 15:55:41 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a Page ID, return it if it's cached, or NULL of it's not.
|
|
|
|
*
|
|
|
|
* If no ID is provided, then this will return an array copy of the full cache.
|
|
|
|
*
|
|
|
|
* You may also pass in the string "id=123", where 123 is the page_id
|
|
|
|
*
|
|
|
|
* @param int|string|null $id
|
|
|
|
* @return Page|array|null
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getCache($id = null) {
|
|
|
|
if(!$id) return $this->pageIdCache;
|
|
|
|
if(!ctype_digit("$id")) $id = str_replace('id=', '', $id);
|
|
|
|
if(ctype_digit("$id")) $id = (int) $id;
|
|
|
|
if(!isset($this->pageIdCache[$id])) return null;
|
|
|
|
/** @var Page $page */
|
|
|
|
$page = $this->pageIdCache[$id];
|
|
|
|
$page->setOutputFormatting($this->pages->outputFormatting);
|
|
|
|
return $page;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cache the given page.
|
|
|
|
*
|
|
|
|
* @param Page $page
|
|
|
|
* @return void
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function cache(Page $page) {
|
|
|
|
if($page->id) $this->pageIdCache[$page->id] = $page;
|
|
|
|
}
|
|
|
|
|
2022-11-05 18:32:48 +01:00
|
|
|
/**
|
|
|
|
* Cache given page into a named group that it can be uncached with
|
|
|
|
*
|
|
|
|
* @param Page $page
|
|
|
|
* @param string $groupName
|
|
|
|
* @since 3.0.198
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function cacheGroup(Page $page, $groupName) {
|
|
|
|
if(!$page->id) return;
|
|
|
|
if(!isset($this->cacheGroups[$groupName])) $this->cacheGroups[$groupName] = array();
|
|
|
|
$this->pageIdCache[$page->id] = $page;
|
|
|
|
$this->cacheGroups[$groupName][] = $page->id;
|
|
|
|
}
|
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
/**
|
|
|
|
* Remove the given page from the cache.
|
|
|
|
*
|
|
|
|
* Note: does not remove pages from selectorCache. Call uncacheAll to do that.
|
|
|
|
*
|
|
|
|
* @param Page|int $page Page to uncache or ID of page (prior to 3.0.153 only Page object was accepted)
|
|
|
|
* @param array $options Additional options to modify behavior:
|
|
|
|
* - `shallow` (bool): By default, this method also calls $page->uncache(). To prevent call to $page->uncache(), set 'shallow' => true.
|
|
|
|
* @return bool True if page was uncached, false if it didn't need to be
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function uncache($page, array $options = array()) {
|
|
|
|
if($page instanceof Page) {
|
|
|
|
$pageId = $page->id;
|
|
|
|
} else {
|
|
|
|
$pageId = is_int($page) ? $page : (int) "$page";
|
|
|
|
$page = isset($this->pageIdCache[$pageId]) ? $this->pageIdCache[$pageId] : null;
|
|
|
|
}
|
|
|
|
if(empty($options['shallow']) && $page) {
|
|
|
|
$page->uncache();
|
|
|
|
}
|
|
|
|
if(isset($this->pageIdCache[$pageId])) {
|
|
|
|
unset($this->pageIdCache[$pageId]);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove all pages from the cache
|
|
|
|
*
|
|
|
|
* @param Page $page Optional Page that initiated the uncacheAll
|
|
|
|
* @param array $options Additional options to modify behavior:
|
|
|
|
* - `shallow` (bool): By default, this method also calls $page->uncache(). To prevent call to $page->uncache(), set 'shallow' => true.
|
|
|
|
* @return int Number of pages uncached
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function uncacheAll(Page $page = null, array $options = array()) {
|
|
|
|
|
|
|
|
if($page) {} // to ignore unused parameter inspection
|
2022-11-05 18:32:48 +01:00
|
|
|
$user = $this->wire()->user;
|
|
|
|
$language = $this->wire()->languages ? $user->language : null;
|
2022-03-08 15:55:41 +01:00
|
|
|
$cnt = 0;
|
|
|
|
|
|
|
|
$this->pages->sortfields(true); // reset
|
|
|
|
|
2022-11-05 18:32:48 +01:00
|
|
|
if($this->wire()->config->debug) {
|
2022-03-08 15:55:41 +01:00
|
|
|
$this->pages->debugLog('uncacheAll', 'pageIdCache=' . count($this->pageIdCache) . ', pageSelectorCache=' .
|
|
|
|
count($this->pageSelectorCache));
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach($this->pageIdCache as $id => $page) {
|
|
|
|
if($id == $user->id || ($language && $language->id == $id)) continue;
|
|
|
|
if($page->numChildren) continue;
|
|
|
|
if(empty($options['shallow'])) $page->uncache();
|
|
|
|
unset($this->pageIdCache[$page->id]);
|
|
|
|
$cnt++;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->pageIdCache = array();
|
|
|
|
$this->pageSelectorCache = array();
|
2022-11-05 18:32:48 +01:00
|
|
|
$this->cacheGroups = array();
|
2022-03-08 15:55:41 +01:00
|
|
|
|
|
|
|
Page::$loadingStack = array();
|
|
|
|
Page::$instanceIDs = array();
|
|
|
|
|
|
|
|
return $cnt;
|
|
|
|
}
|
|
|
|
|
2022-11-05 18:32:48 +01:00
|
|
|
/**
|
|
|
|
* Uncache pages that were cached with given group name
|
|
|
|
*
|
|
|
|
* @param string $groupName
|
|
|
|
* @param array $options
|
|
|
|
* @return int
|
|
|
|
* @since 3.0.198
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function uncacheGroup($groupName, array $options = array()) {
|
|
|
|
$qty = 0;
|
|
|
|
if(!isset($this->cacheGroups[$groupName])) return 0;
|
|
|
|
foreach($this->cacheGroups[$groupName] as $pageId) {
|
|
|
|
if(!isset($this->pageIdCache[$pageId])) continue;
|
|
|
|
$page = $this->pageIdCache[$pageId];
|
|
|
|
if($page && empty($options['shallow'])) $page->uncache();
|
|
|
|
unset($this->pageIdCache[$pageId]);
|
|
|
|
$qty++;
|
|
|
|
}
|
|
|
|
unset($this->cacheGroups[$groupName]);
|
|
|
|
return $qty;
|
|
|
|
}
|
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
/**
|
|
|
|
* Cache the given selector string and options with the given PageArray
|
|
|
|
*
|
|
|
|
* @param string $selector
|
|
|
|
* @param array $options
|
|
|
|
* @param PageArray $pages
|
|
|
|
* @return bool True if pages were cached, false if not
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function selectorCache($selector, array $options, PageArray $pages) {
|
|
|
|
|
|
|
|
// get the string that will be used for caching
|
|
|
|
$selector = $this->getSelectorCache($selector, $options, true);
|
|
|
|
|
|
|
|
// optimization: don't cache single pages that have an unpublished status or higher
|
|
|
|
if(count($pages) && !empty($options['findOne']) && $pages->first()->status >= Page::statusUnpublished) return false;
|
|
|
|
|
|
|
|
$this->pageSelectorCache[$selector] = clone $pages;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert an options array to a string
|
|
|
|
*
|
|
|
|
* @param array $options
|
|
|
|
* @return string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected function optionsArrayToString(array $options) {
|
|
|
|
$str = '';
|
|
|
|
ksort($options);
|
|
|
|
foreach($options as $key => $value) {
|
|
|
|
if(is_array($value)) {
|
|
|
|
$value = $this->optionsArrayToString($value);
|
|
|
|
} else if(is_object($value)) {
|
|
|
|
if(method_exists($value, '__toString')) {
|
|
|
|
$value = (string) $value;
|
|
|
|
} else {
|
|
|
|
$value = wireClassName($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$str .= "[$key:$value]";
|
|
|
|
}
|
|
|
|
return $str;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve any cached page IDs for the given selector and options OR false if none found.
|
|
|
|
*
|
|
|
|
* You may specify a third param as TRUE, which will cause this to just return the selector string (with hashed options)
|
|
|
|
*
|
|
|
|
* @param string $selector
|
|
|
|
* @param array $options
|
|
|
|
* @param bool $returnSelector default false
|
|
|
|
* @return array|null|string|PageArray
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getSelectorCache($selector, $options, $returnSelector = false) {
|
|
|
|
|
|
|
|
if(count($options)) {
|
|
|
|
$optionsHash = $this->optionsArrayToString($options);
|
|
|
|
$selector .= "," . $optionsHash;
|
|
|
|
} else {
|
|
|
|
$selector .= ",";
|
|
|
|
}
|
|
|
|
|
|
|
|
// optimization to use consistent conventions for commonly interchanged names
|
|
|
|
$selector = str_replace(
|
|
|
|
array(
|
|
|
|
'path=/,',
|
|
|
|
'parent=/,'
|
|
|
|
),
|
|
|
|
array(
|
|
|
|
'id=1,',
|
|
|
|
'parent_id=1,'
|
|
|
|
),
|
|
|
|
$selector
|
|
|
|
);
|
|
|
|
|
|
|
|
// optimization to filter out common status checks for pages that won't be cached anyway
|
|
|
|
if(!empty($options['findOne'])) {
|
|
|
|
$selector = str_replace(
|
|
|
|
array(
|
|
|
|
'status<' . Page::statusUnpublished,
|
|
|
|
'status<' . Page::statusMax,
|
|
|
|
'start=0',
|
|
|
|
'limit=1',
|
|
|
|
',',
|
|
|
|
' '
|
|
|
|
),
|
|
|
|
'',
|
|
|
|
$selector
|
|
|
|
);
|
|
|
|
$selector = trim($selector, ", ");
|
|
|
|
}
|
|
|
|
|
|
|
|
// cache non-default languages separately
|
2022-11-05 18:32:48 +01:00
|
|
|
if($this->wire()->languages) {
|
|
|
|
$language = $this->wire()->user->language;
|
2022-03-08 15:55:41 +01:00
|
|
|
if(!$language->isDefault()) {
|
|
|
|
$selector .= ", _lang=$language->id"; // for caching purposes only, not recognized by PageFinder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if($returnSelector) return $selector;
|
|
|
|
if(isset($this->pageSelectorCache[$selector])) return $this->pageSelectorCache[$selector];
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|