praiadeseselle/wire/core/Wire.php

1896 lines
69 KiB
PHP
Raw Permalink Normal View History

2022-03-08 15:55:41 +01:00
<?php namespace ProcessWire;
/**
* ProcessWire Base Class "Wire"
*
* #pw-summary Wire is the base class for most ProcessWire classes and modules.
* #pw-body =
* Wire derived classes have a `$this->wire()` method that provides access to ProcessWires 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 2021 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 $fieldgroups #pw-internal
* @property Fields $fields #pw-internal
* @property Fieldtypes $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 $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 $notices #pw-internal
* @property Page $page #pw-internal
* @property Pages $pages #pw-internal
* @property Permissions $permissions #pw-internal
* @property Process|ProcessPageView $process #pw-internal
* @property WireProfilerInterface $profiler #pw-internal
* @property Roles $roles #pw-internal
* @property Sanitizer $sanitizer #pw-internal
* @property Session $session #pw-internal
* @property Templates $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 objects 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 objects 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;
$result = null;
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 isnt.
*
* 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 isnt.
*
* - 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 isnt 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 isnt 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 isnt 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
*
*/
private $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|bool 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));
$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 <em>markup</em>", 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::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::allowMarkup` (constant): Indicates notice should allow the use of HTML markup tags.
* - `true` (boolean): Shortcut for the `Notice::log` constant.
* - In 3.0.149+ you may also specify a space-separated string of flag names.
* @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 <em>markup</em>", 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::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::allowMarkup` (constant): Indicates notice should allow the use of HTML markup tags.
* - `true` (boolean): Shortcut for the `Notice::log` constant.
* - In 3.0.149+ you may also specify a space-separated string of flag names.
* @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 <em>markup</em>", 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::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::allowMarkup` (constant): Indicates notice should allow the use of HTML markup tags.
* - `true` (boolean): Shortcut for the `Notice::log` constant.
* - In 3.0.149+ you may also specify a space-separated string of flag names.
* @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) $_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);
}
}