save() Returns true on success. #pw-group-manipulation * @method bool saveField(Page $page, $field, array $options = array()) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation * @method bool trash(Page $page, $save = true) Move a page to the trash. If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. #pw-group-manipulation * @method bool restore(Page $page, $save = true) Restore a trashed page to its original location. #pw-group-manipulation * @method int|array emptyTrash(array $options = array()) Empty the trash and return number of pages deleted. #pw-group-manipulation * @method bool delete(Page $page, $recursive = false, array $options = array()) Permanently delete a page and it's fields. Unlike trash(), pages deleted here are not restorable. If you attempt to delete a page with children, and don't specifically set the $recursive param to True, then this method will throw an exception. If a recursive delete fails for any reason, an exception will be thrown. #pw-group-manipulation * @method Page|NullPage clone(Page $page, Page $parent = null, $recursive = true, $options = array()) Clone an entire page, it's assets and children and return it. #pw-group-manipulation * @method Page|NullPage add($template, $parent, $name = '', array $values = array()) #pw-group-manipulation * @method int sort(Page $page, $value = false) Set the “sort” value for given $page while adjusting siblings, or re-build sort for its children. #pw-group-manipulation * @method setupNew(Page $page) Setup new page that does not yet exist by populating some fields to it. #pw-internal * @method string setupPageName(Page $page, array $options = array()) Determine and populate a name for the given page. #pw-internal * @method void insertBefore(Page $page, Page $beforePage) Insert one page as a sibling before another. #pw-advanced * @method void insertAfter(Page $page, Page $afterPage) Insert one page as a sibling after another. #pw-advanced * * METHODS PURELY FOR HOOKS * ======================== * You can hook these methods, but you should not call them directly. * See the phpdoc in the actual methods for more details about arguments and additional properties that can be accessed. * * @method saveReady(Page $page) Hook called just before a page is saved. * @method saved(Page $page, array $changes = array(), $values = array()) Hook called after a page is successfully saved. * @method added(Page $page) Hook called when a new page has been added. * @method moved(Page $page) Hook called when a page has been moved from one parent to another. * @method templateChanged(Page $page) Hook called when a page template has been changed. * @method trashReady(Page $page) Hook called when a page is about to be moved to the trash. * @method trashed(Page $page) Hook called when a page has been moved to the trash. * @method restored(Page $page) Hook called when a page has been moved OUT of the trash. * @method deleteReady(Page $page, array $options) Hook called just before a page is deleted. * @method deleted(Page $page, array $options) Hook called after a page has been deleted. * @method deleteBranchReady(Page $page, array $options) Hook called before a branch of pages deleted, on initiating page only. * @method deletedBranch(Page $page, array $options, $numDeleted) Hook called after branch of pages deleted, on initiating page only. * @method cloneReady(Page $page, Page $copy) Hook called just before a page is cloned. * @method cloned(Page $page, Page $copy) Hook called after a page has been successfully cloned. * @method renamed(Page $page) Hook called after a page has been successfully renamed. * @method sorted(Page $page, $children = false, $total = 0) Hook called after $page has been sorted. * @method statusChangeReady(Page $page) Hook called when a page's status has changed and is about to be saved. * @method statusChanged(Page $page) Hook called after a page status has been changed and saved. * @method publishReady(Page $page) Hook called just before an unpublished page is published. * @method published(Page $page) Hook called after an unpublished page has just been published. * @method unpublishReady(Page $page) Hook called just before a pubished page is unpublished. * @method unpublished(Page $page) Hook called after a published page has just been unpublished. * @method saveFieldReady(Page $page, Field $field) Hook called just before a saveField() method saves a page fied. * @method savedField(Page $page, Field $field) Hook called after saveField() method successfully executes. * @method savePageOrFieldReady(Page $page, $fieldName = '') Hook inclusive of both saveReady() and saveFieldReady(). * @method savedPageOrField(Page $page, array $changes) Hook inclusive of both saved() and savedField(). * @method found(PageArray $pages, array $details) Hook called at the end of a $pages->find(). * * TO-DO * ===== * @todo Update saveField to accept array of field names as an option. * */ class Pages extends Wire { /** * Max length for page name * */ const nameMaxLength = 128; /** * Default name for the root/home page * */ const defaultRootName = 'home'; /** * Instance of PagesSortfields * */ protected $sortfields; /** * Current debug state * * @var bool * */ protected $debug = false; /** * Runtime debug log of Pages class activities, see getDebugLog() * */ protected $debugLog = array(); /** * @var PagesLoader * */ protected $loader; /** * @var PagesEditor * */ protected $editor; /** * @var PagesNames * */ protected $names; /** * @var PagesLoaderCache * */ protected $cacher; /** * @var PagesTrash * */ protected $trasher; /** * @var PagesParents * */ protected $parents; /** * @var PagesRaw * */ protected $raw; /** * Array of PagesType managers * * @var PagesType[] * */ protected $types = array(); /** * Create the Pages object * * @param ProcessWire $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->raw = null; } /** * Initialize $pages API var by preloading some pages * * #pw-internal * */ public function init() { $this->loader->getById($this->wire('config')->preloadPageIDs); } /**************************************************************************************************************** * BASIC PUBLIC PAGES API METHODS * */ /** * Count and return how many pages will match the given selector. * * If no selector provided, it returns count of all pages in site. * * ~~~~~~~~~ * // Return count of how may pages in the site use the blog-post template * $numBlogPosts = $pages->count("template=blog-post"); * ~~~~~~~~~ * * #pw-group-retrieval * * @param string|array|Selectors $selector Specify selector, or omit to retrieve a site-wide count. * @param array|string $options See $options for $pages->find(). * @return int * @see Pages::find() * */ public function count($selector = '', $options = array()) { return $this->loader->count($selector, $options); } /** * Given a Selector string, return the Page objects that match in a PageArray. * * - This is one of the most commonly used API methods in ProcessWire. * - If you only need to find one page, use the `Pages::get()` or `Pages::findOne()` method instead (and note the difference). * - If you need to find a huge quantity of pages (like thousands) without limit or pagination, look at the `Pages::findMany()` method. * * ~~~~~ * // Find all pages using template "building" with 25 or more floors * $skyscrapers = $pages->find("template=building, floors>=25"); * ~~~~~ * * #pw-group-retrieval * * @param string|int|array|Selectors $selector Specify selector (standard usage), but can also accept page ID or array of page IDs. * @param array|string $options One or more options that can modify certain behaviors. May be associative array or "key=value" selector string. * - `findOne` (bool): Apply optimizations for finding a single page (default=false). * - `findAll` (bool): Find all pages with no exclusions, same as "include=all" option (default=false). * - `findIDs` (bool|int): 1 to get array of page IDs, true to return verbose array, 2 to return verbose array with all cols in 3.0.153+. (default=false). * - `getTotal` (bool): Whether to set returning PageArray's "total" property (default=true, except when findOne=true). * - `loadPages` (bool): Whether to populate the returned PageArray with found pages (default=true). * The only reason why you'd want to change this to false would be if you only needed the count details from * the PageArray: getTotal(), getStart(), getLimit, etc. This is intended as an optimization for $pages->count(). * Does not apply if $selector argument is an array. * - `cache` (bool): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache]. * - `allowCustom` (boolean): Allow use of _custom="another selector" in given $selector? For specific uses. (default=false) * - `caller` (string): Optional name of calling function, for debugging purposes, i.e. "pages.count" (default=blank). * - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. (default=none). Typically you would specify this * directly in the selector string, so the option is mainly useful if your first argument is not a string. * - `stopBeforeID` (int): Stop loading pages once page matching this ID is found (default=0). * - `startAfterID` (int): Start loading pages once page matching this ID is found (default=0). * - `lazy` (bool): Specify true to force lazy loading. This is the same as using the Pages::findMany() method (default=false). * - `loadOptions` (array): Optional associative array of options to pass to getById() load options. * @return PageArray|array PageArray of that matched the given selector, or array of page IDs (if using findIDs option). * * Non-visible pages are excluded unless an "include=x" mode is specified in the selector * (where "x" is "hidden", "unpublished" or "all"). If "all" is specified, then non-accessible * pages (via access control) can also be included. * @see Pages::findOne(), Pages::findMany(), Pages::get() * */ public function ___find($selector, $options = array()) { return $this->loader->find($selector, $options); } /** * Like find() but returns only the first match as a Page object (not PageArray). * * This is functionally similar to the `get()` method except that its default behavior is to * filter for access control and hidden/unpublished/etc. states, in the same way that the * `find()` method does. You can add an `include=...` to your selector string to bypass. * This method also accepts an `$options` array, whereas `get()` does not. * * ~~~~~~ * // Find the newest page using the blog-post template * $blogPost = $pages->findOne("template=blog-post, sort=-created"); * ~~~~~~ * * #pw-group-retrieval * * @param string|array|Selectors $selector Selector string, array or Selectors object * @param array|string $options See $options for $pages->find() * @return Page|NullPage Returns a Page on success, or a NullPage (having id=0) on failure * @since 3.0.0 * @see Pages::get(), Pages::find(), Pages::findMany() * */ public function findOne($selector, $options = array()) { return $this->loader->findOne($selector, $options); } /** * Like find(), but with “lazy loading” to support giant result sets without running out of memory. * * When using this method, you can retrieve tens of thousands, or hundreds of thousands of pages * or more, without needing a pagination "limit" in your selector. Individual pages are loaded * and unloaded in chunks as you iterate them, making it possible to iterate all pages without * running out of memory. This is useful for performing some kind of calculation on all pages or * other tasks like that. Note however that if you are building something from the result set * that consumes more memory for each page iterated (like concatening a string of page titles * or something), then you could still run out of memory there. * * The example below demonstrates use of this method. Note that attempting to do the same using * the regular `$pages->find()` would run out of memory, as it's unlikely the server would have * enough memory to store 20k pages in memory at once. * * ~~~~~ * // Calculating a total from 20000 pages * $totalCost = 0; * $items = $pages->findMany("template=foo"); // 20000 pages * foreach($items as $item) { * $totalCost += $item->cost; * } * echo "Total cost is: $totalCost"; * ~~~~~ * * #pw-group-retrieval * * @param string|array|Selectors $selector Selector to find pages * @param array $options Options to modify behavior. See `Pages::find()` $options argument for details. * @return PageArray * @since 3.0.19 * @see Pages::find(), Pages::findOne() * */ public function findMany($selector, $options = array()) { $debug = $this->debug; if($debug) $this->debug(false); $options['lazy'] = true; $options['caller'] = 'pages.findMany'; if(!isset($options['cache'])) $options['cache'] = false; $matches = $this->find($selector, $options); if($debug) $this->debug($debug); return $matches; } /** * Find pages and specify which fields to join (overriding configured autojoin settings) * * This is a useful optimization when you know exactly which fields you will be using from the returned * pages and you want to have their values joined into the page loading query to reduce overhead. Note * that this overrides the configured autojoin settings in ProcessWire fields. * * If a particular page in the returned set of pages was already loaded before this method call, * then the one already in memory will be used rather than this method loading another copy of it. * * ~~~~~ * // 1. Example of loading blog posts where we want to join title, date, summary: * $posts = $pages->findJoin("template=blog-post", [ 'title', 'date', 'summary' ]); * * // 2. You can also specify the join fields as a CSV string: * $posts = $pages->findJoin("template=blog-post", 'title, date, summary'); * * // 3. You can also use the join functionality on a regular $pages->find() by specifying * // property 'join' or 'field' in the selector. The words 'join' and 'field' are aliases * // of each other here, just in case you have an existing field with one of those names. * // Otherwise, use whichever makes more sense to you. The following examples demonstrate * // this and all do exactly the same thing as examples 1 and 2 above: * $posts = $pages->find("template=blog-post, join=title|date|summary"); * $posts = $pages->find("template=blog-post, field=title|date|summary"); * $posts = $pages->find("template=blog-post, join=title, join=date, join=summary"); * $posts = $pages->find("template=blog-post, field=title, field=date, field=summary"); * * // 4. Let’s say you want to load pages with NO autojoin fields, here is how. * // The following loads all blog-post pages and prevents ANY fields from being joined, * // even if they are configured to be autojoin in ProcessWire: * $posts = $pages->findJoin("template=blog-post", false); * $posts = $pages->find("template=blog-post, join=none"); // same as above * ~~~~~ * * #pw-group-retrieval * * @param string|array|Selectors $selector * @param array|string|bool $joinFields Array or CSV string of field names to autojoin, or false to join none. * @param array $options * @return PageArray * @since 3.0.172 * */ public function findJoin($selector, $joinFields, $options = array()) { $fields = $this->wire()->fields; if($joinFields === false) { $name = 'none'; while($fields->get($name)) $name .= 'X'; $joinFields = array($name); } else if(empty($joinFields)) { $joinFields = array(); } else if(!is_array($joinFields)) { $joinFields = (string) $joinFields; if(strpos($joinFields, ',') !== false) { $joinFields = explode(',', $joinFields); } else if(strpos($joinFields, '|') !== false) { $joinFields = explode('|', $joinFields); } else { $joinFields = array($joinFields); } } foreach($joinFields as $key => $name) { if(is_int($name) || ctype_digit($name)) { $field = $fields->get($name); if(!$field) continue; $name = $field->name; } else if(strpos($name, '.') !== false) { list($name,) = explode('.', $name, 2); // subfields not allowed } $joinFields[$key] = trim($name); } $options['joinFields'] = $joinFields; return $this->find($selector, $options); } /** * Like find() except returns array of IDs rather than Page objects * * - This is a faster method to use when you only need to know the matching page IDs. * - The default behavior is to simply return a regular PHP array of matching page IDs in order. * - The alternate behavior (verbose) returns more information for each match, as outlined below. * * **Verbose option:** * When specifying boolean true for the `$options` argument (or using the `verbose` option), * the return value is an array of associative arrays, with each of those associative arrays * containing `id`, `parent_id` and `templates_id` keys for each page. * * ~~~~~ * // returns array of page IDs (integers) like [ 1234, 1235, 1236 ] * $a = $pages->findIDs("foo=bar"); * * // verbose option: returns array of associative arrays, each with id, parent_id and templates_id * $a = $pages->findIDs("foo=bar", true); * ~~~~~ * * #pw-group-retrieval * * @param string|array|Selectors $selector Selector to find page IDs. * @param array|bool|int|string $options Options to modify behavior. * - `verbose` (bool|int|string): Specify true to make return value array of associative arrays, each with id, parent_id, templates_id. * Specify integer `2` or string `*` to return verbose array of associative arrays, each with all columns from pages table. * - `indexed` (bool): Index by page ID? (default=false) Added 3.0.172 * - The verbose option above can also be specified as alternative to the $options argument. * - See `Pages::find()` $options argument for additional options. * @return array Array of page IDs, or in verbose mode: array of arrays, each with id, parent_id and templates_id keys. * @since 3.0.46 * */ public function findIDs($selector, $options = array()) { $verbose = false; if(!is_array($options)) { // verbose option specified in $options array $verbose = $options; $options = array(); } if(isset($options['verbose'])) { $verbose = $options['verbose']; unset($options['verbose']); } if($verbose === 2 || $verbose === '*') { $options['findIDs'] = 2; } else { $options['findIDs'] = $verbose ? true : 1; } /** @var array $ids */ $ids = $this->find($selector, $options); if(!empty($options['indexed'])) { $a = array(); foreach($ids as $value) { $id = $verbose ? $value['id'] : $value; $a[$id] = $value; } $ids = $a; } return $ids; } /** * Find pages and return raw data from them in a PHP array * * Note that the data returned from this method is raw and unformatted, directly * as it exists in the database. In most cases you should use `$pages->find()` instead, * but this method provides a convenient alternative for some cases. * * The `$selector` argument can be any page-finding selector that you would provide * to a regular `$pages->find()` call. The most interesting stuff relates to the * `$field` argument though, which is what the rest of this section looks at: * * If you omit the `$field` argument, it will return all data for the found pages in * an array where the keys are the page IDs and the values are associative arrays * containing all of each page raw field and property values indexed by name… * `$a = $pages->findRaw("template=blog");` …but findRaw() is more useful for cases * where you want to retrieve specific things without having to load the entire page * (or its data). Below are a few examples of how you can do this. * * ~~~~~ * // If you provide a string (field name) for `$field`, then it will return an * // array with the values of the `data` column of that field. The `$field` can * // also be the name of a native pages table property like `id` or `name`. * $a = $pages->findRaw("template=blog", "title"); * * // The above would return an array of blog page titles indexed by page ID. If * // you provide an array for `$field` then it will return an array for each page, * // where each of those arrays is indexed by the field names you requested. * $a = $pages->findRaw("template=blog", [ "title", "date" ]); * * // You may specify field name(s) like `field.subfield` to retrieve a specific * // column/subfield. When it comes to Page references or Repeaters, the subfield * // can also be the name of a field that exists on the Page reference or repeater. * $a = $pages->findRaw("template=blog", [ "title", "categories.title" ]); * * // You can also use this format below to get multiple subfields from one field: * $a = $pages->findRaw("template=blog", [ "title", "categories" => [ "id", "title" ] ]); * * // You can optionally rename fields in the returned value like this below, which * // asks the 'title' field to have the name 'headline' in return value (3.0.176+): * $a = $pages->findRaw("template=blog", [ "title" => "headline" ]); * * // You may specify wildcard field name(s) like `field.*` to return all columns * // for `field`. This retrieves all columns from the field’s table. This is * // especially useful with fields like Table or Combo that might have several * // different columns: * $a = $pages->findRaw("template=villa", "rates_table.*" ); * * // If you prefer, you can specify the field name(s) in the selector (3.0.173+): * $a = $pages->findRaw("template=blog, field=title"); * $a = $pages->findRaw("template=blog, field=title|categories.title"); * * // Specify “objects=1” in selector to use objects rather than associative arrays * // for pages and fields (3.0.174+): * $a = $pages->findRaw("template=blog, field=title|categories.title, objects=1"); * * // Specify “entities=1” to entity encode all string values: * $a = $pages->findRaw("template=blog, field=title|summary, entities=1"); * * // Specify “entities=field” or “entities=field1|field2” to entity encode just * // the specific fields that you name (3.0.174+): * $a = $pages->findRaw("template=blog, entities=title|summary"); * * // If you prefer, options can also be enabled this way (3.0.174+): * $a = $pages->findRaw("template=blog, options=objects|entities"); * ~~~~~ * * #pw-advanced * #pw-group-retrieval * * @param string|array|Selectors|int $selector Page matching selector or page ID * @param string|array|Field $field Name of field/property to get, or array of them, or omit to get all (default='') * Note: this argument may also be specified in the $selector argument as "field=foo" or "field=foo|bar|baz" (3.0.173+). * @param array $options Options to adjust behavior (may also be specified in selector, i.e. “objects=1, entities=foo|bar”) * - `objects` (bool): Use objects rather than associative arrays? (default=false) 3.0.174+ * - `entities` (bool|array): Entity encode string values? True or 1 to enable, or specify array of field names. (default=false) 3.0.174+ * @return array * @since 3.0.172 * */ public function findRaw($selector, $field = '', $options = array()) { return $this->raw()->find($selector, $field, $options); } /** * Returns the first page matching the given selector with no exclusions * * Use this method when you need to retrieve a specific page without exclusions for access control or page status. * * ~~~~~~ * // Get a page by ID * $p = $pages->get(1234); * * // Get a page by path * $p = $pages->get('/about/contact/'); * * // Get a random 'skyscraper' page by selector string * $p = $pages->get('template=skyscraper, sort=random'); * ~~~~~~ * * #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, $options = array()) { return $this->loader->get($selector, $options); } /** * Get single page and return raw data in an associative array * * Note that the data returned from this method is raw and unformatted, directly as it exists in the database. * In most cases you should use `$pages->get()` instead, but this method is a convenient alternative for some cases. * * Please see the documentation for the `$pages->findRaw()` method, which all applies to this method as well. * The biggest difference is that this method returns data for just 1 page, unlike `$pages->findRaw()` which can * return data for many pages at once. * * #pw-advanced * * @param string|array|Selectors|int $selector Page matching selector or page ID * @param string|array|Field $field Name of field/property to get, or array of them, or omit to get all (default='') * @param array $options * @return array * */ public function getRaw($selector, $field = '', $options = array()) { return $this->raw()->get($selector, $field, $options); } /** * Get a fresh, non-cached copy of a Page from the database * * This method is the same as `$pages->get()` except that it skips over all memory caches when loading a Page. * Meaning, if the Page is already in memory, it doesn’t use the one in memory and instead reloads from the DB. * Nor does it place the Page it loads in any memory cache. Use this method to load a fresh copy of a page * that you might need to compare to an existing loaded copy, or to load a copy that won’t be seen or touched * by anything in ProcessWire other than your own code. * * ~~~~~ * $p1 = $pages->get(1234); * $p2 = $pages->get($p1->path); * $p1 === $p2; // true: same Page instance * * $p3 = $pages->getFresh($p1); * $p1 === $p3; // false: same Page but different instance * ~~~~~ * * #pw-advanced * * @param Page|string|array|Selectors|int $selectorOrPage Specify Page to get copy of, selector or ID * @param array $options Options to modify behavior * @return Page|NullPage * @since 3.0.172 * */ public function getFresh($selectorOrPage, $options = array()) { return $this->loader()->getFresh($selectorOrPage, $options); } /** * Get one ID of page matching given selector with no exclusions, like get() but returns ID rather than a Page * * This method is an alias of the has() method, and depending on what you are after, may make more * or less sense with your code readability. Use whichever better suits your case. * * #pw-group-retrieval * * @param string|array|Selectors $selector Specify selector to find first matching page ID * @param bool|array $options Specify boolean true to return all pages columns rather than just IDs. * Or specify array of options (see find method for details), `verbose` option can optionally be in array. * @return int|string|array * @see Pages::get(), Pages::has(), Pages::findIDs() * @since 3.0.156 * */ public function getID($selector, $options = array()) { if(is_array($options)) { if(empty($options['caller'])) $options['caller'] = 'pages.getID'; $verbose = false; if(isset($options['verbose'])) { $verbose = $options['verbose']; unset($options['verbose']); } } else { $verbose = $options; $options = array(); } return $this->loader->has($selector, $verbose, $options); } /** * Given array or CSV string of Page IDs, return a PageArray * * #pw-group-retrieval * * @param array|string|WireArray $ids Any one of the following: * - Single page ID (string or int) * - Array of page IDs * - Comma or pipe-separated string of page IDs * - Array of associative arrays having id and templates_id: [ [ 'id' => 1, 'templates_id' => 2], [ 'id' => 3, 'templates_id' => 4 ] ] * @param array $options Options to affect behavior. The 'template' option is recommended when you have this info available. * - `template` (Template|int|string): Template object, name or ID to use for loaded pages. (default=null) * - `parent` (Page|int|string): Parent Page object, ID, or path to use for loaded pages. (default=null) * - `cache` (bool): Place loaded pages in memory cache? (default=true) * - `getFromCache` (bool): Allow use of previously cached pages in memory (rather than re-loading it from DB)? (default=true) * - `getNumChildren` (bool): Specify false to disable retrieval and population of 'numChildren' Page property. (default=true) * - `getOne` (bool): Specify true to return just one Page object, rather than a PageArray. (default=false) * - `autojoin` (bool): Allow use of autojoin option? (default=true) * - `joinFields` (array): Autojoin the field names specified in this array, regardless of field settings (requires autojoin=true). (default=empty) * - `joinSortfield` (bool): Whether the 'sortfield' property will be joined to the page. (default=true) * - `findTemplates` (bool): Determine which templates will be used (when no template specified) for more specific autojoins. (default=true) * - `pageClass` (string): Class to instantiate Page objects with. Leave blank to determine from template. (default=auto-detect) * - `pageArrayClass` (string): PageArray-derived class to store pages in (when 'getOne' is false). (default=PageArray) * - `pageArray` (PageArray|null): Populate this existing PageArray rather than creating a new one. (default=null) * - `page` (Page|null): Existing Page object to populate (also requires the getOne option to be true). (default=null) * @return PageArray|Page Returns PageArray unless the getOne option was specified in which case a Page is returned. * @since 3.0.156 Previous versions can use $pages->getById() for similar behavior * */ public function getByIDs($ids, array $options = array()) { $template = empty($options['template']) ? null : $options['template']; $parent = empty($options['parent']) ? null : $options['parent']; $parent_id = null; if($template) { unset($options['template']); if($template instanceof Template) { // cool, cool } else if(is_int($template) || is_string($template)) { $template = $this->wire('templates')->get($template); } else { $template = null; } } if(!empty($options['parent_id'])) { unset($options['parent_id']); $parent_id = (int) $options['parent_id']; } else if($parent) { unset($options['parent']); if($parent instanceof Page) { $parent_id = $parent->id; } else if(is_int($parent) || ctype_digit("$parent")) { $parent_id = (int) "$parent"; } else if(is_string($parent) && $parent) { $parent_id = $this->has($parent); } if(!$parent_id) $parent_id = null; } if(count($options)) { $options['template'] = $template && $template instanceof Template ? $template : null; $options['parent_id'] = $parent_id; return $this->loader->getById($ids, $options); } else { return $this->loader->getById($ids, $template, $parent_id); } } /** * Is there any page that matches the given $selector in the system? (with no exclusions) * * - This can be used as an “exists” type of method. * - Returns ID of first matching page if any exist, or 0 if none exist (returns array if `$verbose` is true). * - Like with the `get()` method, no pages are excluded, so an `include=all` is not necessary in selector. * - If you need to quickly check if something exists, this method is preferable to using a count() or get(). * * When `$verbose` option is used, an array is returned instead. Verbose return array includes page `id`, * `parent_id` and `templates_id` indexes. * * #pw-group-retrieval * * @param string|int|array|Selectors $selector * @param bool $verbose Return verbose array with page id, parent_id, templates_id rather than just page id? (default=false) * @return array|int * @since 3.0.153 * @see Pages::count(), Pages::get() * */ public function has($selector, $verbose = false) { return $this->loader->has($selector, $verbose); } /** * Save a page object and its fields to database. * * If the page is new, it will be inserted. If existing, it will be updated. * This is the same as calling `$page->save()`. If you want to just save a particular field * in a Page, use `$page->save($fieldName)` instead. * * ~~~~~~ * // Modify a page and save it * $p = $pages->get('/festivals/decatur/beer/'); * $p->of(false); // turn off output formatting, if it's on * $p->title = "Decatur Beer Festival"; * $p->summary = "Come and enjoy fine beer and good company at the Decatur Beer Festival."; * $pages->save($p); * ~~~~~~ * * #pw-group-manipulation * * @param Page $page Page object to save * @param array $options Optional array to modify default behavior, with one or more of the following: * - `uncacheAll` (boolean): Whether the memory cache should be cleared (default=true). * - `resetTrackChanges` (boolean): Whether the page's change tracking should be reset (default=true). * - `quiet` (boolean): When true, modified date and modified_users_id won't be updated (default=false). * - `adjustName` (boolean): Adjust page name to ensure it is unique within its parent (default=false). * - `forceID` (integer): Use this ID instead of an auto-assigned one (new page) or current ID (existing page). * - `ignoreFamily` (boolean): Bypass check of allowed family/parent settings when saving (default=false). * - `noHooks` (boolean): Prevent before/after save hooks (default=false), please also use $pages->___save() for call. * - `noFields` (boolean): Bypass saving of custom fields, leaving only native properties to be saved (default=false). * @return bool True on success, false on failure * @throws WireException * @see Page::save(), Pages::saveField() * */ public function ___save(Page $page, $options = array()) { return $this->editor()->save($page, $options); } /** * Save only a field from the given page * * This is the same as calling `$page->save($field)`. * * ~~~~~ * // Update the summary field on $page and save it * $page->summary = "Those who know do not speak. Those who speak do not know."; * $pages->saveField($page, 'summary'); * ~~~~~ * * #pw-group-manipulation * * @param Page $page Page to save * @param string|Field $field Field object or name (string) * @param array|string $options Optionally specify one or more of the following to modify default behavior: * - `quiet` (boolean): Specify true to bypass updating of modified user and time (default=false). * - `noHooks` (boolean): Prevent before/after save hooks (default=false), please also use $pages->___saveField() for call. * @return bool True on success, false on failure * @throws WireException * @see Page::save(), Page::setAndSave(), Pages::save() * */ public function ___saveField(Page $page, $field, $options = array()) { return $this->editor()->saveField($page, $field, $options); } /** * Add a new page using the given template and parent * * If no page "name" is specified, one will be automatically assigned. * * ~~~~~ * // Add new page using 'skyscraper' template into Atlanta * $building = $pages->add('skyscraper', '/skyscrapers/atlanta/'); * * // Same as above, but with specifying a name/title as well: * $building = $pages->add('skyscraper', '/skyscrapers/atlanta/', 'Symphony Tower'); * * // Same as above, but with specifying several properties: * $building = $pages->add('skyscraper', '/skyscrapers/atlanta/', [ * 'title' => 'Symphony Tower', * 'summary' => 'A 41-story skyscraper located at 1180 Peachtree Street', * 'height' => 657, * 'floors' => 41 * ]); * ~~~~~ * * #pw-group-manipulation * * @param string|Template $template Template name or Template object * @param string|int|Page $parent Parent path, ID or Page object * @param string $name Optional name or title of page. If none provided, one will be automatically assigned. * If you want to specify a different name and title then specify the $name argument, and $values['title']. * @param array $values Field values to assign to page (optional). If $name is omitted, this may also be 3rd param. * @return Page New page ready to populate. Note that this page has output formatting off. * @throws WireException When some criteria prevents the page from being saved. * */ public function ___add($template, $parent, $name = '', array $values = array()) { return $this->editor()->add($template, $parent, $name, $values); } /** * Clone entire page return it. * * This also clones any file assets assets associated with the page. The clone is recursive * by default, cloning children (and so on) as well. To clone only the page without children, * specify false for the `$recursive` argument. * * Warning: this method can fail when recursive and cloning a page with huge amounts of * children (or descendent family), and adequate resources (like memory or time limit) are * not available. * * ~~~~~ * // Clone the Westin Peachtree skyscraper page * $building = $pages->get('/skyscrapers/atlanta/westin-peachtree/'); * $copy = $pages->clone($building); * * // Bonus: Now that the clone exists, lets move and rename it * $copy->parent = '/skyscrapers/detroit/'; * $copy->title = 'Renaissance Center'; * $copy->name = 'renaissance-center'; * $copy->save(); * ~~~~~ * * #pw-group-manipulation * * @param Page $page Page that you want to clone * @param Page|null $parent New parent, if different (default=null, which implies same parent) * @param bool $recursive Clone the children too? (default=true) * @param array|string $options Options that can be passed to modify default behavior of clone or save: * - `forceID` (int): force a specific ID. * - `set` (array): Array of properties to set to the clone (you can also do this later). * - `recursionLevel` (int): recursion level, for internal use only. * @return Page|NullPage The newly cloned Page or a NullPage() with id=0 if unsuccessful. * @throws WireException|\Exception on fatal error * */ public function ___clone(Page $page, Page $parent = null, $recursive = true, $options = array()) { return $this->editor()->_clone($page, $parent, $recursive, $options); } /** * Permanently delete a page, its fields and assets. * * Unlike trash(), pages deleted here are not restorable. If you attempt to delete a page with children, * and don't specifically set the `$recursive` argument to `true`, then this method will throw an exception. * If a recursive delete fails for any reason, an exception will also will be thrown. * * ~~~~~ * // Delete a product page * $product = $pages->get('/products/foo-bar-widget/'); * $pages->delete($product); * ~~~~~ * * #pw-group-manipulation * * @param Page $page Page to delete * @param bool|array $recursive If set to true, then this will attempt to delete all children too. * If you don't need this argument, optionally provide $options array instead. * @param array $options Optional settings to change behavior: * - uncacheAll (bool): Whether to clear memory cache after delete (default=false) * - recursive (bool): Same as $recursive argument, may be specified in $options array if preferred. * @return bool|int Returns true (success), or integer of quantity deleted if recursive mode requested. * @throws WireException on fatal error * @see Pages::trash() * */ public function ___delete(Page $page, $recursive = false, array $options = array()) { return $this->editor()->delete($page, $recursive, $options); } /** * Move a page to the trash * * When a page is moved to the trash, it is in a "delete pending" state. Once trashed, the page can be either restored * to its original location, or permanently deleted (when the trash is emptied). * * ~~~~~ * // Trash a product page * $product = $pages->get('/products/foo-bar-widget/'); * $pages->trash($product); * ~~~~~ * * #pw-group-manipulation * * @param Page $page Page to trash * @param bool $save Set to false if you will perform your own save() call afterwards to complete the operation. Omit otherwise. Primarily for internal use. * @return bool Returns true on success, false on failure. * @throws WireException * @see Pages::restore(), Pages::emptyTrash(), Pages::delete() * */ public function ___trash(Page $page, $save = true) { // If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. return $this->trasher()->trash($page, $save); } /** * Restore a page in the trash back to its original location and state * * If you want to restore the page to some location other than its original location, set the `$page->parent` property * of the page to contain the location you want it to restore to. Otherwise the page will restore to its original location, * when possible to do so. * * ~~~~~ * // Grab a page from the trash and restore it * $trashedPage = $pages->get(1234); * $pages->restore($trashedPage); * ~~~~~ * * #pw-group-manipulation * * @param Page $page Page that is in the trash that you want to restore * @param bool $save Set to false if you only want to prep the page for restore (i.e. you will save the page yourself later). Primarily for internal use. * @return bool True on success, false on failure. * @see Pages::trash() * */ public function ___restore(Page $page, $save = true) { return $this->trasher()->restore($page, $save); } /**************************************************************************************************************** * ADVANCED PAGES API METHODS (more for internal use) * */ /** * Delete all pages in the trash * * Note that once the trash is emptied, pages in the trash are permanently deleted. * This method populates error notices when there are errors deleting specific pages. * * ~~~~~ * // Empty the trash * $pages->emptyTrash(); * ~~~~~ * * #pw-group-manipulation * * @param array $options See PagesTrash::emptyTrash() for advanced options * @return int|array Returns total number of pages deleted from trash, or array if verbose option specified. * This number is negative or 0 if not all pages could be deleted and error notices may be present. * @see Pages::trash(), Pages::restore() * */ public function ___emptyTrash(array $options = array()) { return $this->trasher()->emptyTrash($options); } /** * Given an array or CSV string of Page IDs, return a PageArray (internal API) * * Note that this method is primarily for internal use and most of the options available are specific to the needs * of core methods that utilize them. All pages loaded by ProcessWire pass through this method. * * Optionally specify an `$options` array rather than a template for argument 2. When present, the `template` and `parent_id` * arguments may be provided in the given $options array. These options may be specified: * * **LOAD OPTIONS (argument 2 array):** * * - `cache` (boolean): Place loaded pages in memory cache? (default=true) * - `getFromCache` (boolean): Allow use of previously cached pages in memory (rather than re-loading it from DB)? (default=true) * - `template` (Template): Instance of Template, see the $template argument for details. * - `parent_id` (integer): Parent ID, see $parent_id argument for details. * - `getNumChildren` (boolean): Specify false to disable retrieval and population of 'numChildren' Page property. (default=true) * - `getOne` (boolean): Specify true to return just one Page object, rather than a PageArray. (default=false) * - `autojoin` (boolean): Allow use of autojoin option? (default=true) * - `joinFields` (array): Autojoin the field names specified in this array, regardless of field settings (requires autojoin=true). (default=empty) * - `joinSortfield` (boolean): Whether the 'sortfield' property will be joined to the page. (default=true) * - `findTemplates` (boolean): Determine which templates will be used (when no template specified) for more specific autojoins. (default=true) * - `pageClass` (string): Class to instantiate Page objects with. Leave blank to determine from template. (default=auto-detect) * - `pageArrayClass` (string): PageArray-derived class to store pages in (when 'getOne' is false). (default=PageArray) * - `pageArray` (PageArray|null): Populate this existing PageArray rather than creating a new one. (default=null) * - `page` (Page|null): Existing Page object to populate (also requires the getOne option to be true). (default=null) * * **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. * - Specify false for `getNumChildren` for potential speed optimization when you know for certain pages will not have children. * - Specify false for `autojoin` for potential speed optimization in certain scenarios (can also be a bottleneck, so be sure to test). * - Specify false for `joinSortfield` for potential speed optimization when you know the Page will not have children or won't need to know the order. * - Specify false for `findTemplates` so this method doesn't have to look them up. Potential speed optimization if you have few autojoin fields globally. * - Note that if you specify false for `findTemplates` the pageClass is assumed to be 'Page' unless you specify something different for the 'pageClass' option. * * ~~~~~ * // Retrieve pages by IDs in CSV string * $items = $pages->getById("1111,2222,3333"); * * // Retrieve pages by IDs in PHP array * $items = $pages->getById([1111,2222,3333]); * * // Specify that retrieved pages are using template 'skyscraper' as an optimization * $items = $pages->getById([1111,2222,3333], $templates->get('skyscraper')); * * // Retrieve pages with $options array * $items = $pages->getById([1111,2222,3333], [ * 'template' => $templates->get('skyscraper'), * 'parent_id' => 1024 * ]); * ~~~~~ * * #pw-internal * * @param array|WireArray|string $_ids Array of Page IDs or CSV string of Page IDs. * @param Template|array|null $template Specify a template to make the load faster, because it won't have to attempt to join all possible fields... just those used by the template. * Optionally specify an $options array instead, see the method notes above. * @param int|null $parent_id Specify a parent to make the load faster, as it reduces the possibility for full table scans. * This argument is ignored when an options array is supplied for the $template. * @return PageArray|Page Returns Page only if the 'getOne' option is specified, otherwise always returns a PageArray. * @throws WireException * */ public function getById($_ids, $template = null, $parent_id = null) { return $this->loader->getById($_ids, $template, $parent_id); } /** * Given an ID, return a path to a page, without loading the actual page * * 1. Always returns path in default language, unless a language argument/option is specified. * 2. Path may be different from 'url' as it doesn't include the root URL at the beginning. * 3. In most cases, it's preferable to use `$page->path()` rather than this method. This method is * here just for cases where a path is needed without loading the page. * 4. It's possible for there to be `Page::path()` hooks, and this method completely bypasses them, * which is another reason not to use it unless you know such hooks aren't applicable to you. * * ~~~~~ * // Get the path for page having ID 1234 * $path = $pages->getPath(1234); * echo "Path for page 1234 is: $path"; * ~~~~~ * * #pw-advanced * * @param int|Page $id ID of the page you want the path to * @param null|array|Language|int|string $options Specify $options array or Language object, id or name. Allowed options include: * - `language` (int|string|anguage): To retrieve in non-default language, specify language object, ID or name (default=null) * - `useCache` (bool): Allow pulling paths from already loaded pages? (default=true) * - `usePagePaths` (bool): Allow pulling paths from PagePaths module, if installed? (default=true) * @return string Path to page or blank on error/not-found. * @since 3.0.6 * @see Page::path() * */ public function getPath($id, $options = array()) { return $this->loader->getPath($id, $options); } /** * Alias of getPath method for backwards compatibility * * @param int $id * @return string * */ public function _path($id) { return $this->loader->getPath($id); } /** * Get a page by its path, similar to $pages->get('/path/to/page/') but with more options * * 1. There are no exclusions for page status or access. If needed, you should validate access * on any page returned from this method. * 2. In a multi-language environment, you must specify the `$useLanguages` option to be true, if you * want a result for a $path that is (or might be) a multi-language path. Otherwise, multi-language * paths will make this method return a NullPage (or 0 if getID option is true). * 3. Partial paths may also match, so long as the partial path is completely unique in the site. * If you don't want that behavior, double check the path of the returned page. * * ~~~~~ * // Get a page by path * $p = $pages->getByPath('/skyscrapers/atlanta/191-peachtree/'); * * // Now validate that the page we retrieved is valid * if($p->id && $p->viewable()) { * // Page is valid to display * } * * // Get a page by path with options * $p = $pages->getByPath('/products/widget/', [ * 'useLanguages' => true, * 'useHistory' => true * ]); * ~~~~~ * * #pw-advanced * * @param string $path Path of page you want to retrieve. * @param array|bool $options array of options (below), or specify boolean for $useLanguages option only. * - `getID` (int): Specify true to just return the page ID (default=false). * - `useLanguages` (bool): Specify true to allow retrieval by language-specific paths (default=false). * - `useHistory` (bool): Allow use of previous paths used by the page, if PagePathHistory module is installed (default=false). * - `allowUrl` (bool): Allow getting page by path OR url? Specify false to find only by path. This option only applies if * the site happens to run from a subdirectory. (default=true) 3.0.184+ * - `allowPartial` (bool): Allow partial paths to match? (default=true) 3.0.184+ * - `allowUrlSegments` (bool): Allow paths with URL segments to match? When true and page match cannot be found, the closest * parent page that allows URL segments will be returned. Found URL segments are populated to a `_urlSegments` array * property on the returned page object. This also cancels the allowPartial setting. (default=false) 3.0.184+ * @return Page|int * @since 3.0.6 * */ public function getByPath($path, $options = array()) { return $this->loader->getByPath($path, $options); } /** * Auto-populate some fields for a new page that does not yet exist * * Currently it does this: * - Sets up a unique page->name based on the format or title if one isn't provided already. * - Assigns a 'sort' value'. * * #pw-internal * * @param Page $page * */ public function ___setupNew(Page $page) { return $this->editor()->setupNew($page); } /** * Auto-assign a page name to the given page * * Typically this would be used only if page had no name or if it had a temporary untitled name. * * Page will be populated with the name given. This method will not populate names to pages that * already have a name, unless the name is "untitled" * * #pw-internal * * @param Page $page * @param array $options * - format: Optionally specify the format to use, or leave blank to auto-determine. * @return string If a name was generated it is returned. If no name was generated blank is returned. * */ public function ___setupPageName(Page $page, array $options = array()) { return $this->editor()->setupPageName($page, $options); } /** * Update page modification time to now (or the given modification time) * * This behaves essentially the same as the unix `touch` command, but for ProcessWire pages. * * ~~~~~ * // Touch the current $page to current date/time * $pages->touch($page); * * // Touch the current $page and set modification date to 2016/10/24 * $pages->touch($page, "2016-10-24 00:00"); * * // Touch all "skyscraper" pages in "Atlanta" to current date/time * $skyscrapers = $pages->find("template=skyscraper, parent=/cities/atlanta/"); * $pages->touch($skyscrapers); * ~~~~~ * * #pw-group-manipulation * * @param Page|PageArray|array $pages May be Page, PageArray or array of page IDs (integers) * @param null|int|string|array $options Omit (null) to update to now, or unix timestamp or strtotime() recognized time string, * or if you do not need this argument, you may optionally substitute the $type argument here, * or in 3.0.183+ you can also specify array of options here instead: * - `time` (string|int|null): Unix timestamp or strtotime() recognized string to use, omit for use current time (default=null) * - `type` (string): One of 'modified', 'created', 'published' (default='modified') * - `user` (bool|User): True to also update modified/created user to current user, or specify User object to use (default=false) * @param string $type Date type to update, one of 'modified', 'created' or 'published' (default='modified') Added 3.0.147 * Skip this argument if using options array for previous argument or if using the default type 'modified'. * @throws WireException|\PDOException if given invalid format for $modified argument or failed database query * @return bool True on success, false on fail * @since 3.0.0 * */ public function ___touch($pages, $options = null, $type = 'modified') { return $this->editor()->touch($pages, $options, $type); } /** * Set the “sort” value for given $page while adjusting siblings, or re-build sort for its children * * *This method is primarily applicable to manually sorted pages. If pages are automatically * sorted by some other field, this method isn’t useful unless using the “re-build children” option, * which may be helpful if converting a page’s children from auto-sort to manual sort.* * * The default behavior of this method is to set the “sort” value for the given $page, and adjust the * sort value of sibling pages having the same or greater sort value, to ensure all are unique and in * order without gaps. * * The alternate behavior of this method is to re-build the sort values of all children of the given $page. * This is done by specifying boolean true for the $value argument. When used, duplicate sort values and * gaps are removed from all children. * * **Do you need this method?** * If you are wondering whether you need to use this method for something, chances are that you do not. * This method is mostly applicable for internal core use, as ProcessWire manages Page sort values on its own * internally for the most part. * * ~~~~~ * // set $page to have sort=5, moving any 5+ sort pages ahead * $pages->sort($page, 5); * * // re-build sort values for children of $page, removing duplicates and gaps * $pages->sort($page, true); * ~~~~~ * * #pw-advanced * * @param Page $page Page to sort (or parent of pages to sort, if using $value=true option) * @param int|bool $value Specify one of the following: * - Omit to set and use sort value from given $page. * - Specify sort value (integer) to save that value. * - Specify boolean true to instead rebuild sort for all of $page children. * @return int Number of pages that had sort values adjusted * @throws WireException * */ public function ___sort(Page $page, $value = false) { if($value === false) $value = $page->sort; if($value === true) return $this->editor()->sortRebuild($page); return $this->editor()->sortPage($page, $value); } /** * Sort/move one page above another (for manually sorted pages) * * #pw-advanced * * @param Page $page Page you want to move/sort * @param Page $beforePage Page you want to insert before * @throws WireException * */ public function ___insertBefore(Page $page, Page $beforePage) { $this->editor()->insertBefore($page, $beforePage); } /** * Sort/move one page after another (for manually sorted pages) * * #pw-advanced * * @param Page $page Page you want to move/sort * @param Page $afterPage Page you want to insert after * @throws WireException * */ public function ___insertAfter(Page $page, Page $afterPage) { $this->editor()->insertBefore($page, $afterPage, true); } /** * Is the given page in a state where it can be saved from the API? * * Note: this does not account for user permission checking. * It only checks if the page is in a state to be saveable via the API. * * #pw-internal * * @param Page $page * @param string $reason Text containing the reason why it can't be saved (assuming it's not saveable) * @param string|Field $fieldName Optional fieldname to limit check to. * @param array $options Options array given to the original save method (optional) * @return bool True if saveable, False if not * */ public function isSaveable(Page $page, &$reason, $fieldName = '', array $options = array()) { return $this->editor()->isSaveable($page, $reason, $fieldName, $options); } /** * Is the given page deleteable from the API? * * Note: this does not account for user permission checking. * It only checks if the page is in a state to be deleteable via the API. * * #pw-internal * * @param Page $page * @return bool True if deleteable, False if not * */ public function isDeleteable(Page $page) { return $this->editor()->isDeleteable($page); } /** * 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 * * #pw-internal * * @param int|string|null $id * @return Page|array|null * */ public function getCache($id = null) { return $this->cacher->getCache($id); } /** * Cache the given page. * * #pw-internal * * @param Page $page * @return void * */ public function cache(Page $page) { $this->cacher->cache($page); } /** * Remove the given page(s) from the cache, or uncache all by omitting $page argument * * When no $page argument is given, this method behaves the same as $pages->uncacheAll(). * When any $page argument is given, this does not remove pages from selectorCache. * * #pw-internal * * @param Page|PageArray|int|null $page Page to uncache, PageArray of pages to uncache, ID of page to uncache (3.0.153+), or omit to uncache all. * @param array $options Additional options to modify behavior: * - `shallow` (bool): By default, this method also calls $page->uncache(). To prevent that call, set this to true. * @return int Number of pages uncached * */ public function uncache($page = null, array $options = array()) { $cnt = 0; if(is_null($page)) { $cnt = $this->cacher->uncacheAll(null, $options); } else if($page instanceof Page || is_int($page)) { if($this->cacher->uncache($page, $options)) $cnt++; } else if($page instanceof PageArray) { foreach($page as $p) { if($this->cacher->uncache($p, $options)) $cnt++; } } return $cnt; } /** * Remove all pages from the cache (to clear memory) * * This method clears all pages that ProcessWire has cached in memory, making room for more pages to be loaded. * Use of this method (along with pagination) may be necessary when modifying or calculating from thousand of pages. * * ~~~~~ * // calculate total dollar value of all 50000+ products in inventory * $total = 0; * $start = 0; * $limit = 500; * * do { * $products = $pages->find("template=product, start=$start, limit=$limit"); * if(!$products->count()) break; * foreach($products as $product) { * $total += ($product->qty * $product->price); * } * unset($products); * $start += $limit; * // clear cache to make room for another 500 products * $pages->uncacheAll(); * } while(true); * * echo "Total value of all products: $" . number_format($total); * ~~~~~ * * #pw-advanced * * @param Page $page Optional Page that initiated the uncacheAll * @param array $options Options to modify default behavior: * - `shallow` (bool): By default, this method also calls $page->uncache(). To prevent that call, set this to true. * @return int Number of pages uncached * */ public function uncacheAll(Page $page = null, array $options = array()) { return $this->cacher->uncacheAll($page, $options); } /** * For internal Page instance access, return the Pages sortfields property * * #pw-internal * * @param bool $reset Specify boolean true to reset the Sortfields instance * @return PagesSortFields * */ public function sortfields($reset = false) { if($reset) { unset($this->sortfields); $this->sortfields = $this->wire(new PagesSortfields()); } return $this->sortfields; } /** * Return a fuel or other property set to the Pages instance * * @param string $key * @return mixed * */ public function __get($key) { if($key == 'outputFormatting') return $this->loader->getOutputFormatting(); if($key == 'cloning') return $this->editor()->isCloning(); if($key == 'autojoin') return $this->loader->getAutojoin(); return parent::__get($key); } /** * Set whether loaded pages have their outputFormatting turn on or off * * This affects pages loaded after this method has been called. * By default, output formatting is turned on on the front-end of the site, * and off on the back-end (admin) of the site. * * See the Pages::of() method alias, which is preferred for the public API. * * #pw-internal * * @param bool $outputFormatting * */ public function setOutputFormatting($outputFormatting = true) { $this->loader->setOutputFormatting($outputFormatting); } /** * Get or set the current output formatting state * * This affects pages loaded after this method has been called. * By default, output formatting is turned on on the front-end of the site, * and off on the back-end (admin) of the site. * * ~~~~~ * // Dictate that loaded pages should have output formatting enabled * $pages->of(true); * * // Get the output formatting state for future loaded pages * if($pages->of()) { * echo "Output formatting is ON"; * } else { * echo "Output formatting is OFF"; * } * ~~~~~ * * #pw-advanced * * @param null|bool $of Specify boolean to set output formatting state, or omit to get output formatting state. * @return bool Returns current output formatting state. * */ public function of($of = null) { if($of !== null) $this->setOutputFormatting($of ? true : false); return $this->outputFormatting; } /** * Log a Pages class event * * Only active in debug mode. * * #pw-internal * * @param string $action Name of action/function that occurred. * @param string $details Additional details, like a selector string. * @param string|object The value that was returned. * */ public function debugLog($action = '', $details = '', $result = '') { if(!$this->debug) return; $this->debugLog[] = array( 'time' => microtime(), 'action' => (string) $action, 'details' => (string) $details, 'result' => (string) $result ); } /** * Get the Pages class debug log * * Only active in debug mode * * #pw-internal * * @param string $action Optional action within the debug log to find * @return array * */ public function getDebugLog($action = '') { if(!$this->wire('config')->debug) return array(); if(!$action) return $this->debugLog; $debugLog = array(); foreach($this->debugLog as $item) if($item['action'] == $action) $debugLog[] = $item; return $debugLog; } /** * Return a PageFinder object, ready to use * * #pw-internal * * @return PageFinder * */ public function getPageFinder() { return $this->wire(new PageFinder()); } /** * Enable or disable use of autojoin for all queries * * Default should always be true, and you may use this to turn it off temporarily, but * you should remember to turn it back on * * #pw-internal * * @param bool $autojoin * */ public function setAutojoin($autojoin = true) { $this->loader->setAutojoin($autojoin); } /** * Return a new/blank PageArray * * #pw-internal * * @param array $options Optionally specify ONE of the following: * - `pageArrayClass` (string): Name of PageArray class to use (if not “PageArray”). * - `pageArray` (PageArray): Wire and return this given PageArray, rather than instantiating a new one. * @return PageArray * */ public function newPageArray(array $options = array()) { if(!empty($options['pageArray']) && $options['pageArray'] instanceof PageArray) { $this->wire($options['pageArray']); return $options['pageArray']; } $class = 'PageArray'; if(!empty($options['pageArrayClass'])) $class = $options['pageArrayClass']; $class = wireClassName($class, true); $pageArray = $this->wire(new $class()); if(!$pageArray instanceof PageArray) $pageArray = $this->wire(new PageArray()); return $pageArray; } /** * Return a new/blank Page object (in memory only) * * #pw-internal * * @param array|string|Template $options Optionally specify array of any of the following: * - `template` (Template|id|string): Template to use via object, ID or name. * - `pageClass` (string): Class to use for Page. If not specified, default is from template setting, or 'Page' if no template. * - Any other Page properties or fields you want to set (parent, name, title, etc.). Note that most page fields will need to * have a `template` set first, so make sure to include it in your options array when providing other fields. * - In PW 3.0.152+ you may specify the Template object, name or ID instead of an $options array. * @return Page * */ public function newPage($options = array()) { if(!is_array($options)) { if(is_object($options) && $options instanceof Template) { $options = array('template' => $options); } else if($options && (is_string($options) || is_int($options))) { $options = array('template' => $options); } else { $options = array(); } } if(!empty($options['pageClass'])) { $class = $options['pageClass']; } else { $class = 'Page'; } if(!empty($options['template'])) { $template = $options['template']; if(!is_object($template)) { $template = empty($template) ? null : $this->wire('templates')->get($template); } if($template && empty($options['pageClass'])) { $class = $template->getPageClass(); } } else { $template = null; } if(strpos($class, "\\") === false) $class = wireClassName($class, true); $page = $this->wire(new $class($template)); if(!$page instanceof Page) $page = $this->wire(new Page($template)); unset($options['pageClass'], $options['template']); foreach($options as $name => $value) { $page->set($name, $value); } return $page; } /** * Return a new NullPage * * #pw-internal * * @return NullPage * */ public function newNullPage() { $page = new NullPage(); $this->wire($page); return $page; } /** * Execute a PDO statement, with retry and error handling (deprecated) * * #pw-internal * * @param \PDOStatement $query * @param bool $throw Whether or not to throw exception on query error (default=true) * @param int $maxTries Max number of times it will attempt to retry query on error * @return bool * @throws \PDOException * @deprecated Use $database->execute() instead * */ public function executeQuery(\PDOStatement $query, $throw = true, $maxTries = 3) { return $this->wire('database')->execute($query, $throw, $maxTries); } /** * Enables use of $pages(123), $pages('/path/') or $pages('selector string') * * When given an integer or page path string, it calls $pages->get(key); * When given a string, it calls $pages->find($key); * When given an array, it calls $pages->getById($key); * * @param string|int|array $key * @return Page|Pages|PageArray * */ public function __invoke($key) { if(empty($key)) return $this; // no argument if(is_int($key)) return $this->get($key); // page ID if(is_array($key) && ctype_digit(implode('', $key))) return $this->getById($key); // array of page IDs if(is_string($key) && strpos($key, '/') !== false && $this->sanitizer->pagePathName($key) === $key) return $this->get($key); // page path return $this->find($key); // selector string or array } /** * Save to pages activity log, if enabled in config * * #pw-internal * * @param $str * @param Page|null Page to log * @return WireLog * */ public function log($str, Page $page) { if(!in_array('pages', $this->wire('config')->logs)) return parent::___log(); if($this->wire('process') != 'ProcessPageEdit') $str .= " [From URL: " . $this->wire('input')->url() . "]"; $options = array('name' => 'pages', 'url' => $page->path); return parent::___log($str, $options); } /** * @return PagesLoader * * #pw-internal * */ public function loader() { return $this->loader; } /** * @return PagesEditor * * #pw-internal * */ public function editor() { if(!$this->editor) $this->editor = $this->wire(new PagesEditor($this)); return $this->editor; } /** * Get Pages API methods specific to generating and modifying page names * * @return PagesNames * * #pw-advanced * */ public function names() { if(!$this->names) $this->names = $this->wire(new PagesNames($this)); return $this->names; } /** * @return PagesLoaderCache * * #pw-internal * */ public function cacher() { return $this->cacher; } /** * @return PagesTrash * * #pw-internal * */ public function trasher() { if(!$this->trasher) $this->trasher = $this->wire(new PagesTrash($this)); return $this->trasher; } /** * @return PagesParents * * #pw-internal * */ public function parents() { if(!$this->parents) $this->parents = $this->wire(new PagesParents($this)); return $this->parents; } /** * @return PagesRaw * @since 3.0.172 * * #pw-internal * */ public function raw() { if(!$this->raw) $this->raw = $this->wire(new PagesRaw($this)); return $this->raw; } /** * Get array of all PagesType managers * * #pw-internal * * @param PagesType|string Specify a PagesType object to add a Manager, or specify class name to retrieve manager * @return array|PagesType|null|bool Returns requested type, null if not found, or boolean true if manager added. * */ public function types($type = null) { if(!$type) return $this->types; if(is_string($type)) return isset($this->types[$type]) ? $this->types[$type] : null; if(!$type instanceof PagesType) return null; $name = $type->className(); $this->types[$name] = $type; return true; } /** * Get or set debug state * * #pw-internal * * @param bool|null $debug * @return bool * */ public function debug($debug = null) { $value = $this->debug; if(!is_null($debug)) { $this->debug = (bool) $debug; $this->loader->debug($debug); } return $value; } /*********************************************************************************************************************** * COMMON PAGES HOOKS * */ /** * Hook called after a page is successfully saved * * This is the same as hooking after `Pages::save`, except that it occurs before other save-related hooks. * Whereas `Pages::save` hooks occur after. In most cases, the distinction does not matter. * * #pw-hooker * * @param Page $page The page that was saved * @param array $changes Array of field names that changed * @param array $values Array of values that changed, if values were being recorded, see Wire::getChanges(true) for details. * */ public function ___saved(Page $page, array $changes = array(), $values = array()) { $str = "Saved page"; if(count($changes)) $str .= " (Changes: " . implode(', ', $changes) . ")"; $this->log($str, $page); /** @var WireCache $cache */ $cache = $this->wire('cache'); $cache->maintenance($page); foreach($this->types as $manager) { if($manager->hasValidTemplate($page)) $manager->saved($page, $changes, $values); } } /** * Hook called after a new page has been added * * #pw-hooker * * @param Page $page Page that was added. * */ public function ___added(Page $page) { $this->log("Added page", $page); foreach($this->types as $manager) { if($manager->hasValidTemplate($page)) $manager->added($page); } $page->setQuietly('_added', true); } /** * Hook called when a page has been moved from one parent to another * * Note the previous parent is accessible in the `$page->parentPrevious` property. * * #pw-hooker * * @param Page $page Page that was moved. * */ public function ___moved(Page $page) { if($page->parentPrevious) { $this->log("Moved page from {$page->parentPrevious->path}$page->name/", $page); } else { $this->log("Moved page", $page); } } /** * Hook called when a page's template has been changed * * Note the previous template is available in the `$page->templatePrevious` property. * * #pw-hooker * * @param Page $page Page that had its template changed. * */ public function ___templateChanged(Page $page) { if($page->templatePrevious) { $this->log("Changed template on page from '$page->templatePrevious' to '$page->template'", $page); } else { $this->log("Changed template on page to '$page->template'", $page); } } /** * Hook called when a Page is about to be trashed * * @param Page $page * @since 3.0.163 * */ public function ___trashReady(Page $page) { if($page) {} // ignore } /** * Hook called when a page has been moved to the trash * * #pw-hooker * * @param Page $page Page that was moved to the trash * */ public function ___trashed(Page $page) { $this->log("Trashed page", $page); } /** * Hook called when a page has been moved OUT of the trash (restored) * * #pw-hooker * * @param Page $page Page that was restored * */ public function ___restored(Page $page) { $this->log("Restored page", $page); } /** * Hook called just before a page is saved * * May be preferable to a before `Pages::save` hook because you know for sure a save will * be executed immediately after this is called. Whereas you don't necessarily know * that when the before `Pages::save` is called, as an error may prevent it. * * #pw-hooker * * @param Page $page The page about to be saved * @return array Optional extra data to add to pages save query, which the hook can populate. * */ public function ___saveReady(Page $page) { $data = array(); foreach($this->types as $manager) { if(!$manager->hasValidTemplate($page)) continue; $a = $manager->saveReady($page); if(!empty($a) && is_array($a)) $data = array_merge($data, $a); } return $data; } /** * Hook called when a page is about to be deleted, but before data has been touched * * This is different from a before `Pages::delete` hook because this hook is called once it has * been confirmed that the page is deleteable and *will* be deleted. * * #pw-hooker * * @param Page $page Page that is about to be deleted. * @param array $options Options passed to delete method (since 3.0.163) * */ public function ___deleteReady(Page $page, array $options = array()) { if($options) {} // ignore foreach($this->types as $manager) { if($manager->hasValidTemplate($page)) $manager->deleteReady($page); } } /** * Hook called after a page and its data have been deleted * * #pw-hooker * * @param Page $page Page that was deleted * @param array $options Options passed to delete method (since 3.0.163) * */ public function ___deleted(Page $page, array $options = array()) { if($options) {} if(empty($options['_deleteBranch'])) $this->log("Deleted page", $page); /** @var WireCache $cache */ $cache = $this->wire('cache'); $cache->maintenance($page); foreach($this->types as $manager) { if($manager->hasValidTemplate($page)) $manager->deleted($page); } } /** * Hook called before a branch of pages is about to be deleted, called on root page of branch only * * Note: this is called only on deletions that had 'recursive' option true and 1+ children. * * #pw-hooker * * @param Page $page Page that was deleted * @param array $options Options passed to delete method * @since 3.0.163 * */ public function ___deleteBranchReady(Page $page, array $options) { if($page && $options) {} } /** * Hook called after a a branch of pages has been deleted, called on root page of branch only * * Note: this is called only on deletions that had 'recursive' option true and 1+ children. * * #pw-hooker * * @param Page $page Page that was the root of the branch * @param array $options Options passed to delete method * @param int $numDeleted Number of pages deleted * @since 3.0.163 * */ public function ___deletedBranch(Page $page, array $options, $numDeleted) { if($page && $options) {} $this->log("Deleted branch with $numDeleted page(s)", $page); } /** * Hook called when a page is about to be cloned, but before data has been touched * * #pw-hooker * * @param Page $page The original page to be cloned * @param Page $copy The actual clone about to be saved * */ public function ___cloneReady(Page $page, Page $copy) { } /** * Hook called when a page has been cloned * * #pw-hooker * * @param Page $page The original page to be cloned * @param Page $copy The completed cloned version of the page * */ public function ___cloned(Page $page, Page $copy) { $this->log("Cloned page to $copy->path", $page); } /** * Hook called when a page has been renamed (i.e. had its name field change) * * The previous name can be accessed at `$page->namePrevious`. * The new name can be accessed at `$page->name`. * * This hook is only called when a page's name changes. It is not called when * a page is moved unless the name was changed at the same time. * * **Multi-language note:** * Also note this hook may be called if a page's multi-language name changes. * In those cases the language-specific name is stored in "name123" while the * previous value is stored in "-name123" (where 123 is the language ID). * * #pw-hooker * * @param Page $page The $page that was renamed * */ public function ___renamed(Page $page) { if($page->namePrevious && $page->namePrevious != $page->name) { $this->log("Renamed page from '$page->namePrevious' to '$page->name'", $page); } } /** * Hook called after a page has been sorted, or had its children re-sorted * * #pw-hooker * * @param Page $page Page given to have sort adjusted * @param bool $children If true, children of $page have been all been re-sorted * @param int $total Total number of pages that had sort adjusted as a result * */ public function ___sorted(Page $page, $children = false, $total = 0) { if($page && $children && $total) {} } /** * Hook called when a page status has been changed and saved * * Previous status may be accessed at `$page->statusPrevious`. * * #pw-hooker * * @param Page $page * */ public function ___statusChanged(Page $page) { $status = $page->status; $statusPrevious = $page->statusPrevious; $isPublished = !$page->isUnpublished(); $wasPublished = !($statusPrevious & Page::statusUnpublished); if($isPublished && !$wasPublished) $this->published($page); if(!$isPublished && $wasPublished) $this->unpublished($page); $from = array(); $to = array(); foreach(Page::getStatuses() as $name => $flag) { if($flag == Page::statusUnpublished) continue; // logged separately if($statusPrevious & $flag) $from[] = $name; if($status & $flag) $to[] = $name; } if(count($from) || count($to)) { $added = array(); $removed = array(); foreach($from as $name) if(!in_array($name, $to)) $removed[] = $name; foreach($to as $name) if(!in_array($name, $from)) $added[] = $name; $str = ''; if(count($added)) $str = "Added status '" . implode(', ', $added) . "'"; if(count($removed)) { if($str) $str .= ". "; $str .= "Removed status '" . implode(', ', $removed) . "'"; } if($str) $this->log($str, $page); } } /** * Hook called when a page's status is about to be changed and saved * * Previous status may be accessed at `$page->statusPrevious`. * * #pw-hooker * * @param Page $page * */ public function ___statusChangeReady(Page $page) { $isPublished = !$page->isUnpublished(); $wasPublished = !($page->statusPrevious & Page::statusUnpublished); if($isPublished && !$wasPublished) $this->publishReady($page); if(!$isPublished && $wasPublished) $this->unpublishReady($page); } /** * Hook called after an unpublished page has just been published * * #pw-hooker * * @param Page $page * */ public function ___published(Page $page) { $this->log("Published page", $page); } /** * Hook called after published page has just been unpublished * * #pw-hooker * * @param Page $page * */ public function ___unpublished(Page $page) { $this->log("Unpublished page", $page); } /** * Hook called right before an unpublished page is published and saved * * #pw-hooker * * @param Page $page * */ public function ___publishReady(Page $page) { } /** * Hook called right before a published page is unpublished and saved * * #pw-hooker * * @param Page $page * */ public function ___unpublishReady(Page $page) { } /** * Hook called at the end of a $pages->find(), includes extra info not seen in the resulting PageArray * * #pw-hooker * * @param PageArray $pages The pages that were found * @param array $details Extra information on how the pages were found, including: * - `pageFinder` (PageFinder): The PageFinder instance that was used. * - `pagesInfo` (array): The array returned by PageFinder. * - `options` (array): Options that were passed to $pages->find(). * */ public function ___found(PageArray $pages, array $details) { } /** * Hook called when Pages::saveField is ready to execute * * #pw-hooker * * @param Page $page * @param Field $field * */ public function ___saveFieldReady(Page $page, Field $field) { } /** * Hook called after Pages::saveField successfully executes * * #pw-hooker * * @param Page $page * @param Field $field * */ public function ___savedField(Page $page, Field $field) { $this->log("Saved page field '$field->name'", $page); } /** * Hook called when either of Pages::save or Pages::saveField is ready to execute * * #pw-hooker * * @param Page $page * @param string $fieldName Populated only if call originates from saveField * */ public function ___savePageOrFieldReady(Page $page, $fieldName = '') { } /** * Hook called after either of Pages::save or Pages::saveField successfully executes * * #pw-hooker * * @param Page $page * @param array $changes Names of fields * */ public function ___savedPageOrField(Page $page, array $changes = array()) { } }