wire()` method that provides access to ProcessWire’s API variables. * API variables can also be accessed as local properties in most cases. Wire also provides basic methods * for tracking changes and managing runtime notices specific to the instance. * * Wire derived classes can specify which methods are “hookable” by precending the method name with * 3 underscores like this: `___myMethod()`. Other classes can then hook either before or after that method, * modifying arguments or return values. Several other hook methods are also provided for Wire derived * classes that are hooking into others. * #pw-body * #pw-order-groups common,identification,hooks,notices,changes,hooker,api-helpers * #pw-summary-api-helpers Shortcuts to ProcessWire API variables. Access without any arguments returns the API variable. Some support arguments as shortcuts to methods in the API variable. * #pw-summary-changes Methods to support tracking and retrieval of changes made to the object. * #pw-summary-hooks Methods for managing hooks for an object instance or class. * * ProcessWire 3.x, Copyright 2022 by Ryan Cramer * https://processwire.com * * #pw-use-constants * * @property string $className #pw-internal * * API variables accessible as properties (unless $useFuel has been set to false): * * @property AdminTheme|AdminThemeFramework|null $adminTheme #pw-internal * @property WireCache $cache #pw-internal * @property Config $config #pw-internal * @property WireDatabasePDO $database #pw-internal * @property Database $db #pw-internal deprecated * @property WireDateTime $datetime #pw-internal * @property Fieldgroups|Fieldgroup[] $fieldgroups #pw-internal * @property Fields|Field[] $fields #pw-internal * @property Fieldtypes|Fieldtype[] $fieldtypes #pw-internal * @property WireFileTools $files #pw-internal * @property Fuel $fuel #pw-internal * @property WireHooks $hooks #pw-internal * @property WireInput $input #pw-internal * @property Languages|Language[] $languages (present only if LanguageSupport installed) #pw-internal * @property WireLog $log #pw-internal * @property WireMailTools $mail #pw-internal * @property Modules $modules #pw-internal * @property Notices|Notice[] $notices #pw-internal * @property Page $page #pw-internal * @property Pages $pages #pw-internal * @property Permissions|Permission[] $permissions #pw-internal * @property Process|ProcessPageView $process #pw-internal * @property WireProfilerInterface $profiler #pw-internal * @property Roles|Role[] $roles #pw-internal * @property Sanitizer $sanitizer #pw-internal * @property Session $session #pw-internal * @property Templates|Template[] $templates #pw-internal * @property Paths $urls #pw-internal * @property User $user #pw-internal * @property Users $users #pw-internal * @property ProcessWire $wire #pw-internal * * The following map API variables to function names and apply only if another function in the class does not * already have the same name, which would override. All defined API variables can be accessed as functions * that return the API variable, whether documented below or not. * * @method WireCache|string|array|PageArray|null cache($name = '', $expire = null, $func = null) Access the $cache API variable as a function. #pw-group-api-helpers * @method Config|mixed config($key = '', $value = null) Access the $config API variable as a function. #pw-group-api-helpers * @method WireDatabasePDO database() Access the $database API variable as a function. #pw-group-api-helpers * @method WireDateTime|string|int datetime($format = '', $value = '') Access the $datetime API variable as a function. #pw-group-api-helpers * @method Field|Fields|null fields($name = '') Access the $fields API variable as a function. #pw-group-api-helpers * @method WireFileTools files() Access the $files API variable as a function. #pw-group-api-helpers * @method WireInput|WireInputData|WireInputDataCookie|array|string|int|null input($type = '', $key = '', $sanitizer = '') Access the $input API variable as a function. #pw-group-api-helpers * @method WireInputDataCookie|string|int|array|null inputCookie($key = '', $sanitizer = '') Access the $input->cookie() API variable as a function. #pw-group-api-helpers * @method WireInputData|string|int|array|null inputGet($key = '', $sanitizer = '') Access the $input->get() API variable as a function. #pw-group-api-helpers * @method WireInputData|string|int|array|null inputPost($key = '', $sanitizer = '') Access the $input->post() API variable as a function. #pw-group-api-helpers * @method Languages|Language|NullPage|null languages($name = '') Access the $languages API variable as a function. #pw-group-api-helpers * @method Modules|Module|ConfigurableModule|null modules($name = '') Access the $modules API variable as a function. #pw-group-api-helpers * @method Page|Mixed page($key = '', $value = null) Access the $page API variable as a function. #pw-group-api-helpers * @method Pages|PageArray|Page|NullPage pages($selector = '') Access the $pages API variable as a function. #pw-group-api-helpers * @method Permissions|Permission|PageArray|null|NullPage permissions($selector = '') Access the $permissions API variable as a function. #pw-group-api-helpers * @method Roles|Role|PageArray|null|NullPage roles($selector = '') Access the $roles API variable as a function. #pw-group-api-helpers * @method Sanitizer|string|int|array|null|mixed sanitizer($name = '', $value = '') Access the $sanitizer API variable as a function. #pw-group-api-helpers * @method Session|mixed session($key = '', $value = null) Access the $session API variable as a function. #pw-group-api-helpers * @method Templates|Template|null templates($name = '') Access the $templates API variable as a function. #pw-group-api-helpers * @method User|mixed user($key = '', $value = null) Access the $user API variable as a function. #pw-group-api-helpers * @method Users|PageArray|User|mixed users($selector = '') Access the $users API variable as a function. #pw-group-api-helpers * * Other standard hookable methods * * @method changed(string $what, $old = null, $new = null) See Wire::___changed() * @method log($str = '', array $options = array()) See Wire::___log() * @method callUnknown($method, $arguments) See Wire::___callUnknown() * @method Wire trackException(\Exception $e, $severe = true, $text = null) * * */ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable { /******************************************************************************************************* * API VARIABLE/FUEL INJECTION AND ACCESS * * PLEASE NOTE: All the following fuel related variables/methods will be going away in PW 3.0. * You should use the $this->wire() method instead for compatibility with PW 3.0. The only methods * and variables sticking around for PW 3.0 are: * * $this->wire(...); * $this->useFuel(bool); * $this->useFuel * */ /** * Whether this class may use fuel variables in local scope, like $this->item * * @var bool * */ protected $useFuel = true; /** * Total number of Wire class instances * * @var int * */ static private $_instanceTotal = 0; /** * ID of this Wire class instance * * @var int * */ private $_instanceNum = 0; /** * Construct * */ public function __construct() {} /** * Clone this Wire instance * */ public function __clone() { $this->_instanceNum = 0; $this->getInstanceNum(); } /** * Get this Wire object’s instance number * * - This is a unique number among all other Wire (or derived) instances in the system. * - If this instance ID has not yet been set, this will set it. * - Note that this is different from the ProcessWire instance ID. * * #pw-group-identification * * @param bool $getTotal Specify true to get the total quantity of Wire instances rather than this instance number. * @return int Instance number * */ public function getInstanceNum($getTotal = false) { if(!$this->_instanceNum) { self::$_instanceTotal++; $this->_instanceNum = self::$_instanceTotal; } if($getTotal) return self::$_instanceTotal; return $this->_instanceNum; } /** * Add fuel to all classes descending from Wire * * #pw-internal * * @param string $name * @param mixed $value * @param bool $lock Whether the API value should be locked (non-overwritable) * @internal Fuel is an internal-only keyword. * Unless static needed, use $this->wire($name, $value) instead. * @deprecated Use $this->wire($name, $value, $lock) instead. * */ public static function setFuel($name, $value, $lock = false) { $wire = ProcessWire::getCurrentInstance(); $log = $wire->wire()->log; if($log) $log->deprecatedCall(); $wire->fuel()->set($name, $value, $lock); } /** * Get the Fuel specified by $name or NULL if it doesn't exist * * #pw-internal * * @param string $name * @return mixed|null * @internal Fuel is an internal-only keyword. * Use $this->wire(name) or $this->wire()->name instead, unless static is required. * @deprecated * */ public static function getFuel($name = '') { $wire = ProcessWire::getCurrentInstance(); $log = $wire->wire()->log; if($log) $log->deprecatedCall(); if(empty($name)) return $wire->fuel(); return $wire->fuel()->$name; } /** * Returns an iterable Fuel object of all Fuel currently loaded * * #pw-internal * * @return Fuel * @deprecated This method will be going away. * Use $this->wire() instead, or if static required use: Wire::getFuel() with no arguments * */ public static function getAllFuel() { $wire = ProcessWire::getCurrentInstance(); $log = $wire->wire()->log; if($log) $log->deprecatedCall(); return $wire->fuel(); } /** * Get the Fuel specified by $name or NULL if it doesn't exist (DEPRECATED) * * #pw-internal * * DO NOT USE THIS METHOD: It is deprecated and only used by the ProcessWire class. * It is here in the Wire class for legacy support only. Use the wire() method instead. * * @param string $name * @return mixed|null * */ public function fuel($name = '') { $wire = $this->wire(); $log = $wire->wire()->log; if($log) $log->deprecatedCall(); return $wire->fuel($name); } /** * Should fuel vars be scoped locally to this class instance? (internal use only) * * If so, you can do things like $this->apivar. * If not, then you'd have to do $this->wire('apivar'). * * If you specify a value, it will set the value of useFuel to true or false. * If you don't specify a value, the current value will be returned. * * Local fuel scope should be disabled in classes where it might cause any conflict with class vars. * * #pw-internal * * @param bool $useFuel Optional boolean to turn it on or off. * @return bool Current value of $useFuel * */ public function useFuel($useFuel = null) { if($useFuel !== null) $this->useFuel = $useFuel ? true : false; return $this->useFuel; } /******************************************************************************************************* * IDENTIFICATION * */ /** * Return this object’s class name * * By default, this method returns the class name without namespace. To include the namespace, call it * with boolean true as the first argument. * * ~~~~~ * echo $page->className(); // outputs: Page * echo $page->className(true); // outputs: ProcessWire\Page * ~~~~~ * * #pw-group-identification * * @param array|bool|null $options Specify boolean `true` to return class name with namespace, or specify an array of * one or more options: * - `lowercase` (bool): Specify true to make it return hyphenated lowercase version of class name (default=false). * - `namespace` (bool): Specify true to include namespace from returned class name (default=false). * - *Note: The lowercase and namespace options may not both be true at the same time.* * @return string String with class name * */ public function className($options = null) { if(is_bool($options)) { $options = array('namespace' => $options); } else if(is_array($options)) { if(!empty($options['lowercase'])) $options['namespace'] = false; } else { $options = array(); } if(isset($options['namespace']) && $options['namespace'] === true) { $className = get_class($this); if(strpos($className, '\\') === false) $className = "\\$className"; } else { $className = wireClassName($this, false); } if(!empty($options['lowercase'])) { static $cache = array(); if(isset($cache[$className])) { $className = $cache[$className]; } else { $_className = $className; $part = substr($className, 1); if(strtolower($part) != $part) { // contains more than 1 uppercase character, convert to hyphenated lowercase $className = substr($className, 0, 1) . preg_replace('/([A-Z])/', '-$1', $part); } $className = strtolower($className); $cache[$_className] = $className; } } return $className; } /** * Unless overridden, classes descending from Wire return their class name when typecast as a string * * @return string * */ public function __toString() { return $this->className(); } /******************************************************************************************************* * HOOKS * */ /** * Hooks that are local to this instance of the class only. * */ protected $localHooks = array(); /** * @var WireHooks|null * */ private $_wireHooks = null; /** * @return WireHooks|null * @since 3.0.171 * */ protected function _wireHooks() { if($this->_wireHooks === null) $this->_wireHooks = $this->wire()->hooks; return $this->_wireHooks; } /** * Return all local hooks for this instance * * #pw-internal * * @return array * */ public function getLocalHooks() { return $this->localHooks; } /** * Set local hooks for this instance * * #pw-internal * * @param array $hooks * */ public function setLocalHooks(array $hooks) { $this->localHooks = $hooks; } /** * Call a method in this object, for use by WireHooks * * #pw-internal * * @param string $method * @param array $arguments * @return mixed * */ public function _callMethod($method, $arguments) { $qty = $arguments ? count($arguments) : 0; switch($qty) { case 0: $result = $this->$method(); break; case 1: $result = $this->$method($arguments[0]); break; case 2: $result = $this->$method($arguments[0], $arguments[1]); break; case 3: $result = $this->$method($arguments[0], $arguments[1], $arguments[2]); break; default: $result = call_user_func_array(array($this, $method), $arguments); } return $result; } /** * Call a hook method (optimization when it's known for certain the method exists) * * #pw-internal * * @param string $method Method name, without leading "___" * @param array $arguments * @return mixed * */ public function _callHookMethod($method, array $arguments = array()) { if(method_exists($this, $method)) { return $this->_callMethod($method, $arguments); } /** @var WireHooks $hooks */ $hooks = $this->_wireHooks(); if($hooks && $hooks->isMethodHooked($this, $method)) { $result = $hooks->runHooks($this, $method, $arguments); return $result['return']; } else { return $this->_callMethod("___$method", $arguments); } } /** * Provides the gateway for calling hooks in ProcessWire * * When a non-existant method is called, this checks to see if any hooks have been defined and sends the call to them. * * Hooks are defined by preceding the "hookable" method in a descending class with 3 underscores, like __myMethod(). * When the API calls $myObject->myMethod(), it gets sent to $myObject->___myMethod() after any 'before' hooks have been called. * Then after the ___myMethod() call, any "after" hooks are then called. "after" hooks have the opportunity to change the return value. * * Hooks can also be added for methods that don't actually exist in the class, allowing another class to add methods to this class. * * See the Wire::runHooks() method for the full implementation of hook calls. * * @param string $method * @param array $arguments * @return mixed * @throws WireException * */ public function __call($method, $arguments) { if(empty($arguments) && Fuel::isCommon($method)) { // faster version of _callWireAPI for when conditions allow if($this->_wire && !method_exists($this, "___$method")) { // get a common API var with no arguments as method call more quickly $val = $this->_wire->fuel($method); if($val !== null) return $val; } } $hooks = $this->_wireHooks(); if($hooks) { $result = $hooks->runHooks($this, $method, $arguments); if(!$result['methodExists'] && !$result['numHooksRun']) { $result = $this->_callWireAPI($method, $arguments); if(!$result) return $this->callUnknown($method, $arguments); } } else { $result = $this->_callWireAPI($method, $arguments); if(!$result) return $this->___callUnknown($method, $arguments); } return $result['return']; } /** * Helper to __call() method that maps a call to an API variable when appropriate * * @param string $method * @param array $arguments * @return array|bool * @internal * */ protected function _callWireAPI($method, $arguments) { $var = $this->_wire ? $this->_wire->fuel()->$method : null; if(!$var) return false; // requested method maps to an API variable $result = array('return' => null); if(count($arguments)) { $funcName = 'wire' . ucfirst($method); if(__NAMESPACE__) $funcName = __NAMESPACE__ . "\\$funcName"; if(function_exists($funcName)) { // a function exists with this API var name $wire = ProcessWire::getCurrentInstance(); // ensure function call maps to this PW instance if($wire !== $this->_wire) ProcessWire::setCurrentInstance($this->_wire); $result['return'] = call_user_func_array($funcName, $arguments); if($wire !== $this->_wire) ProcessWire::setCurrentInstance($wire); } } else { // if no arguments provided, just return API var $result['return'] = $var; } return $result; } /** * If method call resulted in no handler, this hookable method is called. * * This standard implementation just throws an exception. This is a template method, so the reason it * exists is so that other classes can override and provide their own handler. Classes that provide * their own handler should not do a `parent::__callUnknown()` unless they also fail, as that will * cause an exception to be thrown. * * If you want to override this method with a hook, see the example below. * ~~~~~ * $wire->addHookBefore('Wire::callUnknown', function(HookEvent $event) { * // Get information about unknown method that was called * $methodObject = $event->object; * $methodName = $event->arguments(0); // string * $methodArgs = $event->arguments(1); // array * // The replace option replaces the method and blocks the exception * $event->replace = true; * // Now do something with the information you have, for example * // you might want to populate a value to $event->return if * // you want the unknown method to return a value. * }); * ~~~~~ * * #pw-hooker * * @param string $method Requested method name * @param array $arguments Arguments provided * @return null|mixed Return value of method (if applicable) * @throws WireException * */ protected function ___callUnknown($method, $arguments) { if($arguments) {} // intentional to avoid unused argument notice $config = $this->wire()->config; if($config && $config->disableUnknownMethodException) return null; throw new WireException("Method " . $this->className() . "::$method does not exist or is not callable in this context"); } /** * Provides the implementation for calling hooks in ProcessWire * * Unlike __call, this method won't trigger an Exception if the hook and method don't exist. * Instead it returns a result array containing information about the call. * * #pw-internal * * @param string $method Method or property to run hooks for. * @param array $arguments Arguments passed to the method and hook. * @param string|array $type May be either 'method', 'property' or array of hooks (from getHooks) to run. Default is 'method'. * @return array Returns an array with the following information: * [return] => The value returned from the hook or NULL if no value returned or hook didn't exist. * [numHooksRun] => The number of hooks that were actually run. * [methodExists] => Did the hook method exist as a real method in the class? (i.e. with 3 underscores ___method). * [replace] => Set by the hook at runtime if it wants to prevent execution of the original hooked method. * */ public function runHooks($method, $arguments, $type = 'method') { return $this->_wireHooks()->runHooks($this, $method, $arguments, $type); } /** * Return all hooks associated with this class instance or method (if specified) * * #pw-group-hooks * * @param string $method Optional method that hooks will be limited to. Or specify '*' to return all hooks everywhere. * @param int $type Type of hooks to return, specify one of the following constants (from the WireHooks class): * - `WireHooks::getHooksAll` returns all hooks (default). * - `WireHooks::getHooksLocal` returns local hooks only. * - `WireHooks::getHooksStatic` returns static hooks only. * @return array * */ public function getHooks($method = '', $type = 0) { return $this->_wireHooks()->getHooks($this, $method, $type); } /** * Returns true if the method/property is hooked, false if it isn’t. * * This is for optimization use. It does not distinguish about class instance. * It only distinguishes about class if you provide a class with the `$method` argument (i.e. `Class::`). * As a result, a true return value indicates something "might" be hooked, as opposed to be * being definitely hooked. * * If checking for a hooked method, it should be in the form "Class::method()" or "method()". * If checking for a hooked property, it should be in the form "Class::property" or "property". * * #pw-internal * * @param string $method Method or property name in one of the following formats: * Class::method() * Class::property * method() * property * @param Wire|null $instance Optional instance to check against (see hasHook method for details) * Note that if specifying an $instance, you may not use the Class::method() or Class::property options for $method argument. * @return bool * @deprecated * */ static public function isHooked($method, Wire $instance = null) { /** @var ProcessWire $wire */ $wire = $instance ? $instance->wire() : ProcessWire::getCurrentInstance(); if($instance) return $instance->wire()->hooks->hasHook($instance, $method); return $wire->hooks->isHooked($method); } /** * Returns true if the method or property is hooked, false if it isn’t. * * - This method checks for both static hooks and local hooks. * - Accepts a `method()` or `property` name as an argument. * - Class context is assumed to be the current class this method is called on. * - Also considers the class parents for hooks. * * ~~~~~ * if($pages->hasHook('find()')) { * // the Pages::find() method is hooked * } * ~~~~~ * * #pw-group-hooks * * @param string $name Method() name or property name: * - If checking for a hooked method, it should be in the form `method()`. * - If checking for a hooked property, it should be in the form `property`. * @return bool True if this class instance has the hook, false if not. * @throws WireException When you try to call it with a Class::something() type method, which is not supported. * */ public function hasHook($name) { return $this->_wireHooks()->hasHook($this, $name); } /** * Hook a function/method to a hookable method call in this object * * - This method provides the implementation for addHookBefore(), addHookAfter(), addHookProperty(), addHookMethod() * - Hookable method calls are methods preceded by three underscores. * - You may also specify a method that doesn't exist already in the class. * - The hook method that you define may be part of a class or a globally scoped function. * * #pw-internal * * @param string|array $method Method name to hook into, NOT including the three preceding underscores. * May also be Class::Method for same result as using the fromClass option. * May also be array or CSV string of hook definitions to attach multiple to the same $toMethod (since 3.0.137). * @param object|null|callable $toObject Object to call $toMethod from, * Or null if $toMethod is a function outside of an object, * Or function|callable if $toObject is not applicable or function is provided as a closure. * @param string|array $toMethod Method from $toObject, or function name to call on a hook event, or $options array. Optional. * @param array $options Options that can modify default behaviors: * - `type` (string): May be 'method', 'property' or 'either'. If property, then it will respond to $obj->property * rather than $obj->method(). If 'either' it will respond to both. The default type is 'method'. * - `before` (bool): Execute the hook before the method call? (allows modification of arguments). * Not applicable if 'type' is 'property'. * - `after` (bool): Execute the hook after the method call? (allows modification of return value). * Not applicable if 'type' is 'property'. * - `priority` (int): A number determining the priority of a hook, where lower numbers are executed before * higher numbers. The default priority is 100. * - `allInstances` (bool): attach the hook to all instances of this object? Set automatically, but you may * still use in some instances. * - `fromClass` (string): The name of the class containing the hooked method, if not the object where addHook * was called. Set automatically, but you may still use in some instances. * - `argMatch` (array|null): An array of Selectors objects where the indexed argument (n) to the hooked method * must match, in order to execute hook. Default is null. * - `objMatch` (array|null): Selectors object that the current object must match in order to execute hook. * Default is null. * @return string A special Hook ID that should be retained if you need to remove the hook later. * If multiple methods were hooked then it is a CSV string of hook IDs, accepted removeHook method (since 3.0.137). * @throws WireException * @see https://processwire.com/docs/modules/hooks/ * */ public function addHook($method, $toObject, $toMethod = null, $options = array()) { return $this->_wireHooks()->addHook($this, $method, $toObject, $toMethod, $options); } /** * Add a hook to be executed before the hooked method * * - Use a "before" hook when you have code that should execute before a hookable method executes. * - One benefit of using a "before" hook is that you can have it modify the arguments that are sent to the hookable method. * - This type of hook can also completely replace a hookable method if hook populates an `$event->replace` property. * See the HookEvent class for details. * * ~~~~~ * // Attach hook to a method in current object * $this->addHookBefore('Page::path', $this, 'yourHookMethodName'); * * // Attach hook to an inline function * $this->addHookBefore('Page::path', function($event) { ... }); * * // Attach hook to a procedural function * $this->addHookBefore('Page::path', 'your_function_name'); * * // Attach hook from single object instance ($page) to inline function * $page->addHookBefore('path', function($event) { ... }); * ~~~~~ * * #pw-group-hooks * * @param string|array $method Method to hook in one of the following formats (please omit 3 leading underscores): * - `Class::method` - If hooking to *all* object instances of the class. * - `method` - If hooking to a single object instance. * - Since 3.0.137 it may also be multiple methods to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. * - Procedural function name, if hook is implemented by a procedural function. * - Null if you want to use the 3rd argument and don't need this argument. * @param string|array $toMethod Method from $toObject, or function name to call on a hook event. * This argument can be sustituted as the 2nd argument when the 2nd argument isn’t needed, * or it can be the $options argument. * @param array $options Array of options that can modify behavior: * - `type` (string): May be either 'method' or 'property'. If property, then it will respond to $obj->property * rather than $obj->method(). The default type is 'method'. * - `priority` (int): A number determining the priority of a hook, where lower numbers are executed before * higher numbers. The default priority is 100. * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookBefore($method, $toObject, $toMethod = null, $options = array()) { // This is the same as calling addHook with the 'before' option set the $options array. $options['before'] = true; if(!isset($options['after'])) $options['after'] = false; return $this->_wireHooks()->addHook($this, $method, $toObject, $toMethod, $options); } /** * Add a hook to be executed after the hooked method * * - Use an "after" hook when you have code that should execute after a hookable method executes. * - One benefit of using an "after" hook is that you can have it modify the return value. * * ~~~~~ * // Attach hook to a method in current object * $this->addHookAfter('Page::path', $this, 'yourHookMethodName'); * * // Attach hook to an inline function * $this->addHookAfter('Page::path', function($event) { ... }); * * // Attach hook to a procedural function * $this->addHookAfter('Page::path', 'your_function_name'); * * // Attach hook from single object instance ($page) to inline function * $page->addHookAfter('path', function($event) { ... }); * ~~~~~ * * #pw-group-hooks * * @param string|array $method Method to hook in one of the following formats (please omit 3 leading underscores): * - `Class::method` - If hooking to *all* object instances of the class. * - `method` - If hooking to a single object instance. * - Since 3.0.137 it may also be multiple methods to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. * - Procedural function name, if hook is implemented by a procedural function. * - Null if you want to use the 3rd argument and don't need this argument. * @param string|array $toMethod Method from $toObject, or function name to call on a hook event. * This argument can be sustituted as the 2nd argument when the 2nd argument isn't needed, * or it can be the $options argument. * @param array $options Array of options that can modify behavior: * - `type` (string): May be either 'method' or 'property'. If property, then it will respond to $obj->property * rather than $obj->method(). The default type is 'method'. * - `priority` (int): A number determining the priority of a hook, where lower numbers are executed before * higher numbers. The default priority is 100. * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookAfter($method, $toObject, $toMethod = null, $options = array()) { $options['after'] = true; if(!isset($options['before'])) $options['before'] = false; return $this->_wireHooks()->addHook($this, $method, $toObject, $toMethod, $options); } /** * Add a hook that will be accessible as a new object property. * * This enables you to add a new accessible property to an existing object, which will execute * your hook implementation method when called upon. * * Note that adding a hook with this just makes it possible to call the hook as a property. * Any hook property you add can also be called as a method, i.e. `$obj->foo` and `$obj->foo()` * are the same. * * ~~~~~ * // Adding a hook property * $wire->addHookProperty('Page::lastModifiedStr', function($event) { * $page = $event->object; * $event->return = wireDate('relative', $page->modified); * }); * * // Accessing the property (from any instance) * echo $page->lastModifiedStr; // outputs: "10 days ago" * ~~~~~ * * #pw-group-hooks * * @param string|array $property Name of property you want to add, must not collide with existing property or method names: * - `Class::property` to add the property to all instances of Class. * - `property` if just adding to a single object instance. * - Since 3.0.137 it may also be multiple properties to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. * - Procedural function name, if hook is implemented by a procedural function. * - Null if you want to use the 3rd argument and don't need this argument. * @param string|array $toMethod Method from $toObject, or function name to call on a hook event. * This argument can be sustituted as the 2nd argument when the 2nd argument isn’t needed, * or it can be the $options argument. * @param array $options Options typically aren't used in this context, but see Wire::addHookBefore() $options if you'd like. * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookProperty($property, $toObject, $toMethod = null, $options = array()) { // This is the same as calling addHook with the 'type' option set to 'property' in the $options array. // Note that descending classes that override __get must call getHook($property) and/or runHook($property). $options['type'] = 'property'; return $this->_wireHooks()->addHook($this, $property, $toObject, $toMethod, $options); } /** * Add a hook accessible as a new public method in a class (or object) * * - This enables you to add a new accessible public method to an existing object, which will execute * your hook implementation method when called upon. * * - Hook method can accept arguments and/or populate return values, just like any other regular method * in the class. However, methods such as this do not have access to private or protected * properties/methods in the class. * * - Methods added like this themselves become hookable as well. * * #pw-group-hooks * * ~~~~~ * // Adds a myHasParent($parent) method to all Page objects * $wire->addHookMethod('Page::myHasParent', function($event) { * $page = $event->object; * $parent = $event->arguments(0); * if(!$parent instanceof Page) { * throw new WireException("Page::myHasParent() requires a Page argument"); * } * if($page->parents()->has($parent)) { * // this page has the given parent * $event->return = true; * } else { * // does not have the given parent * $event->return = false; * } * }); * * // Calling the new method (from any instance) * $parent = $pages->get('/products/'); * if($page->myHasParent($parent)) { * // $page has the given $parent * } * ~~~~~ * * @param string $method Name of method you want to add, must not collide with existing property or method names: * - `Class::method` to add the method to all instances of Class. * - `method` to just add to a single object instance. * - Since 3.0.137 it may also be multiple methods to hook in CSV string or array. * @param object|null|callable $toObject Specify one of the following: * - Object instance to call `$toMethod` from (like `$this`). * - Inline function (closure) if providing implemention inline. * - Procedural function name, if hook is implemented by a procedural function. * - Null if you want to use the 3rd argument and don't need this argument. * @param string|array $toMethod Method from $toObject, or function name to call on a hook event. * This argument can be sustituted as the 2nd argument when the 2nd argument isn’t needed, * or it can be the $options argument. * @param array $options Options typically aren't used in this context, but see Wire::addHookBefore() $options if you'd like. * @return string A special Hook ID (or CSV string of hook IDs) that should be retained if you need to remove the hook later. * @since 3.0.16 Added as an alias to addHook() for syntactic clarity, previous versions can use addHook() method with same arguments. * @see https://processwire.com/docs/modules/hooks/ * */ public function addHookMethod($method, $toObject, $toMethod = null, $options = array()) { return $this->_wireHooks()->addHook($this, $method, $toObject, $toMethod, $options); } /** * Given a Hook ID, remove the hook * * Once a hook is removed, it will no longer execute. * * ~~~~~ * // Add a hook * $hookID = $pages->addHookAfter('find', function($event) { * // do something * }); * * // Remove the hook * $pages->removeHook($hookID); * ~~~~~ * ~~~~~ * // Hook function that removes itself * $hookID = $pages->addHookAfter('find', function($event) { * // do something * $event->removeHook(null); // note: calling removeHook on $event * }); * ~~~~~ * * #pw-group-hooks * * @param string|array|null $hookId ID of hook to remove (ID is returned by the addHook() methods) * Since 3.0.137 it may also be an array or CSV string of hook IDs to remove. * @return $this * */ public function removeHook($hookId) { return $this->_wireHooks()->removeHook($this, $hookId); } /******************************************************************************************************* * CHANGE TRACKING * */ /** * For setTrackChanges() method flags: track names only (default). * * #pw-group-changes * */ const trackChangesOn = 2; /** * For setTrackChanges() method flags: track names and values. * * #pw-group-changes * */ const trackChangesValues = 4; /** * Track changes mode * * @var int Bitmask * */ protected $trackChanges = 0; /** * Array containing the names of properties (as array keys) that were changed while change tracking was ON. * * Array values are insignificant unless trackChangeMode is trackChangesValues (1), in which case the values are the previous values. * * @var array * */ private $changes = array(); /** * Does the object have changes, or has the given property changed? * * Applicable only when object has change tracking enabled. * * ~~~~~ * // Check if page has changed * if($page->isChanged()) { * // Page has changes * } * * // Check if the page title field has changed * if($page->isChanged('title')) { * // The title has changed * } * ~~~~~ * * #pw-group-changes * * @param string $what Name of property, or if left blank, checks if any properties have changed. * @return bool True if property has changed, false if not. * */ public function isChanged($what = '') { if(!$what) return count($this->changes) > 0; return array_key_exists($what, $this->changes); } /** * Hookable method that is called whenever a property has changed while change tracking is enabled. * * - Enables hooks to monitor changes to the object. * - Do not call this method directly, as the `Wire::trackChange()` method already does so. * - Descending classes should call `$this->trackChange('name', $oldValue, $newValue);` when a property they are tracking has changed. * * #pw-group-hooker * * @param string $what Name of property that changed * @param mixed $old Previous value before change * @param mixed $new New value * @see Wire::trackChange() * */ public function ___changed($what, $old = null, $new = null) { // for hooks to listen to } /** * Track a change to a property in this object * * The change will only be recorded if change tracking is enabled for this object instance. * * #pw-group-changes * * @param string $what Name of property that changed * @param mixed $old Previous value before change * @param mixed $new New value * @return $this * */ public function trackChange($what, $old = null, $new = null) { if($this->trackChanges & self::trackChangesOn) { // establish it as changed if(array_key_exists($what, $this->changes)) { // remember last value so we can avoid duplication in hooks or storage $lastValue = end($this->changes[$what]); } else { $lastValue = null; $this->changes[$what] = array(); } if(is_null($old) || is_null($new) || $lastValue !== $new) { /** @var WireHooks $hooks */ $hooks = $this->_wireHooks(); if(($hooks && $hooks->isHooked('changed()')) || !$hooks) { $this->changed($what, $old, $new); // triggers ___changed hook } else { $this->___changed($what, $old, $new); } } if($this->trackChanges & self::trackChangesValues) { // track changed values, but avoid successive duplication of same value if(is_object($old) && $old === $new) $old = clone $old; // keep separate copy of objects for old value if($lastValue !== $old || !count($this->changes[$what])) $this->changes[$what][] = $old; } else { // don't track changed values, just names of fields $this->changes[$what][] = null; } } return $this; } /** * Untrack a change to a property in this object * * #pw-group-changes * * @param string $what Name of property that you want to remove its change being tracked * @return $this * */ public function untrackChange($what) { unset($this->changes[$what]); return $this; } /** * Turn change tracking ON or OFF * * ~~~~~ * // Enable change tracking * $page->setTrackChanges(true); * * // Disable change tracking * $page->setTrackChanges(false); * * // Enable change tracking and remember values * $page->setTrackChanges(Wire::trackChangesValues); * $page->setTrackChanges(true); * ~~~~~ * * #pw-group-changes * * @param bool|int $trackChanges Specify one of the following: * - `true` (bool): Enables change tracking. * - `false` (bool): Disables change tracking * - `Wire::trackChangesOn` (constant): Enables change tracking (same as specifying boolean true). * - `Wire::trackChangesValues` (constant): Enables tracking of changed values when change tracking is already on. * This uses more memory since it keeps previous values, so it is not enabled by default. Once enabled, the * setting will persist through boolean true|false arguments. * @return $this * */ public function setTrackChanges($trackChanges = true) { if(is_bool($trackChanges) || !$trackChanges) { // turn change track on or off if($trackChanges) { $this->trackChanges = $this->trackChanges | self::trackChangesOn; // add bit } else { $this->trackChanges = $this->trackChanges & ~self::trackChangesOn; // remove bit } } else if(is_int($trackChanges)) { // set bitmask $allowed = array( self::trackChangesOn, self::trackChangesValues, self::trackChangesOn | self::trackChangesValues ); if(in_array($trackChanges, $allowed)) $this->trackChanges = $trackChanges; } return $this; } /** * Returns true or 1 if change tracking is on, or false or 0 if it is not, or mode bitmask (int) if requested. * * #pw-group-changes * * @param bool $getMode When true, the track changes mode bitmask will be returned * @return int 0/false if off, 1/true if On, or mode bitmask if requested * */ public function trackChanges($getMode = false) { if($getMode) return $this->trackChanges; return $this->trackChanges & self::trackChangesOn; } /** * Clears out any tracked changes and turns change tracking ON or OFF * * ~~~~ * // Clear any changes that have been tracked and start fresh * $page->resetTrackChanges(); * ~~~~ * * #pw-group-changes * * @param bool $trackChanges True to turn change tracking ON, or false to turn OFF. Default of true is assumed. * @return $this * */ public function resetTrackChanges($trackChanges = true) { $this->changes = array(); return $this->setTrackChanges($trackChanges); } /** * Return an array of properties that have changed while change tracking was on. * * ~~~~~ * // Get an array of changed field names * $changes = $page->getChanges(); * ~~~~~ * * #pw-group-changes * * @param bool $getValues Specify one of the following, or omit for default setting. * - `false` (bool): return array of changed property names (default setting). * - `true` (bool): return an associative array containing an array of previous values, indexed by * property name, oldest to newest. Requires Wire::trackChangesValues mode to be enabled. * - `2` (int): Return array where both keys and values are changed property names. * @return array * */ public function getChanges($getValues = false) { if($getValues === 2) { $changes = array(); foreach($this->changes as $name => $value) { if($value) {} // value ignored $changes[$name] = $name; } return $changes; } else if($getValues) { return $this->changes; } else { return array_keys($this->changes); } } /******************************************************************************************************* * NOTICES AND LOGS * */ /** * @var Notices[] * */ protected $_notices = array( 'errors' => null, 'warnings' => null, 'messages' => null ); /** * Record a Notice, internal use (contains the code for message, warning and error methods) * * @param string|array|Wire $text Title of notice * @param int|string $flags Flags bitmask or space separated string of flag names * @param string $name Name of container * @param string $class Name of Notice class * @return $this * */ protected function _notice($text, $flags, $name, $class) { if($flags === true) $flags = Notice::log; $class = wireClassName($class, true); $notice = $this->wire(new $class($text, $flags)); /** @var Notice $notice */ $notice->class = $this->className(); if($this->_notices[$name] === null) $this->_notices[$name] = $this->wire(new Notices()); $notices = $this->wire()->notices; if($notices) $notices->add($notice); // system wide if(!($notice->flags & Notice::logOnly)) $this->_notices[$name]->add($notice); // local only return $this; } /** * Record an informational or “success” message in the system-wide notices. * * This method automatically identifies the message as coming from this class. * * ~~~~~ * $this->message("This is the notice text"); * $this->message("This notice is also logged", true); * $this->message("This notice is only shown in debug mode", Notice::debug); * $this->message("This notice allows markup", Notice::allowMarkup); * $this->message("Notice using multiple flags", Notice::debug | Notice::logOnly); * ~~~~~ * * #pw-group-notices * * @param string|array|Wire $text Text to include in the notice * @param int|bool|string $flags Optional flags to alter default behavior: * - `Notice::admin` (constant): Show notice only if user is in the admin. * - `Notice::allowMarkdown` (constant): Allow basic markdown and bracket markup (see $sanitizer->entitiesMarkdown()). * - `Notice::allowMarkup` (constant): Indicates notice should allow the use of HTML markup tags. * - `Notice::debug` (constant): Indicates notice should only be shown when debug mode is active. * - `Notice::log` (constant): Indicates notice should also be logged. * - `Notice::logOnly` (constant): Indicates notice should only be logged. * - `Notice::login` (constant): Show notice only if it will be seen by a logged-in user. * - `Notice::noGroup` (constant): Indicates notice should not group with others of the same type (where supported). * - `Notice::prepend` (constant): Indicates notice should prepend rather than append. * - `Notice::superuser` (constant): Show notice only if current user is a superuser. * - `true` (boolean): Shortcut for the `Notice::log` constant. * - In 3.0.149+ you may also specify a space-separated string of flag names, i.e. "admin log noGroup". * @return $this * @see Wire::messages(), Wire::warning(), Wire::error() * */ public function message($text, $flags = 0) { return $this->_notice($text, $flags, 'messages', 'NoticeMessage'); } /** * Record a warning error message in the system-wide notices. * * This method automatically identifies the warning as coming from this class. * * ~~~~~ * $this->warning("This is the notice text"); * $this->warning("This notice is also logged", true); * $this->warning("This notice is only shown in debug mode", Notice::debug); * $this->warning("This notice allows markup", Notice::allowMarkup); * $this->warning("Notice using multiple flags", Notice::debug | Notice::logOnly); * ~~~~~ * * #pw-group-notices * * @param string|array|Wire $text Text to include in the notice * @param int|bool|string $flags Optional flags to alter default behavior: * - `Notice::admin` (constant): Show notice only if user is in the admin. * - `Notice::allowMarkdown` (constant): Allow basic markdown and bracket markup (see $sanitizer->entitiesMarkdown()). * - `Notice::allowMarkup` (constant): Indicates notice should allow the use of HTML markup tags. * - `Notice::debug` (constant): Indicates notice should only be shown when debug mode is active. * - `Notice::log` (constant): Indicates notice should also be logged. * - `Notice::logOnly` (constant): Indicates notice should only be logged. * - `Notice::login` (constant): Show notice only if it will be seen by a logged-in user. * - `Notice::noGroup` (constant): Indicates notice should not group with others of the same type (where supported). * - `Notice::prepend` (constant): Indicates notice should prepend rather than append. * - `Notice::superuser` (constant): Show notice only if current user is a superuser. * - `true` (boolean): Shortcut for the `Notice::log` constant. * - In 3.0.149+ you may also specify a space-separated string of flag names, i.e. "admin log noGroup". * @return $this * @see Wire::warnings(), Wire::message(), Wire::error() * * */ public function warning($text, $flags = 0) { return $this->_notice($text, $flags, 'warnings', 'NoticeWarning'); } /** * Record an non-fatal error message in the system-wide notices. * * - This method automatically identifies the error as coming from this class. * - You should still make fatal errors throw a `WireException` (or class derived from it). * * ~~~~~ * $this->error("This is the notice text"); * $this->error("This notice is also logged", true); * $this->error("This notice is only shown in debug mode", Notice::debug); * $this->error("This notice allows markup", Notice::allowMarkup); * $this->error("Notice using multiple flags", Notice::debug | Notice::logOnly); * ~~~~~ * * #pw-group-notices * * @param string|array|Wire $text Text to include in the notice * @param int|bool|string $flags Optional flags to alter default behavior: * - `Notice::admin` (constant): Show notice only if user is in the admin. * - `Notice::allowMarkdown` (constant): Allow basic markdown and bracket markup (see $sanitizer->entitiesMarkdown()). * - `Notice::allowMarkup` (constant): Indicates notice should allow the use of HTML markup tags. * - `Notice::debug` (constant): Indicates notice should only be shown when debug mode is active. * - `Notice::log` (constant): Indicates notice should also be logged. * - `Notice::logOnly` (constant): Indicates notice should only be logged. * - `Notice::login` (constant): Show notice only if it will be seen by a logged-in user. * - `Notice::noGroup` (constant): Indicates notice should not group with others of the same type (where supported). * - `Notice::prepend` (constant): Indicates notice should prepend rather than append. * - `Notice::superuser` (constant): Show notice only if current user is a superuser. * - `true` (boolean): Shortcut for the `Notice::log` constant. * - In 3.0.149+ you may also specify a space-separated string of flag names, i.e. "admin log noGroup". * @return $this * @see Wire::errors(), Wire::message(), Wire::warning() * */ public function error($text, $flags = 0) { return $this->_notice($text, $flags, 'errors', 'NoticeError'); } /** * Hookable method called when an Exception occurs * * - It will log Exception to `exceptions.txt` log if 'exceptions' is in `$config->logs`. * - It will re-throw Exception if `$config->allowExceptions` is true. * - If additional `$text` is provided, it will be sent to notice method call. * * #pw-hooker * * @param \Exception|WireException $e Exception object that was thrown. * @param bool|int $severe Whether or not it should be considered severe (default=true). * @param string|array|object|true $text Additional details (optional): * - When provided, it will be sent to `$this->error($text)` if $severe is true, or `$this->warning($text)` if $severe is false. * - Specify boolean `true` to just send the `$e->getMessage()` to `$this->error()` or `$this->warning()`. * @return $this * @throws \Exception If `$severe==true` and `$config->allowExceptions==true` * */ public function ___trackException(\Exception $e, $severe = true, $text = null) { $config = $this->wire()->config; $log = $this->wire()->log; $msg = $e->getMessage(); if($text !== null) { if($text === true) $text = $msg; $severe ? $this->error($text) : $this->warning($text); if(strlen($msg) && strpos($text, $msg) === false) $msg = "$text - $msg"; } if(in_array('exceptions', $config->logs) && $log) { $msg .= " (in " . str_replace($config->paths->root, '/', $e->getFile()) . " line " . $e->getLine() . ")"; $log->save('exceptions', $msg); } if($severe && $config->allowExceptions) { throw $e; // re-throw, if requested } return $this; } /** * Return or manage errors recorded by just this object or all Wire objects * * This method returns and manages errors that were previously set by `Wire::error()`. * * ~~~~~ * // Get errors for one object * $errors = $obj->errors(); * * // Get first error in object * $error = $obj->errors('first'); * * // Get errors for all Wire objects * $errors = $obj->errors('all'); * * // Get and clear all errors for all Wire objects * $errors = $obj->errors('clear all'); * ~~~~~ * * #pw-group-notices * * @param string|array $options One or more of array elements or space separated string of: * - `first` - only first item will be returned * - `last` - only last item will be returned * - `all` - include all errors, including those beyond the scope of this object * - `clear` - clear out all items that are returned from this method * - `array` - return an array of strings rather than series of Notice objects. * - `string` - return a newline separated string rather than array/Notice objects. * @return Notices|array|string Array of `NoticeError` errors, or string if last, first or str option was specified. * */ public function errors($options = array()) { if(!is_array($options)) $options = explode(' ', strtolower($options)); $options[] = 'errors'; return $this->messages($options); } /** * Return or manage warnings recorded by just this object or all Wire objects * * This method returns and manages warnings that were previously set by `Wire::warning()`. * * ~~~~~ * // Get warnings for one object * $warnings = $obj->warnings(); * * // Get first warning in object * $warning = $obj->warnings('first'); * * // Get warnings for all Wire objects * $warnings = $obj->warnings('all'); * * // Get and clear all warnings for all Wire objects * $warnings = $obj->warnings('clear all'); * ~~~~~ * * #pw-group-notices * * @param string|array $options One or more of array elements or space separated string of: * - `first` - only first item will be returned * - `last` - only last item will be returned * - `all` - include all errors, including those beyond the scope of this object * - `clear` - clear out all items that are returned from this method * - `array` - return an array of strings rather than series of Notice objects. * - `string` - return a newline separated string rather than array/Notice objects. * @return Notices|array|string Array of `NoticeWarning` warnings, or string if last, first or str option was specified. * * */ public function warnings($options = array()) { if(!is_array($options)) $options = explode(' ', strtolower($options)); $options[] = 'warnings'; return $this->messages($options); } /** * Return or manage messages recorded by just this object or all Wire objects * * This method returns and manages messages that were previously set by `Wire::message()`. * * ~~~~~ * // Get messages for one object * $messages = $obj->messages(); * * // Get first message in object * $message = $obj->messages('first'); * * // Get messages for all Wire objects * $messages = $obj->messages('all'); * * // Get and clear all messages for all Wire objects * $messages = $obj->messages('clear all'); * ~~~~~ * * #pw-group-notices * * @param string|array $options One or more of array elements or space separated string of: * - `first` - only first item will be returned * - `last` - only last item will be returned * - `all` - include all messages, including those beyond the scope of this object * - `clear` - clear out all items that are returned from this method * - `array` - return an array of strings rather than series of Notice objects. * - `string` - return a newline separated string rather than array/Notice objects. * @return Notices|array|string Array of `NoticeMessage` messages, or string if last, first or str option was specified. * */ public function messages($options = array()) { if(!is_array($options)) $options = explode(' ', strtolower($options)); if(in_array('errors', $options)) { $type = 'errors'; } else if(in_array('warnings', $options)) { $type = 'warnings'; } else { $type = 'messages'; } $clear = in_array('clear', $options); if(in_array('all', $options)) { // get all of either messages, warnings or errors (either in or out of this object instance) $notices = $this->wire()->notices; $value = $this->wire(new Notices()); /** @var Notices $value */ foreach($notices as $notice) { if($notice->getName() != $type) continue; $value->add($notice); if($clear) $notices->remove($notice); // clear global } if($clear) $this->_notices[$type] = null; // clear local } else { // get messages, warnings or errors specific to this object instance /** @var Notices $value */ $value = $this->_notices[$type] === null ? $this->wire(new Notices()) : $this->_notices[$type]; if(in_array('first', $options)) { $value = $clear ? $value->shift() : $value->first(); } else if(in_array('last', $options)) { $value = $clear ? $value->pop() : $value->last(); } else if($clear) { $this->_notices[$type] = null; } if($clear && $value) { $this->wire()->notices->removeItems($value); // clear from global notices } } if(in_array('array', $options) || in_array('string', $options)) { if($value instanceof Notice) { $value = array($value->text); } else { $_value = array(); foreach($value as $notice) { /** @var Notice $notice */ $_value[] = $notice->text; } $value = $_value; } if(in_array('string', $options)) { $value = implode("\n", $value); } } return $value; } /** * Log a message for this class * * Message is saved to a log file in ProcessWire's logs path to a file with * the same name as the class, converted to hyphenated lowercase. For example, * a class named `MyWidgetData` would have a log named `my-widget-data.txt`. * * ~~~~~ * $this->log("This message will be logged"); * ~~~~~ * * #pw-group-notices * * @param string $str Text to log, or omit to return the `$log` API variable. * @param array $options Optional extras to include: * - `url` (string): URL to record the with the log entry (default=auto-detect) * - `name` (string): Name of log to use (default=auto-detect) * - `user` (User|string|null): User instance, user name, or null to log for current User. (default=null) * @return WireLog * */ public function ___log($str = '', array $options = array()) { $log = $this->wire()->log; if($log && strlen($str)) { if(isset($options['name'])) { $name = $options['name']; unset($options['name']); } else { $name = $this->className(array('lowercase' => true)); } $log->save($name, $str, $options); } return $log; } /******************************************************************************************************* * TRANSLATION * */ /** * Translate the given text string into the current language if available. * * If not available, or if the current language is the native language, then it returns the text as is. * * #pw-group-translation * * @param string|array $text Text string to translate (or array in 3.0.151 also supported) * @return string * */ public function _($text) { return __($text, $this); } /** * Perform a language translation in a specific context * * Used when to text strings might be the same in English, but different in other languages. * * #pw-group-translation * * @param string|array $text Text for translation. * @param string $context Name of context * @return string Translated text or original text if translation not available. * */ public function _x($text, $context) { return _x($text, $context, $this); } /** * Perform a language translation with singular and plural versions * * #pw-group-translation * * @param string $textSingular Singular version of text (when there is 1 item). * @param string $textPlural Plural version of text (when there are multiple items or 0 items). * @param int $count Quantity used to determine whether singular or plural. * @return string Translated text or original text if translation not available. * */ public function _n($textSingular, $textPlural, $count) { return _n($textSingular, $textPlural, $count, $this); } /******************************************************************************************************* * API VARIABLE MANAGEMENT * * To replace fuel in PW 3.0 * */ /** * ProcessWire instance * * @var ProcessWire|bool|null * */ protected $_wire = null; /** * Set the current ProcessWire instance for this object (PW 3.0) * * #pw-internal * * @param ProcessWire $wire * */ public function setWire(ProcessWire $wire) { $wired = $this->_wire; if($wired === $wire) return; $this->_wire = $wire; if($this->_wireHooks) $this->_wireHooks = $wire->wire()->hooks; if($wired) return; $this->getInstanceNum(); $this->wired(); } /** * Get the current ProcessWire instance (PW 3.0) * * You can also use the wire() method with no arguments. * * #pw-internal * * @return null|ProcessWire * */ public function getWire() { return $this->_wire ? $this->_wire : null; } /** * Is this object wired to a ProcessWire instance? * * #pw-internal * * @return bool * */ public function isWired() { return $this->_wire ? true : false; } /** * Get an API variable, create an API variable, or inject dependencies. * * This method provides the following: * * - Access to API variables: * `$pages = $this->wire('pages');` * * - Access to current ProcessWire instance: * `$wire = $this->wire();` * * - Creating new API variables: * `$this->wire('widgets', $widgets);` * * - Injection of dependencies to Wire derived objects: * `$this->wire($widgets);` * * Most Wire derived objects also support access to API variables directly via `$this->apiVar`. * * There is also the `wire()` procedural function, which provides the same access to get API * variables. Note however the procedural version does not support creating API variables or * injection of dependencies. * * ~~~~~ * // Get the 'pages' API variable * $pages = $this->wire('pages'); * * // Get the 'pages' API variable using alternate syntax * $pages = $this->wire()->pages; * * // Get all API variables (returns a Fuel object) * $all = $this->wire('all'); * * // Get the current ProcessWire instance (no arguments) * $wire = $this->wire(); * * // Create a new API variable named 'widgets' * $this->wire('widgets', $widgets); * * // Create new API variable and lock it so nothing can overwrite * $this->wire('widgets', $widgets, true); * * // Alternate syntax for the two above * $this->wire()->set('widgets', $widgets); * $this->wire()->set('widgets', $widgets, true); // lock * * // Inject dependencies into Wire derived object * $this->wire($widgets); * * // Inject dependencies during construct * $newPage = $this->wire(new Page()); * ~~~~~ * * @param string|object $name Name of API variable to retrieve, set, or omit to retrieve the master ProcessWire object. * @param null|mixed $value Value to set if using this as a setter, otherwise omit. * @param bool $lock When using as a setter, specify true if you want to lock the value from future changes (default=false). * @return ProcessWire|Wire|Session|Page|Pages|Modules|User|Users|Roles|Permissions|Templates|Fields|Fieldtypes|Sanitizer|Config|Notices|WireDatabasePDO|WireHooks|WireDateTime|WireFileTools|WireMailTools|WireInput|string|mixed * @throws WireException * * */ public function wire($name = '', $value = null, $lock = false) { if($this->_wire) { // this instance is wired $wire = $this->_wire; // quick exit when _wire already set and not getting/setting API var if($name === '') return $wire; } else { // this object has not yet been wired! use last known current instance as fallback // note this condition is unsafe in multi-instance mode $wire = ProcessWire::getCurrentInstance(); if(!$wire) return is_object($name) ? $name : null; // there are no ProcessWire instances if($name && $this->_wire === null) { $this->_wire = false; // false prevents this from being called another time for this object $wire->_objectNotWired($this, $name, $value); } } if(is_object($name)) { // make an object wired (inject ProcessWire instance to object) if($name instanceof WireFuelable) { if($this->_wire) $name->setWire($wire); // inject fuel, PW 3.0 if(is_string($value) && $value) { // set as new API var if API var name specified in $value $wire->fuel()->set($value, $name, $lock); } $value = $name; // return the provided instance } else { throw new WireException('Wire::wire($o) expected WireFuelable for $o and was given ' . get_class($name)); } } else if($value !== null) { // setting a API variable/fuel value, and make it wired if($value instanceof WireFuelable && $this->_wire) $value->setWire($wire); $wire->fuel()->set($name, $value, $lock); } else if(empty($name)) { // return ProcessWire instance $value = $wire; } else if($name === '*' || $name === 'all' || $name == 'fuel') { // return Fuel instance $value = $wire->fuel(); } else { // get API variable $value = $wire->fuel()->$name; } return $value; } /** * Initialization called when object injected with ProcessWire instance (aka “wired”) * * - Can be used for any constructor-type initialization that depends on API vars. * - Called automatically when object is “wired”, do not call it on your own. * - Expects to be called only once per object instance. * - Typically called after `__construct()` but before any other method calls. * - Please note: If object is never “wired” then this method will not be called! * * ~~~~~ * class Test extends Wire { * function wired() { * echo "Wired to ProcessWire instance: "; * echo $this->wire()->getProcessWireInstanceID(); * } * } * * // objects in ProcessWire are “wired” like this: * $o = new Test(); * $this->wire($o); // outputs "ProcessWire instance: n" * * // or on one line, like this… * $this->wire(new Test()); // outputs "ProcessWire instance: n" * ~~~~~ * * #pw-internal * * @since 3.0.158 * */ public function wired() { } /** * Get an object property by direct reference or NULL if it doesn't exist * * If not overridden, this is primarily used as a shortcut for the fuel() method. * * Descending classes may have their own __get() but must pass control to this one when they can't find something. * * @param string $name * @return mixed|null * */ public function __get($name) { if($name === 'wire') return $this->wire(); if($name === 'fuel') return $this->wire('fuel'); if($name === 'className') return $this->className(); if($this->useFuel()) { $value = $this->wire($name); if($value !== null) return $value; } $hooks = $this->_wireHooks(); /** @var WireHooks $hooks */ if($hooks && $hooks->isHooked($name)) { // potential property hook $result = $hooks->runHooks($this, $name, array(), 'property'); return $result['return']; } return null; } /** * debugInfo PHP 5.6+ magic method * * This is used when you print_r() an object instance. * * @return array * */ public function __debugInfo() { /** @var WireDebugInfo $debugInfo */ require_once(__DIR__ . '/WireDebugInfo.php'); $debugInfo = $this->wire(new WireDebugInfo()); return $debugInfo->getDebugInfo($this, true); } /** * Minimal/small debug info * * Same as __debugInfo() but with no hooks info, no change tracking info, and less verbose * * #pw-internal * * @return array * @since 3.0.130 * */ public function debugInfoSmall() { /** @var WireDebugInfo $debugInfo */ $debugInfo = $this->wire(new WireDebugInfo()); return $debugInfo->getDebugInfo($this, true); } }