From c1f4693ca0a820815608b02ae12b9b45588d7da1 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Tue, 22 Nov 2016 14:26:41 -0500 Subject: [PATCH] Documentation updates to several core classes --- wire/core/Notices.php | 103 +++++++++++++++-- wire/core/PageArray.php | 5 + wire/core/Pagefile.php | 3 + wire/core/Pageimage.php | 10 +- wire/core/PagesType.php | 145 ++++++++++++++++++++---- wire/core/ProcessWire.php | 161 ++++++++++++++++++++------ wire/core/Selector.php | 188 +++++++++++++++++++++++++------ wire/core/Selectors.php | 89 +++++++++++++-- wire/core/Session.php | 34 +++++- wire/core/SessionCSRF.php | 59 +++++++++- wire/core/WireDatabaseBackup.php | 103 +++++++++++++---- wire/core/WireDatabasePDO.php | 4 +- wire/core/WireHttp.php | 17 ++- 13 files changed, 774 insertions(+), 147 deletions(-) diff --git a/wire/core/Notices.php b/wire/core/Notices.php index 78984e54..22b057af 100644 --- a/wire/core/Notices.php +++ b/wire/core/Notices.php @@ -3,6 +3,10 @@ /** * ProcessWire Notices * + * #pw-summary Manages notifications in the ProcessWire admin, primarily for internal use. + * #pw-use-constants + * #pw-use-constructor + * * Base class that holds a message, source class, and timestamp. * Contains notices/messages used by the application to the user. * @@ -28,6 +32,8 @@ abstract class Notice extends WireData { /** * Flag indicates the notice is a warning * + * #pw-internal + * * @deprecated use NoticeWarning instead. * */ @@ -46,7 +52,7 @@ abstract class Notice extends WireData { const logOnly = 16; /** - * Flag indicates the notice is allowed to contain markup and won't be automatically entity encoded + * Flag indicates the notice is allowed to contain markup and won’t be automatically entity encoded * * Note: entity encoding is done by the admin theme at output time, which should detect this flag. * @@ -56,8 +62,8 @@ abstract class Notice extends WireData { /** * Create the Notice * - * @param string $text - * @param int $flags + * @param string $text Notification text + * @param int $flags Flags * */ public function __construct($text, $flags = 0) { @@ -69,6 +75,8 @@ abstract class Notice extends WireData { } /** + * Get the notice log + * * @return string Name of log (basename) * */ @@ -111,21 +119,88 @@ class NoticeWarning extends Notice { /** - * A class to contain multiple Notice instances, whether messages or errors + * ProcessWire Notices + * + * #pw-summary A class to contain multiple Notice instances, whether messages, warnings or errors + * #pw-body = + * This class manages notices that have been sent by `Wire::message()`, `Wire::warning()` and `Wire::error()` calls. + * The message(), warning() and error() methods are available on every `Wire` derived object. This class is primarily + * for internal use in the admin. However, it may also be useful in some front-end contexts. + * ~~~~~ + * // Adding a NoticeMessage using object syntax + * $notices->add(new NoticeMessage("Hello World")); + * + * // Adding a NoticeMessage using regular syntax + * $notices->message("Hello World"); + * + * // Adding a NoticeWarning, and allow markup in it + * $notices->message("Hello World", Notice::allowMarkup); + * + * // Adding a NoticeError that only appears if debug mode is on + * $notices->error("Hello World", Notice::debug); + * ~~~~~ + * Iterating and outputting Notices: + * ~~~~~ + * foreach($notices as $notice) { + * // skip over debug notices, if debug mode isn't active + * if($notice->flags & Notice::debug && !$config->debug) continue; + * // entity encode notices unless the allowMarkup flag is set + * if($notice->flags & Notice::allowMarkup) { + * $text = $notice->text; + * } else { + * $text = $sanitizer->entities($notice->text); + * } + * // output either an error, warning or message notice + * if($notice instanceof NoticeError) { + * echo "

$text

"; + * } else if($notice instanceof NoticeWarning) { + * echo "

$text

"; + * } else { + * echo "

$text

"; + * } + * } + * ~~~~~ + * + * #pw-body + * * */ class Notices extends WireArray { const logAllNotices = false; // for debugging/dev purposes - + + /** + * #pw-internal + * + * @param mixed $item + * @return bool + * + */ public function isValidItem($item) { return $item instanceof Notice; - } + } + /** + * #pw-internal + * + * @return Notice + * + */ public function makeBlankItem() { return $this->wire(new NoticeMessage('')); } + /** + * Add a Notice object + * + * ~~~~ + * $notices->add(new NoticeError("An error occurred!")); + * ~~~~ + * + * @param Notice $item + * @return $this + * + */ public function add($item) { if($item->flags & Notice::debug) { @@ -178,6 +253,12 @@ class Notices extends WireArray { $this->wire('log')->save($item->getName(), $text); } + /** + * Are there NoticeError items present? + * + * @return bool + * + */ public function hasErrors() { $numErrors = 0; foreach($this as $notice) { @@ -185,7 +266,13 @@ class Notices extends WireArray { } return $numErrors > 0; } - + + /** + * Are there NoticeWarning items present? + * + * @return bool + * + */ public function hasWarnings() { $numWarnings = 0; foreach($this as $notice) { @@ -199,6 +286,8 @@ class Notices extends WireArray { * * This enables us to safely print_r the string for debugging purposes * + * #pw-internal + * * @param array $a * @return array * diff --git a/wire/core/PageArray.php b/wire/core/PageArray.php index dbd2ed3a..0bf4e8bf 100644 --- a/wire/core/PageArray.php +++ b/wire/core/PageArray.php @@ -14,6 +14,11 @@ * * PageArray is returned by all API methods in ProcessWire that can return more than one page at once. * `$pages->find()` and `$page->children()` are common examples. + * + * The recommended way to create a new PageArray is to use the `$pages->newPageArray()` method: + * ~~~~~ + * $pageArray = $pages->newPageArray(); + * ~~~~~ * #pw-body * * ProcessWire 3.x, Copyright 2016 by Ryan Cramer diff --git a/wire/core/Pagefile.php b/wire/core/Pagefile.php index 186e805c..1ccf0a8f 100644 --- a/wire/core/Pagefile.php +++ b/wire/core/Pagefile.php @@ -7,6 +7,7 @@ * #pw-summary-traversal For the most part you’ll want to traverse from the parent `Pagefiles` object than these methods. * #pw-summary-manipulation Remember to follow up any manipulations with a `$pages->save()` call. * #pw-summary-tags Be sure to see the `Pagefiles::getTag()` and `Pagesfiles::findTag()` methods, which enable you retrieve files by tag. + * #pw-use-constructor * #pw-body = * Pagefile objects are contained by a `Pagefiles` object. * #pw-body @@ -49,6 +50,8 @@ class Pagefile extends WireData { /** * Reference to the owning collection of Pagefiles + * + * @var Pagefiles * */ protected $pagefiles; diff --git a/wire/core/Pageimage.php b/wire/core/Pageimage.php index 31a8e36e..f1f81e10 100644 --- a/wire/core/Pageimage.php +++ b/wire/core/Pageimage.php @@ -6,6 +6,7 @@ * #pw-summary Represents an image item attached to a page, typically via an Image Fieldtype. * #pw-summary-variations A variation refers to an image that is based upon another (like a resized or cropped version for example). * #pw-order-groups common,resize-and-crop,variations,other + * #pw-use-constructor * #pw-body = * Pageimage objects are usually contained by a `Pageimages` object, which is a type of `Pagefiles` and `WireArray`. * In addition to the methods and properties below, you'll also want to look at `Pagefile` which this class inherits @@ -93,9 +94,14 @@ class Pageimage extends Pagefile { protected $error = ''; /** - * Construct a new Pagefile + * Construct a new Pageimage + * + * ~~~~~ + * // Construct a new Pageimage, assumes that $page->images is a FieldtypeImage Field + * $pageimage = new Pageimage($page->images, '/path/to/file.png'); + * ~~~~~ * - * @param Pagefiles $pagefiles + * @param Pageimages|Pagefiles $pagefiles * @param string $filename Full path and filename to this pagefile * @throws WireException * diff --git a/wire/core/PagesType.php b/wire/core/PagesType.php index 2e882462..c793d421 100644 --- a/wire/core/PagesType.php +++ b/wire/core/PagesType.php @@ -3,8 +3,15 @@ /** * ProcessWire PagesType * - * Provides an interface to the Pages class but specific to - * a given page class/type, with predefined parent and template. + * #pw-summary Provides an interface to the Pages class but specific to a given page class/type, with predefined parent and template. + * #pw-body = + * This class is primarily used by the core as an alternative to `$pages`, providing an API for other Page types like + * `User`, `Role`, `Permission`, and `Language`. The `$users`, `$roles`, `$permissions` and `$languages` API variables + * are all instances of `PagesType`. This class is typically not instantiated on its own and instead acts as a base class + * which is extended. + * + * #pw-body + * #pw-use-constructor * * ProcessWire 3.x, Copyright 2016 by Ryan Cramer * https://processwire.com @@ -83,6 +90,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Add one or more templates that this PagesType represents * + * #pw-group-family + * * @param array|int|string $templates Single or array of Template objects, IDs, or names * */ @@ -110,6 +119,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Add one or more of parents that this PagesType represents * + * #pw-group-family + * * @param array|int|string|Page $parents Single or array of Page objects, IDs, or paths * */ @@ -170,6 +181,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Is the given page a valid type for this class? + * + * #pw-internal * * @param Page $page * @return bool @@ -233,9 +246,10 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { * Given a Selector string, return the Page objects that match in a PageArray. * * @param string $selectorString - * @param array $options - - findOne: apply optimizations for finding a single page and include pages with 'hidden' status + * @param array $options Options to modify default behavior: + * - `findOne` (bool): apply optimizations for finding a single page and include pages with 'hidden' status * @return PageArray + * @see Pages::find() * */ public function find($selectorString, $options = array()) { @@ -301,13 +315,11 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { } /** - * Save a page object and it's fields to database. + * 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. + * - This is the same as calling $page->save() + * - If the page is new, it will be inserted. If existing, it will be updated. + * - If you want to just save a particular field in a Page, use `$page->save($fieldName)` instead. * * @param Page $page * @return bool True on success @@ -320,11 +332,11 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { } /** - * Permanently delete a page and it's fields. + * Permanently delete a page and its fields. * - * Unlike trash(), pages deleted here are not restorable. + * Unlike `$pages->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 + * 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 be thrown. * * @param Page $page @@ -339,12 +351,12 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { } /** - * Adds a new page with the given $name and returns the Page + * Adds a new page with the given $name and returns it * - * If they page has any other fields, they will not be populated, only the name will. - * Returns a NullPage if error, such as a page of this type already existing with the same name. + * - If the page has any other fields, they will not be populated, only the name will. + * - Returns a `NullPage` on error, such as when a page of this type already exists with the same name/parent. * - * @param string $name + * @param string $name Name to use for the new page * @return Page|NullPage * */ @@ -375,32 +387,82 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { * Make it possible to iterate all pages of this type per the \IteratorAggregate interface. * * Only recommended for page types that don't contain a lot of pages. + * + * #pw-internal * */ public function getIterator() { return $this->find("id>0, sort=name"); - } + } + /** + * Get the template used by this type (or first template if there are multiple) + * + * #pw-group-family + * + * @return Template + * + */ public function getTemplate() { return $this->template; } - + + /** + * Get the templates (plural) used by this type + * + * #pw-group-family + * + * @return array|Template[] Array of Template objects indexed by template ID. + * + */ public function getTemplates() { return count($this->templates) ? $this->templates : array($this->template); } + /** + * Get the parent page ID used by this type (or first parent ID if there are multiple) + * + * #pw-group-family + * + * @return int + * + */ public function getParentID() { return $this->parent_id; } - + + /** + * Get the parent page IDs used by this type + * + * #pw-group-family + * + * @return array Array of parent page IDs (integers) + * + */ public function getParentIDs() { return count($this->parents) ? $this->parents : array($this->parent_id); } + /** + * Get the parent Page object (or first parent Page object if there are multiple) + * + * #pw-group-family + * + * @return Page|NullPage + * + */ public function getParent() { return $this->wire('pages')->get($this->parent_id); } - + + /** + * Get the parent Page objects in a PageArray + * + * #pw-group-family + * + * @return PageArray + * + */ public function getParents() { if(count($this->parents)) { return $this->wire('pages')->getById($this->parents); @@ -411,17 +473,42 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { return $parents; } } - + + /** + * Set the PHP class name to use for Page objects of this type + * + * #pw-group-family + * + * @param string $class + * + */ public function setPageClass($class) { $this->pageClass = $class; } - + + /** + * Get the PHP class name used by Page objects of this type + * + * #pw-group-family + * + * @return string + * + */ public function getPageClass() { if($this->pageClass) return $this->pageClass; if($this->template && $this->template->pageClass) return $this->template->pageClass; return 'Page'; } - + + /** + * Return the number of pages in this type matching the given selector string + * + * @param string $selectorString Optional, if omitted then returns count of all pages of this type + * @param array $options Options to modify default behavior (see $pages->count method for details) + * @return int + * @see Pages::count() + * + */ public function count($selectorString = '', array $options = array()) { if(empty($selectorString) && empty($options) && count($this->parents) == 1) { return $this->getParent()->numChildren(); @@ -449,6 +536,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Hook called just before a page is saved + * + * #pw-hooker * * @param Page $page The page about to be saved * @return array Optional extra data to add to pages save query. @@ -464,6 +553,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { * * This is the same as Pages::save, except that it occurs before other save-related hooks (below), * Whereas Pages::save occurs 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 @@ -474,6 +565,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Hook called when a new page has been added + * + * #pw-hooker * * @param Page $page * @@ -482,6 +575,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Hook called when a page is about to be deleted, but before data has been touched + * + * #pw-hooker * * @param Page $page * @@ -490,6 +585,8 @@ class PagesType extends Wire implements \IteratorAggregate, \Countable { /** * Hook called when a page and it's data have been deleted + * + * #pw-hooker * * @param Page $page * diff --git a/wire/core/ProcessWire.php b/wire/core/ProcessWire.php index affb8b4d..94bb610c 100644 --- a/wire/core/ProcessWire.php +++ b/wire/core/ProcessWire.php @@ -1,45 +1,105 @@ config() method based on current debug mode + // this is reset in the $this->setConfig() method based on current debug mode ini_set('display_errors', true); error_reporting(E_ALL | E_STRICT); @@ -145,7 +205,7 @@ class ProcessWire extends Wire { $this->wire('hooks', new WireHooks($this, $config), true); $this->shutdown = $this->wire(new WireShutdown()); - $this->config($config); + $this->setConfig($config); $this->load($config); if($this->getNumInstances() > 1) { @@ -170,7 +230,7 @@ class ProcessWire extends Wire { * @param Config $config * */ - protected function config(Config $config) { + protected function setConfig(Config $config) { $this->wire('config', $config, true); $this->wire($config->paths); @@ -277,7 +337,9 @@ class ProcessWire extends Wire { } /** - * Load's ProcessWire using the supplied Config and populates all API fuel + * Load’s ProcessWire using the supplied Config and populates all API fuel + * + * #pw-internal * * @param Config $config * @throws WireDatabaseException|WireException on fatal error @@ -417,6 +479,8 @@ class ProcessWire extends Wire { /** * Hookable init for anyone that wants to hook immediately before any autoload modules initialized or after all modules initialized * + * #pw-hooker + * */ protected function ___init() { if($this->debug) Debug::timer('boot.modules.autoload.init'); @@ -426,6 +490,8 @@ class ProcessWire extends Wire { /** * Hookable ready for anyone that wants to hook immediately before any autoload modules ready or after all modules ready + * + * #pw-hooker * */ protected function ___ready() { @@ -438,6 +504,8 @@ class ProcessWire extends Wire { /** * Hookable ready for anyone that wants to hook when the request is finished + * + * #pw-hooker * */ protected function ___finished() { @@ -513,7 +581,16 @@ class ProcessWire extends Wire { if(is_object($value)) return call_user_func_array(array($value, '__invoke'), $arguments); return parent::__call($method, $arguments); } - + + /** + * Get an API variable + * + * #pw-internal + * + * @param string $name Optional API variable name + * @return mixed|null|Fuel + * + */ public function fuel($name = '') { if(empty($name)) return $this->fuel; return $this->fuel->$name; @@ -540,6 +617,8 @@ class ProcessWire extends Wire { /** * Instance ID of this ProcessWire instance * + * #pw-group-instances + * * @return int * */ @@ -550,6 +629,8 @@ class ProcessWire extends Wire { /** * Add a ProcessWire instance and return the instance ID * + * #pw-group-instances + * * @param ProcessWire $wire * @return int * @@ -564,6 +645,8 @@ class ProcessWire extends Wire { /** * Get all ProcessWire instances * + * #pw-group-instances + * * @return array * */ @@ -574,6 +657,8 @@ class ProcessWire extends Wire { /** * Return number of instances * + * #pw-group-instances + * * @return int * */ @@ -584,6 +669,8 @@ class ProcessWire extends Wire { /** * Get a ProcessWire instance by ID * + * #pw-group-instances + * * @param int|null $instanceID Omit this argument to return the current instance * @return null|ProcessWire * @@ -596,6 +683,8 @@ class ProcessWire extends Wire { /** * Get the current ProcessWire instance * + * #pw-group-instances + * * @return ProcessWire|null * */ @@ -610,6 +699,8 @@ class ProcessWire extends Wire { /** * Set the current ProcessWire instance * + * #pw-group-instances + * * @param ProcessWire $wire * */ @@ -620,6 +711,8 @@ class ProcessWire extends Wire { /** * Remove a ProcessWire instance * + * #pw-group-instances + * * @param ProcessWire $wire * */ @@ -634,7 +727,7 @@ class ProcessWire extends Wire { } /** - * Build a Config object for booting ProcessWire + * Static method to build a Config object for booting ProcessWire * * @param string $rootPath Path to root of installation where ProcessWire's index.php file is located. * @param string $rootURL Should be specified only for secondary ProcessWire instances. diff --git a/wire/core/Selector.php b/wire/core/Selector.php index 5592b7e2..051b6c5e 100644 --- a/wire/core/Selector.php +++ b/wire/core/Selector.php @@ -11,33 +11,62 @@ * ProcessWire 3.x, Copyright 2016 by Ryan Cramer * https://processwire.com * - */ - -/** - * Selector maintains a single selector consisting of field name, operator, and value. - * - * Field and value may optionally be arrays, where are assumed to be OR values. + * #pw-summary Selector maintains a single selector consisting of field name, operator, and value. * - * Serves as the base class for the different Selector types (seen below this class). + * #pw-body = + * - Serves as the base class for the different Selector types (`SelectorEqual`, `SelectorNotEqual`, `SelectorLessThan`, etc.) + * - The constructor requires `$field` and `$value` properties which may either be an array or string. + * An array indicates multiple items in an OR condition. Multiple items may also be specified by + * pipe “|” separated strings. + * - Operator is determined by the Selector class name, and thus may not be changed without replacing + * the entire Selector. * - * @property string|array $field Field or fields present in the selector (can be string or array) [1] - * @property array $fields Fields that were present in selector (same as $field, but always array) - * @property string $operator Operator used by the selector [2] - * @property string|array $value Value or values present in the selector (can be string or array) [1] - * @property array $values Values that were present in selector (same as $value, but always array) - * @property bool $not Is this a NOT selector? (i.e. returns the opposite if what it would otherwise) - * @property string|null $group Group name for this selector (if field was prepended with a "group_name@") - * @property string $quote Type of quotes value was in, or blank if it was not quoted. One of: '"[{( - * @property string $str String value of selector - * @property null|bool $forceMatch When boolean, it forces match (true) or non-match (false). + * ~~~~~ + * // very basic usage example + * // constructor takes ($field, $value) which can be strings or arrays + * $s = new SelectorEqual('title', 'About Us'); + * // $page can be any kind of Wire-derived object + * if($s->matches($page)) { + * // $page has title "About Us" + * } + * ~~~~~ + * ~~~~~ + * // another usage example + * $s = new SelectorContains('title|body|summary', 'foo|bar'); + * if($s->matches($page)) { + * // the title, body or summary properties of $page contain either the text "foo" or "bar" + * } + * ~~~~~ * - * [1] The $field and $value properties may either be an array or string. As a result, we recommend - * accessing the $fields or $values properties (instead of $field or $value), because they are always - * return an array. + * ### List of core selector-derived classes * - * [2] Operator is determined by the Selector class name, and thus may not be changed without replacing - * the entire Selector. + * - `SelectorEqual` + * - `SelectorNotEqual` + * - `SelectorGreaterThan` + * - `SelectorLessThan` + * - `SelectorGreaterThanEqual` + * - `SelectorLessThanEqual` + * - `SelectorContains` + * - `SelectorContainsLike` + * - `SelectorContainsWords` + * - `SelectorStarts` + * - `SelectorStartsLike` + * - `SelectorEnds` + * - `SelectorEndsLike` + * - `SelectorBitwiseAnd` * + * #pw-body + * + * @property array $fields Fields that were present in selector (same as $field, but always an array). + * @property string|array $field Field or fields present in the selector (string if single, or array of strings if multiple). Preferable to use $fields property instead. + * @property-read string $operator Operator used by the selector. + * @property array $values Values that were present in selector (same as $value, but always array). + * @property string|array $value Value or values present in the selector (string if single, or array of strings if multiple). Preferable to use $values property instead. + * @property bool $not Is this a NOT selector? Indicates the selector returns the opposite if what it would otherwise. #pw-group-properties + * @property string|null $group Group name for this selector (if field was prepended with a "group_name@"). #pw-group-properties + * @property string $quote Type of quotes value was in, or blank if it was not quoted. One of: '"[{( #pw-group-properties + * @property-read string $str String value of selector, i.e. “a=b”. #pw-group-properties + * @property null|bool $forceMatch When boolean, it forces match (true) or non-match (false). (default=null) #pw-group-properties * */ abstract class Selector extends WireData { @@ -71,27 +100,112 @@ abstract class Selector extends WireData { $this->set('forceMatch', null); // boolean true to force match, false to force non-match } + /** + * Return the operator used by this Selector + * + * @return string + * @since 3.0.42 Prior versions just supported the 'operator' property. + * + */ + public function operator() { + return self::getOperator(); + } + + /** + * Get the field(s) of this Selector + * + * Note that if calling this as a property (rather than a method) it can return either a string or an array. + * + * @param bool|int $forceString Specify one of the following: + * - `true` (bool): to only return a string, where multiple-fields will be split by pipe "|". (default) + * - `false` (bool): to return string if 1 field, or array of multiple fields (same behavior as field property). + * - `1` (int): to return only the first value (string). + * @return string|array|null + * @since 3.0.42 Prior versions only supported the 'field' property. + * @see Selector::fields() + * + */ + public function field($forceString = true) { + $field = parent::get('field'); + if($forceString && is_array($field)) { + if($forceString === 1) { + $field = reset($field); + } else { + $field = implode('|', $field); + } + } + return $field; + } + + /** + * Return array of field(s) for this Selector + * + * @return array + * @see Selector::field() + * @since 3.0.42 Prior versions just supported the 'fields' property. + * + */ + public function fields() { + $field = parent::get('field'); + if(is_array($field)) return $field; + if(!strlen($field)) return array(); + return array($field); + } + + /** + * Get the value(s) of this Selector + * + * Note that if calling this as a property (rather than a method) it can return either a string or an array. + * + * @param bool|int $forceString Specify one of the following: + * - `true` (bool): to only return a string, where multiple-values will be split by pipe "|". (default) + * - `false` (bool): to return string if 1 value, or array of multiple values (same behavior as value property). + * - `1` (int): to return only the first value (string). + * @return string|array|null + * @since 3.0.42 Prior versions only supported the 'value' property. + * @see Selector::values() + * + */ + public function value($forceString = true) { + $value = parent::get('value'); + if($forceString && is_array($value)) { + if($forceString === 1) { + $value = reset($value); + } else { + $value = implode('|', $value); + } + } + return $value; + } + + /** + * Return array of value(s) for this Selector + * + * @return array + * @see Selector::value() + * @since 3.0.42 Prior versions just supported the 'values' property. + * + */ + public function values() { + $values = parent::get('value'); + if(is_array($values)) return $values; + if(!is_object($values) && !strlen($values)) return array(); + return array($values); + } + public function get($key) { if($key == 'operator') return $this->getOperator(); if($key == 'str') return $this->__toString(); - if($key == 'values') { - $value = $this->value; - if(is_array($value)) return $value; - if(!is_object($value) && !strlen($value)) return array(); - return array($value); - } - if($key == 'fields') { - $field = $this->field; - if(is_array($field)) return $field; - if(!strlen($field)) return array(); - return array($field); - } + if($key == 'values') return $this->values(); + if($key == 'fields') return $this->fields(); return parent::get($key); } /** * Returns the selector field(s), optionally forcing as string or array * + * #pw-internal + * * @param string $type Omit for automatic, or specify 'string' or 'array' to force return in that type * @return string|array * @throws WireException if given invalid type @@ -115,6 +229,8 @@ abstract class Selector extends WireData { * When the $type argument is not specified, this method may return a string, array or Selectors object. * A Selectors object is only returned if the value happens to contain an embedded selector. * + * #pw-internal + * * @param string $type Omit for automatic, or specify 'string' or 'array' to force return in that type * @return string|array|Selectors * @throws WireException if given invalid type @@ -160,6 +276,8 @@ abstract class Selector extends WireData { * Return the operator used by this Selector * * Strict standards don't let us make static abstract methods, so this one throws an exception if it's not reimplemented. + * + * #pw-internal * * @return string * @throws WireException @@ -291,6 +409,8 @@ abstract class Selector extends WireData { /** * Add all individual selector types to the runtime Selectors + * + * #pw-internal * */ static public function loadSelectorTypes() { diff --git a/wire/core/Selectors.php b/wire/core/Selectors.php index eb80ba36..6101fb0f 100644 --- a/wire/core/Selectors.php +++ b/wire/core/Selectors.php @@ -1,17 +1,40 @@ 100, currency=USD|EUR"); + * if($selectors->matches($page)) { + * // selector string matches the given $page (which can be any Wire-derived item) + * } + * ~~~~~ + * ~~~~~ + * // iterate and display what's in this Selectors object + * foreach($selectors as $selector) { + * echo "

"; + * echo "Field(s): " . implode('|', $selector->fields) . "
"; + * echo "Operator: " . $selector->operator . "
"; + * echo "Value(s): " . implode('|', $selector->values) . "
"; + * echo "

"; + * } + * ~~~~~ + * #pw-body + * + * @link https://processwire.com/api/selectors/ Official Selectors Documentation * * ProcessWire 3.x, Copyright 2016 by Ryan Cramer * https://processwire.com * */ -require_once(PROCESSWIRE_CORE_PATH . "Selector.php"); - class Selectors extends WireArray { /** @@ -104,6 +127,11 @@ class Selectors extends WireArray { /** * Set the selector string or array (if not set already from the constructor) * + * ~~~~~ + * $selectors = new Selectors(); + * $selectors->init("sale_price|retail_price>100, currency=USD|EUR"); + * ~~~~~ + * * @param string|array $selector * */ @@ -120,6 +148,8 @@ class Selectors extends WireArray { /** * Set the selector string * + * #pw-internal + * * @param string $selectorStr * */ @@ -131,6 +161,8 @@ class Selectors extends WireArray { /** * Import items into this WireArray. * + * #pw-internal + * * @throws WireException * @param string|WireArray $items Items to import. * @return WireArray This instance. @@ -148,6 +180,8 @@ class Selectors extends WireArray { /** * Per WireArray interface, return true if the item is a Selector instance * + * #pw-internal + * * @param Selector $item * @return bool * @@ -158,6 +192,8 @@ class Selectors extends WireArray { /** * Per WireArray interface, return a blank Selector + * + * #pw-internal * */ public function makeBlankItem() { @@ -169,6 +205,8 @@ class Selectors extends WireArray { * * Static since there may be multiple instances of this Selectors class at runtime. * See Selector.php + * + * #pw-internal * * @param string $operator * @param string $class @@ -184,6 +222,10 @@ class Selectors extends WireArray { /** * Return array of all valid operator characters + * + * #pw-group-static-helpers + * + * @return array * */ static public function getOperatorChars() { @@ -192,6 +234,8 @@ class Selectors extends WireArray { /** * Does the given string have an operator in it? + * + * #pw-group-static-helpers * * @param string $str * @return bool @@ -250,6 +294,8 @@ class Selectors extends WireArray { * Does the given string start with a selector? * * Meaning string starts with [field][operator] like "field=" + * + * #pw-group-static-helpers * * @param string $str * @return bool @@ -283,14 +329,18 @@ class Selectors extends WireArray { return $has; } - /** * Create a new Selector object from a field name, operator, and value + * + * This is mostly for internal use, as the Selectors object already does this when you pass it + * a selector string in the constructor or init() method. + * + * #pw-group-advanced * - * @param string $field - * @param string $operator - * @param string $value - * @return Selector + * @param string $field Field name or names (separated by a pipe) + * @param string $operator Operator, i.e. "=" + * @param string $value Value or values (separated by a pipe) + * @return Selector Returns the correct type of `Selector` object that corresponds to the given `$operator`. * @throws WireException * */ @@ -613,6 +663,8 @@ class Selectors extends WireArray { /** * Given a value string with an "api_var" or "api_var.property" return the string value of the property + * + * #pw-internal * * @param string $value var or var.property * @return null|string Returns null if it doesn't resolve to anything or a string of the value it resolves to @@ -634,6 +686,8 @@ class Selectors extends WireArray { * Set whether or not vars should be parsed * * By default this is true, so only need to call this method to disable variable parsing. + * + * #pw-internal * * @param bool $parseVars * @@ -645,6 +699,8 @@ class Selectors extends WireArray { /** * Does the given Selector value contain a parseable value? * + * #pw-internal + * * @param Selector $selector * @return bool * @@ -665,6 +721,8 @@ class Selectors extends WireArray { * Does the given value contain an API var reference? * * It is assumed the value was quoted in "[value]", and the quotes are not there now. + * + * #pw-internal * * @param string $value The value to evaluate * @return bool @@ -757,6 +815,8 @@ class Selectors extends WireArray { /** * Create this Selectors object from an array + * + * #pw-internal * * @param array $a * @throws WireException @@ -883,6 +943,7 @@ class Selectors extends WireArray { if(isset($data['value'])) throw new WireException("You may not specify both 'value' and 'find' at the same time"); // if(!is_array($data['find'])) throw new WireException("Selector 'find' property must be specified as array"); $find = $data['find']; + $data['value'] = array(); } if(isset($data['whitelist']) && $data['whitelist'] !== null) { @@ -967,6 +1028,14 @@ class Selectors extends WireArray { $fields[] = $_name; } + // convert WireArray types to an array of $_values + if(count($_values) === 1) { + $value = reset($_values); + if(is_object($value) && $value instanceof WireArray) { + $_values = explode('|', (string) $value); + } + } + // determine value(s) foreach($_values as $value) { $_sanitize = $sanitize; @@ -1017,6 +1086,8 @@ class Selectors extends WireArray { * - If you need a literal comma, use a double comma ",,". * - If you need a literal equals, use a double equals "==". * + * #pw-group-static-helpers + * * @param string $s * @return array * @@ -1052,6 +1123,8 @@ class Selectors extends WireArray { /** * Given an assoc array, convert to a key=value selector-style string * + * #pw-group-static-helpers + * * @param $a * @return string * diff --git a/wire/core/Session.php b/wire/core/Session.php index fc55f59c..7f240d73 100644 --- a/wire/core/Session.php +++ b/wire/core/Session.php @@ -28,7 +28,7 @@ * @method void loginFailure($name, $reason) #pw-hooker * @method void logoutSuccess(User $user) #pw-hooker * - * @property SessionCSRF $CSRF + * @property SessionCSRF $CSRF * * Expected $config variables include: * =================================== @@ -395,9 +395,7 @@ class Session extends Wire implements \IteratorAggregate { */ public function get($key, $_key = null) { if($key == 'CSRF') { - if(!$this->sessionInit) $this->init(); // init required for CSRF - if(is_null($this->CSRF)) $this->CSRF = $this->wire(new SessionCSRF()); - return $this->CSRF; + return $this->CSRF(); } else if(!is_null($_key)) { // namespace return $this->getFor($key, $_key); @@ -1133,4 +1131,32 @@ class Session extends Wire implements \IteratorAggregate { } } + /** + * Return an instance of ProcessWire’s CSRF object, which provides an API for cross site request forgery protection. + * + * ~~~~ + * // output somewhere in
markup when rendering a form + * echo $session->CSRF->renderInput(); + * ~~~~ + * ~~~~ + * // when processing form (POST request), check to see if token is present + * if($session->CSRF->hasValidToken()) { + * // form submission is valid + * // okay to process + * } else { + * // form submission is NOT valid + * throw new WireException('CSRF check failed!'); + * } + * ~~~~ + * + * @return SessionCSRF + * @see SessionCSRF::renderInput(), SessionCSRF::validate(), SessionCSRF::hasValidToken() + * + */ + public function CSRF() { + if(!$this->sessionInit) $this->init(); // init required for CSRF + if(is_null($this->CSRF)) $this->CSRF = $this->wire(new SessionCSRF()); + return $this->CSRF; + } + } diff --git a/wire/core/SessionCSRF.php b/wire/core/SessionCSRF.php index 891d8f64..07c88b3f 100644 --- a/wire/core/SessionCSRF.php +++ b/wire/core/SessionCSRF.php @@ -2,26 +2,52 @@ /** * ProcessWire CSRF Protection - * + * * ProcessWire 3.x, Copyright 2016 by Ryan Cramer * https://processwire.com * */ /** - * Triggered when CSRF detected + * Exception triggered by SessionCSRF::validate() when CSRF detected * */ class WireCSRFException extends WireException {} /** - * ProcessWire CSRF Protection class + * ProcessWire CSRF Protection + * + * #pw-summary Provides an API for cross site request forgery protection. + * #pw-body = + * ~~~~ + * // output somewhere in form markup when rendering a form + * echo $session->CSRF->renderInput(); + * ~~~~ + * ~~~~ + * // when processing form (POST request), check to see if token is present + * if($session->CSRF->hasValidToken()) { + * // form submission is valid + * // okay to process + * } else { + * // form submission is NOT valid + * throw new WireException('CSRF check failed!'); + * } + * ~~~~ + * ~~~~ + * // this alternative to hasValidToken() throws WireCSRFException when invalid + * $session->CSRF->validate(); + * ~~~~ + * + * #pw-body + * * */ class SessionCSRF extends Wire { /** * Get a CSRF Token name, or create one if it doesn't yet exist + * + * #pw-group-initiating * * @param int|string|null $id Optional unique ID for this token * @return string @@ -38,6 +64,8 @@ class SessionCSRF extends Wire { /** * Get a CSRF Token value as stored in the session, or create one if it doesn't yet exist + * + * #pw-group-initiating * * @param int|string|null $id Optional unique ID for this token * @return string @@ -57,6 +85,8 @@ class SessionCSRF extends Wire { /** * Get a CSRF Token timestamp + * + * #pw-group-initiating * * @param int|string|null $id Optional unique ID for this token * @return string @@ -70,6 +100,8 @@ class SessionCSRF extends Wire { /** * Get a CSRF Token name and value + * + * #pw-group-initiating * * @param int|string|null $id Optional unique ID for this token * @return array ("name" => "token name", "value" => "token value", "time" => created timestamp) @@ -87,7 +119,9 @@ class SessionCSRF extends Wire { * Get a CSRF Token name and value that can only be used once * * Note that a single call to hasValidToken($id) or validate($id) will invalidate the single use token. - * So call them once and store your result if you need the result multiple times. + * So call them once and store your result if you need the result multiple times. + * + * #pw-group-initiating * * @param int|string $id Optional unique ID/name for this token (of omitted one is generated automatically) * @return array ("id' => "token ID", "name" => "token name", "value" => "token value", "time" => created timestamp) @@ -111,6 +145,8 @@ class SessionCSRF extends Wire { /** * Returns true if the current POST request contains a valid CSRF token, false if not + * + * #pw-group-validating * * @param int|string|null $id Optional unique ID for this token, but required if checking a single use token. * @return bool @@ -139,6 +175,8 @@ class SessionCSRF extends Wire { /** * Throws an exception if the token is invalid + * + * #pw-group-validating * * @param int|string|null $id Optional unique ID for this token * @throws WireCSRFException if token not valid @@ -154,6 +192,8 @@ class SessionCSRF extends Wire { /** * Clear out token value + * + * #pw-group-resetting * * @param int|string|null $id Optional unique ID for this token * @@ -166,6 +206,8 @@ class SessionCSRF extends Wire { /** * Clear out all saved token values + * + * #pw-group-resetting * */ public function resetAll() { @@ -174,6 +216,15 @@ class SessionCSRF extends Wire { /** * Render a form input[hidden] containing the token name and value, as looked for by hasValidToken() + * + * ~~~~~ + * + * + * CSRF->renderInput(); ?> + *
+ * ~~~~~ + * + * #pw-group-initiating * * @param int|string|null $id Optional unique ID for this token * @return string diff --git a/wire/core/WireDatabaseBackup.php b/wire/core/WireDatabaseBackup.php index 81cff746..51b8363a 100644 --- a/wire/core/WireDatabaseBackup.php +++ b/wire/core/WireDatabaseBackup.php @@ -1,35 +1,63 @@ backups()`, which returns a `WireDatabaseBackup` instance. + * + * ### Easy Initialization (recommended) + * ~~~~~ + * $backup = $database->backups(); + * ~~~~~ + * + * ### Manual Initialization (if you need it) + * ~~~~~ + * // determine where backups will go (should NOT be web accessible) + * $backupPath = $config->paths->assets . 'backups/'; + * + * // create a new WireDatabaseBackup instance + * $backup = new WireDatabaseBackup($backupPath); + * + * // Option 1: set the already-connected DB connection + * $backup->setDatabase($this->database); + * + * // Option 2: OR provide a Config object that contains the DB connection info + * $backup->setDatabaseConfig($this->config); + * + * ~~~~~ + * ### Backup the database + * ~~~~~ + * $options = array(); // optional + * $file = $backup->backup($options); + * if($file) { + * print_r($backup->notes()); + * } else { + * print_r($backup->errors()); + * } + * ~~~~~ + * Note: the `print_r()` function calls are just for demonstration and testing purposes. We are not suggesting + * you actually do that except when testing. + * + * ### Restore a database + * ~~~~~ + * $options = array(); // optional + * $success = $backup->restore($file, $options); + * if($success) { + * print_r($backup->notes()); + * } else { + * print_r($backup->errors()); + * } + * ~~~~~ + * #pw-body * * ProcessWire 3.x, Copyright 2016 by Ryan Cramer * https://processwire.com * - * USAGE - * - * Initialization - * ============== - * $backup = new WireDatabaseBackup('/path/to/backups/'); - * $backup->setDatabase($this->database); // optional, if omitted it will attempt it's own connection - * $backup->setDatabaseConfig($this->config); // optional, only if setDatabase() was called - * - * Backup - * ====== - * $file = $backup->backup([$options]); - * if($file) print_r($backup->notes()); - * else print_r($backup->errors()); - * - * - * Restore - * ======= - * $success = $backup->restore($file, [$options]); - * if($success) print_r($backup->notes()); - * else print_r($backup->errors()); * */ @@ -548,9 +576,26 @@ class WireDatabaseBackup { /** * Perform a database export/dump * - * @param array $options See $backupOptions - * @return string Full path and filename of database export file, or false on failure. + * @param array $options Options to modify default behavior: + * - `filename` (string): filename for backup: default is to make a dated filename, but this can also be used (basename only, no path) + * - `description` (string): optional description of this backup + * - `tables` (array): if specified, export will only include these tables + * - `user` (string): username to associate with the backup file (string), optional + * - `excludeTables` (array): exclude creating or inserting into these tables + * - `excludeCreateTables` (array): exclude creating these tables, but still export data + * - `excludeExportTables` (array): exclude exporting data, but still create tables + * - `whereSQL` (array): SQL conditions for export of individual tables [table => [SQL conditions]]. The `table` portion (index) may also be a full PCRE regexp, must start with `/` to be recognized as regex. + * - `maxSeconds` (int): max number of seconds allowed for execution (default=1200) + * - `allowDrop` (bool): use DROP TABLES statements before CREATE TABLE statements? (default=true) + * - `allowUpdate` (bool): use UPDATE ON DUPLICATE KEY so that INSERT statements can UPDATE when rows already present (all tables). (default=false) + * - `allowUpdateTables` (array): table names that will use UPDATE ON DUPLICATE KEY (does NOT require allowUpdate=true) + * - `findReplace` (array): find and replace in row data during backup. Example: ['databass' => 'database'] + * - `findReplaceCreateTable` (array): find and replace in create table statements + * Example: ['DEFAULT CHARSET=latin1;' => 'DEFAULT CHARSET=utf8;'] + * - `extraSQL` (array): additional SQL queries to append at the bottom. Example: ['UPDATE pages SET created=NOW()'] + * @return string Full path and filename of database export file, or false on failure. * @throws \Exception on fatal error + * @see WireDatabaseBackup::restore() * */ public function backup(array $options = array()) { @@ -827,9 +872,17 @@ class WireDatabaseBackup { * Import a database SQL file that was created by this class * * @param string $filename Filename to restore, optionally including path (if no path, then path set to construct is assumed) - * @param array $options See WireDatabaseBackup::$restoreOptions + * @param array $options Options to modify default behavior: + * - `tables` (array): table names to restore (empty=all) + * - `allowDrop` (bool): allow DROP TABLE statements (default=true) + * - `haltOnError` (bool): halt execution when an error occurs? (default=false) + * - `maxSeconds` (int): max number of seconds allowed for execution (default=1200) + * - `findReplace` (array): find and replace in row data. Example: ['databass' => 'database'] + * - `findReplaceCreateTable` (array): find and replace in create table statements. + * Example: ['DEFAULT CHARSET=utf8;' => 'DEFAULT CHARSET=utf8mb4;'] * @return true on success, false on failure. Call the errors() method to retrieve errors. * @throws \Exception on fatal error + * @see WireDatabaseBackup::backup() * */ public function restore($filename, array $options = array()) { diff --git a/wire/core/WireDatabasePDO.php b/wire/core/WireDatabasePDO.php index 9c6f7916..ce79b9ec 100644 --- a/wire/core/WireDatabasePDO.php +++ b/wire/core/WireDatabasePDO.php @@ -733,13 +733,13 @@ class WireDatabasePDO extends Wire implements WireDatabase { /** * Retrieve new instance of WireDatabaseBackups ready to use with this connection * - * See WireDatabaseBackup class for usage. + * See `WireDatabaseBackup` class for usage. * * #pw-group-custom * * @return WireDatabaseBackup * @throws WireException|\Exception on fatal error - * @see WireDatabaseBackup + * @see WireDatabaseBackup::backup(), WireDatabaseBackup::restore() * */ public function backups() { diff --git a/wire/core/WireHttp.php b/wire/core/WireHttp.php index 1a43f4b4..9976aa83 100644 --- a/wire/core/WireHttp.php +++ b/wire/core/WireHttp.php @@ -5,7 +5,18 @@ * * Provides capability for sending POST/GET requests to URLs * - * #pw-summary WireHttp enables you to send HTTP requests to URLs, download files, and more. + * #pw-summary WireHttp enables you to send HTTP requests to URLs, download files, and more. + * #pw-body = + * ~~~~~ + * $http = new WireHttp(); + * $response = $http->get("http://domain.com/path/"); + * if($response !== false) { + * echo "Successful response: " . $sanitizer->entities($response); + * } else { + * echo "HTTP request failed: " . $http->getError(); + * } + * ~~~~~ + * #pw-body * * Thanks to @horst for his assistance with several methods in this class. * @@ -219,7 +230,7 @@ class WireHttp extends Wire { * $response = $http->post("http://domain.com/path/", [ * 'foo' => bar', * ]); - * if($response) { + * if($response !== false) { * echo "Successful response: " . $sanitizer->entities($response); * } else { * echo "HTTP request failed: " . $http->getError(); @@ -244,7 +255,7 @@ class WireHttp extends Wire { * $response = $http->get("http://domain.com/path/", [ * 'foo' => 'bar', * ]); - * if($response) { + * if($response !== false) { * echo "Successful response: " . $sanitizer->entities($response); * } else { * echo "HTTP request failed: " . $http->getError();