[ page IDs ] ] * * @var array * */ protected $cacheGroups = array(); /** * @var Pages * */ protected $pages; /** * Construct * * @param Pages $pages * */ public function __construct(Pages $pages) { parent::__construct(); $this->pages = $pages; } /** * 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); } /** * 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; $page = $this->pageIdCache[$id]; /** @var Page $page */ $of = $this->pages->loader()->getOutputFormatting(); if(!$of && $page === $this->wire()->page) return $page; // skip of() adjustment $page->of($of); return $page; } /** * Cache the given page. * * @param Page $page * @return void * */ public function cache(Page $page) { if($page->id) $this->pageIdCache[$page->id] = $page; } /** * 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; } /** * 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 $user = $this->wire()->user; $language = $this->wire()->languages ? $user->language : null; $cnt = 0; $this->pages->sortfields(true); // reset if($this->wire()->config->debug) { $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(); $this->cacheGroups = array(); Page::$loadingStack = array(); Page::$instanceIDs = array(); return $cnt; } /** * 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; } /** * 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 if($this->wire()->languages) { $language = $this->wire()->user->language; if($language && !$language->isDefault && $language->name != 'default') { $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; } }