mirror of
https://github.com/processwire/processwire.git
synced 2025-08-13 02:04:35 +02:00
Add support for conditional hooks that match method return values. For details see: https://processwire.com/docs/modules/hooks/#conditional-hooks-matching-return-value-or-type
This commit is contained in:
@@ -47,6 +47,7 @@ class WireHooks {
|
|||||||
* - fromClass: the name of the class containing the hooked method, if not the object where addHook was executed. Set automatically, but you may still use in some instances.
|
* - fromClass: the name of the class containing the hooked method, if not the object where addHook was executed. Set automatically, but you may still use in some instances.
|
||||||
* - argMatch: array of Selectors objects where the indexed argument (n) to the hooked method must match, order to execute hook.
|
* - argMatch: array of Selectors objects where the indexed argument (n) to the hooked method must match, order to execute hook.
|
||||||
* - objMatch: Selectors object that the current object must match in order to execute hook
|
* - objMatch: Selectors object that the current object must match in order to execute hook
|
||||||
|
* - retMatch: Selectors object that must match the return value, or a match string to match return value
|
||||||
* - public: auto-assigned to true or false by addHook() as to whether the method is public or private/protected.
|
* - public: auto-assigned to true or false by addHook() as to whether the method is public or private/protected.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -58,7 +59,10 @@ class WireHooks {
|
|||||||
'allInstances' => false,
|
'allInstances' => false,
|
||||||
'fromClass' => '',
|
'fromClass' => '',
|
||||||
'argMatch' => null,
|
'argMatch' => null,
|
||||||
|
'argMatchType' => [],
|
||||||
'objMatch' => null,
|
'objMatch' => null,
|
||||||
|
'retMatch' => null,
|
||||||
|
'retMatchType' => '',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -631,7 +635,20 @@ class WireHooks {
|
|||||||
$options['fromClass'] = $fromClass;
|
$options['fromClass'] = $fromClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$retMatch = '';
|
||||||
$argOpen = strpos($method, '(');
|
$argOpen = strpos($method, '(');
|
||||||
|
|
||||||
|
if($argOpen) {
|
||||||
|
if(strpos($method, ':(')) {
|
||||||
|
list($method, $retMatch) = explode(':(', $method, 2);
|
||||||
|
$retMatch = rtrim($retMatch, ') ');
|
||||||
|
} else if(strpos($method, ':<') && substr(trim($method), -1) === '>') {
|
||||||
|
list($method, $retMatch) = explode(':<', $method, 2);
|
||||||
|
$retMatch = "<$retMatch";
|
||||||
|
}
|
||||||
|
$argOpen = strpos($method, '(');
|
||||||
|
}
|
||||||
|
|
||||||
if($argOpen) {
|
if($argOpen) {
|
||||||
// arguments to match may be specified in method name
|
// arguments to match may be specified in method name
|
||||||
$argClose = strpos($method, ')');
|
$argClose = strpos($method, ')');
|
||||||
@@ -659,16 +676,29 @@ class WireHooks {
|
|||||||
// just single argument specified, so argument 0 is assumed
|
// just single argument specified, so argument 0 is assumed
|
||||||
}
|
}
|
||||||
if(is_string($argMatch)) $argMatch = array(0 => $argMatch);
|
if(is_string($argMatch)) $argMatch = array(0 => $argMatch);
|
||||||
|
$argMatchType = [];
|
||||||
foreach($argMatch as $argKey => $argVal) {
|
foreach($argMatch as $argKey => $argVal) {
|
||||||
if(Selectors::stringHasSelector($argVal)) {
|
list($argVal, $argValType) = $this->prepareArgMatch($argVal);
|
||||||
/** @var Selectors $selectors */
|
$argMatch[$argKey] = $argVal;
|
||||||
$selectors = $this->wire->wire(new Selectors());
|
$argMatchType[$argKey] = $argValType;
|
||||||
$selectors->init($argVal);
|
}
|
||||||
$argMatch[$argKey] = $selectors;
|
if(count($argMatch)) {
|
||||||
|
$options['argMatch'] = $argMatch;
|
||||||
|
$options['argMatchType'] = $argMatchType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(count($argMatch)) $options['argMatch'] = $argMatch;
|
} else if(strpos($method, ':')) {
|
||||||
|
list($method, $retMatch) = explode(':', $method, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($retMatch) {
|
||||||
|
// match return value
|
||||||
|
if($options['before'] && !$options['after']) {
|
||||||
|
throw new WireException('You cannot match return values with “before” hooks');
|
||||||
|
}
|
||||||
|
list($retMatch, $retMatchType) = $this->prepareArgMatch($retMatch);
|
||||||
|
$options['retMatch'] = $retMatch;
|
||||||
|
$options['retMatchType'] = $retMatchType;
|
||||||
}
|
}
|
||||||
|
|
||||||
$localHooks = $object->getLocalHooks();
|
$localHooks = $object->getLocalHooks();
|
||||||
@@ -996,50 +1026,25 @@ class WireHooks {
|
|||||||
if($type == 'method' && !empty($hook['options']['argMatch'])) {
|
if($type == 'method' && !empty($hook['options']['argMatch'])) {
|
||||||
// argument comparison to determine at runtime whether to execute the hook
|
// argument comparison to determine at runtime whether to execute the hook
|
||||||
$argMatches = $hook['options']['argMatch'];
|
$argMatches = $hook['options']['argMatch'];
|
||||||
|
$argMatchTypes = $hook['options']['argMatchType'];
|
||||||
$matches = true;
|
$matches = true;
|
||||||
foreach($argMatches as $argKey => $argMatch) {
|
foreach($argMatches as $argKey => $argMatch) {
|
||||||
/** @var Selectors $argMatch */
|
/** @var Selectors $argMatch */
|
||||||
|
$argMatchType = isset($argMatchTypes[$argKey]) ? $argMatchTypes[$argKey] : '';
|
||||||
$argVal = isset($arguments[$argKey]) ? $arguments[$argKey] : null;
|
$argVal = isset($arguments[$argKey]) ? $arguments[$argKey] : null;
|
||||||
if(is_object($argMatch)) {
|
$matches = $this->conditionalArgMatch($argMatch, $argVal, $argMatchType);
|
||||||
// Selectors object
|
|
||||||
if(is_object($argVal)) {
|
|
||||||
$matches = $argMatch->matches($argVal);
|
|
||||||
} else {
|
|
||||||
// we don't work with non-object here
|
|
||||||
$matches = false;
|
|
||||||
}
|
|
||||||
} else if(is_string($argMatch) && strpos($argMatch, '<') === 0 && substr($argMatch, -1) === '>') {
|
|
||||||
// i.e. <Page>, <User>, <string>, <object>, <bool>, etc.
|
|
||||||
$argMatch = trim($argMatch, '<>');
|
|
||||||
if(strpos($argMatch, '|')) {
|
|
||||||
// i.e. <User|Role|Permission> or <int|float> etc.
|
|
||||||
$argMatches = explode('|', str_replace(array('<', '>'), '', $argMatch));
|
|
||||||
} else {
|
|
||||||
$argMatches = array($argMatch);
|
|
||||||
}
|
|
||||||
foreach($argMatches as $argMatchType) {
|
|
||||||
if(isset($this->argMatchTypes[$argMatchType])) {
|
|
||||||
$argMatchFunc = $this->argMatchTypes[$argMatchType];
|
|
||||||
$matches = $argMatchFunc($argVal);
|
|
||||||
} else {
|
|
||||||
$matches = wireInstanceOf($argVal, $argMatchType);
|
|
||||||
}
|
|
||||||
if($matches) break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(is_array($argVal)) {
|
|
||||||
// match any array element
|
|
||||||
$matches = in_array($argMatch, $argVal);
|
|
||||||
} else {
|
|
||||||
// exact string match
|
|
||||||
$matches = $argMatch == $argVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!$matches) break;
|
if(!$matches) break;
|
||||||
}
|
}
|
||||||
if(!$matches) continue; // don't run hook
|
if(!$matches) continue; // don't run hook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($type === 'method' && $when === 'after' && !empty($hook['options']['retMatch'])) {
|
||||||
|
if(!$this->conditionalArgMatch(
|
||||||
|
$hook['options']['retMatch'],
|
||||||
|
$result['return'],
|
||||||
|
$hook['options']['retMatchType'])) continue;
|
||||||
|
}
|
||||||
|
|
||||||
if($this->allowPathHooks && isset($this->pathHooks[$hook['id']])) {
|
if($this->allowPathHooks && isset($this->pathHooks[$hook['id']])) {
|
||||||
$allowRunPathHook = $this->allowRunPathHook($hook['id'], $arguments);
|
$allowRunPathHook = $this->allowRunPathHook($hook['id'], $arguments);
|
||||||
$this->removeHook($object, $hook['id']); // once only
|
$this->removeHook($object, $hook['id']); // once only
|
||||||
@@ -1134,6 +1139,101 @@ class WireHooks {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare argument match
|
||||||
|
*
|
||||||
|
* @param string $argMatch
|
||||||
|
* @return array
|
||||||
|
* @since 3.0.247
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function prepareArgMatch($argMatch) {
|
||||||
|
$argMatch = trim($argMatch, '()');
|
||||||
|
$argMatchType = '';
|
||||||
|
|
||||||
|
list($c1, $c2, $c3) = [ substr($argMatch, 0, 1), substr($argMatch, -1), substr($argMatch, 0, 2) ];
|
||||||
|
|
||||||
|
if($c1 === '<' && $c2 === '>') {
|
||||||
|
// i.e. <WireArray> or <ThisPage|ThatPage>
|
||||||
|
$argMatchType = 'instanceof';
|
||||||
|
$argMatch = trim($argMatch, '<>');
|
||||||
|
|
||||||
|
} else if($c1 === '=' || $c1 === '<' || $c1 === '>' || Selectors::isOperator($c3)) {
|
||||||
|
// selector that starts with operator and translates to "argVal matches argMatch"
|
||||||
|
$argMatch = "___val$argMatch"; // i.e. ___val=something
|
||||||
|
$argMatchType = 'selector';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($argMatchType === 'instanceof') {
|
||||||
|
// ok
|
||||||
|
$argMatch = strpos($argMatch, '|') ? explode('|', $argMatch) : [ $argMatch ];
|
||||||
|
} else if(Selectors::stringHasSelector($argMatch)) {
|
||||||
|
/** @var Selectors $selectors */
|
||||||
|
$selectors = $this->wire->wire(new Selectors());
|
||||||
|
$selectors->init($argMatch);
|
||||||
|
$argMatch = $selectors;
|
||||||
|
$argMatchType = 'selector';
|
||||||
|
} else {
|
||||||
|
$argMatchType = 'equals';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ $argMatch, $argMatchType ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does given value match given match condition?
|
||||||
|
*
|
||||||
|
* @param Selectors|string $argMatch
|
||||||
|
* @param mixed $argVal
|
||||||
|
* @return bool
|
||||||
|
* @since 3.0.247
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function conditionalArgMatch($argMatch, $argVal, $argMatchType) {
|
||||||
|
|
||||||
|
$matches = false;
|
||||||
|
|
||||||
|
if($argMatch instanceof Selectors) {
|
||||||
|
// Selectors object
|
||||||
|
/** @var Selector $s */
|
||||||
|
$s = $argMatch->first();
|
||||||
|
if($s instanceof Selector && $s->field() === '___val') {
|
||||||
|
$o = WireData();
|
||||||
|
$o->set('value', $argVal);
|
||||||
|
$s->field = 'value';
|
||||||
|
$argVal = $o;
|
||||||
|
} else if(is_array($argVal)) {
|
||||||
|
$argVal = count($argVal) && is_string(key($argVal)) ? WireData($argVal) : WireArray($argVal);
|
||||||
|
}
|
||||||
|
if(is_object($argVal)) {
|
||||||
|
$matches = $argMatch->matches($argVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if($argMatchType === 'instanceof') {
|
||||||
|
if(!is_array($argMatch)) $argMatch = [ $argMatch ];
|
||||||
|
foreach($argMatch as $type) {
|
||||||
|
if(isset($this->argMatchTypes[$type])) {
|
||||||
|
$argMatchFunc = $this->argMatchTypes[$type];
|
||||||
|
$matches = $argMatchFunc($argVal);
|
||||||
|
} else {
|
||||||
|
$matches = wireInstanceOf($argVal, $type);
|
||||||
|
}
|
||||||
|
if($matches) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(is_array($argVal)) {
|
||||||
|
// match any array element
|
||||||
|
$matches = in_array($argMatch, $argVal);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// exact match
|
||||||
|
$matches = $argMatch == $argVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matches;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow given path hook to run?
|
* Allow given path hook to run?
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user