diff --git a/application/Espo/Core/Formula/Argument.php b/application/Espo/Core/Formula/Argument.php index 0e5491f6c5..3231a0f51a 100644 --- a/application/Espo/Core/Formula/Argument.php +++ b/application/Espo/Core/Formula/Argument.php @@ -85,7 +85,9 @@ class Argument implements Evaluatable } if ($this->data instanceof Variable) { - return new ArgumentList([$this->data->getName()]); + $value = new Value($this->data->getName()); + + return new ArgumentList([$value]); } if ($this->data instanceof Attribute) { diff --git a/application/Espo/Core/Formula/FuncVariablesAware.php b/application/Espo/Core/Formula/FuncVariablesAware.php new file mode 100644 index 0000000000..03c0371314 --- /dev/null +++ b/application/Espo/Core/Formula/FuncVariablesAware.php @@ -0,0 +1,47 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Formula; + +use Espo\Core\Formula\Exceptions\Error; + +/** + * A function aware of variables. + * + * @since 8.3.0 + */ +interface FuncVariablesAware +{ + /** + * Process. + * + * @throws Error + */ + public function process(EvaluatedArgumentList $arguments, Variables $variables): mixed; +} diff --git a/application/Espo/Core/Formula/FunctionFactory.php b/application/Espo/Core/Formula/FunctionFactory.php index af7122ff6a..e10b80495d 100644 --- a/application/Espo/Core/Formula/FunctionFactory.php +++ b/application/Espo/Core/Formula/FunctionFactory.php @@ -30,7 +30,6 @@ namespace Espo\Core\Formula; use Espo\Core\Formula\Exceptions\UnknownFunction; - use Espo\Core\Formula\Functions\Base; use Espo\Core\Formula\Functions\BaseFunction; use Espo\ORM\Entity; @@ -59,8 +58,12 @@ class FunctionFactory /** * @throws UnknownFunction */ - public function create(string $name, ?Entity $entity = null, ?stdClass $variables = null): Func|BaseFunction|Base - { + public function create( + string $name, + ?Entity $entity = null, + ?stdClass $variables = null + ): Func|FuncVariablesAware|BaseFunction|Base { + if ($this->classNameMap && array_key_exists($name, $this->classNameMap)) { $className = $this->classNameMap[$name]; } @@ -77,7 +80,7 @@ class FunctionFactory $typeName = implode('\\', $arr); - /** @var class-string $className */ + /** @var class-string $className */ $className = 'Espo\\Core\\Formula\\Functions\\' . $typeName . 'Type'; } @@ -85,7 +88,12 @@ class FunctionFactory throw new UnknownFunction("Unknown function: " . $name); } - if ((new ReflectionClass($className))->implementsInterface(Func::class)) { + $class = new ReflectionClass($className); + + if ( + $class->implementsInterface(Func::class) || + $class->implementsInterface(FuncVariablesAware::class) + ) { return $this->injectableFactory->create($className); } diff --git a/application/Espo/Core/Formula/Functions/VariableType.php b/application/Espo/Core/Formula/Functions/VariableType.php index e5aad507df..6ee21a9f52 100644 --- a/application/Espo/Core/Formula/Functions/VariableType.php +++ b/application/Espo/Core/Formula/Functions/VariableType.php @@ -29,18 +29,20 @@ namespace Espo\Core\Formula\Functions; +use Espo\Core\Formula\EvaluatedArgumentList; use Espo\Core\Formula\Exceptions\Error; -use Espo\Core\Formula\ArgumentList; +use Espo\Core\Formula\FuncVariablesAware; +use Espo\Core\Formula\Variables; -class VariableType extends BaseFunction +class VariableType implements FuncVariablesAware { - public function process(ArgumentList $args) + public function process(EvaluatedArgumentList $arguments, Variables $variables): mixed { - if (!count($args)) { + if (!count($arguments)) { throw new Error("No variable name."); } - $name = $args[0]->getData(); + $name = $arguments[0]; if (!is_string($name)) { throw new Error("Bad variable name."); @@ -50,10 +52,6 @@ class VariableType extends BaseFunction throw new Error("Empty variable name."); } - if (!property_exists($this->getVariables(), $name)) { - return null; - } - - return $this->getVariables()->$name; + return $variables->tryGet($name); } } diff --git a/application/Espo/Core/Formula/Processor.php b/application/Espo/Core/Formula/Processor.php index bd22fc0a7b..50486dc945 100644 --- a/application/Espo/Core/Formula/Processor.php +++ b/application/Espo/Core/Formula/Processor.php @@ -94,20 +94,8 @@ class Processor $function = $this->functionFactory->create($item->getType(), $this->entity, $this->variables); - if ($function instanceof Func) { - $evaluatedArguments = array_map( - fn($item) => $this->process($item), - iterator_to_array($item->getArgumentList()) - ); - - try { - return $function->process(new EvaluatedArgumentList($evaluatedArguments)); - } - catch (TooFewArguments|BadArgumentType|BadArgumentValue $e) { - $message = sprintf('Function %s; %s', $item->getType(), $e->getLogMessage()); - - throw new Error($message); - } + if ($function instanceof Func || $function instanceof FuncVariablesAware) { + return $this->processFunc($item, $function); } if ($function instanceof DeprecatedBaseFunction) { @@ -156,6 +144,7 @@ class Processor /** * @return mixed[] * @throws Error + * @throws ExecutionException */ private function processList(ArgumentList $args): array { @@ -167,4 +156,33 @@ class Processor return $list; } + + /** + * @throws Error + * @throws ExecutionException + */ + private function processFunc(Argument $item, Func|FuncVariablesAware $function): mixed + { + $rawEvaluatedArguments = array_map( + fn ($item) => $this->process($item), + iterator_to_array($item->getArgumentList()) + ); + + try { + $evaluatedArguments = new EvaluatedArgumentList($rawEvaluatedArguments); + + if ($function instanceof FuncVariablesAware) { + $variables = new Variables($this->variables ?? (object) []); + + return $function->process($evaluatedArguments, $variables); + } + + return $function->process($evaluatedArguments); + } + catch (TooFewArguments|BadArgumentType|BadArgumentValue $e) { + $message = sprintf('Function %s; %s', $item->getType(), $e->getLogMessage()); + + throw new Error($message); + } + } } diff --git a/application/Espo/Core/Formula/Variables.php b/application/Espo/Core/Formula/Variables.php new file mode 100644 index 0000000000..038c1eec37 --- /dev/null +++ b/application/Espo/Core/Formula/Variables.php @@ -0,0 +1,67 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Formula; + +use Espo\Core\Formula\Exceptions\Error; +use stdClass; + +class Variables +{ + public function __construct( + private stdClass $variables + ) {} + + public function has(string $name): bool + { + return property_exists($this->variables, $name); + } + + /** + * @throws Error + */ + public function get(string $name): mixed + { + if (!property_exists($this->variables, $name)) { + throw new Error("Variable $name is not defined."); + } + + return $this->variables->$name; + } + + public function tryGet(string $name): mixed + { + return $this->variables->$name ?? null; + } + + public function set(string $name, mixed $value): void + { + $this->variables->$name = $value; + } +}