2022-09-20 21:59:52 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Rules;
|
|
|
|
|
|
2024-06-10 16:10:14 +02:00
|
|
|
use Closure;
|
2022-09-20 21:59:52 +02:00
|
|
|
use Illuminate\Contracts\Validation\DataAwareRule;
|
2024-06-10 16:10:14 +02:00
|
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
2022-09-20 21:59:52 +02:00
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
2024-06-10 16:10:14 +02:00
|
|
|
class FormPropertyLogicRule implements DataAwareRule, ValidationRule
|
2022-10-02 20:40:10 +02:00
|
|
|
{
|
2024-02-23 11:54:12 +01:00
|
|
|
public const ACTIONS_VALUES = [
|
2022-09-20 21:59:52 +02:00
|
|
|
'show-block',
|
|
|
|
|
'hide-block',
|
|
|
|
|
'make-it-optional',
|
2023-03-22 15:50:29 +01:00
|
|
|
'require-answer',
|
|
|
|
|
'enable-block',
|
2024-02-23 11:54:12 +01:00
|
|
|
'disable-block',
|
2022-09-20 21:59:52 +02:00
|
|
|
];
|
|
|
|
|
|
2025-02-14 23:32:55 +01:00
|
|
|
private static $conditionMappingData = null;
|
2022-09-20 21:59:52 +02:00
|
|
|
|
2025-02-14 23:32:55 +01:00
|
|
|
public static function getConditionMapping()
|
|
|
|
|
{
|
|
|
|
|
return config('opnform.condition_mapping');
|
|
|
|
|
}
|
2022-09-20 21:59:52 +02:00
|
|
|
|
|
|
|
|
private $isConditionCorrect = true;
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
private $isActionCorrect = true;
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2023-01-25 16:10:33 +01:00
|
|
|
private $conditionErrors = [];
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
private $field = [];
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
private $data = [];
|
|
|
|
|
|
2024-12-16 17:17:29 +01:00
|
|
|
private $operator = '';
|
|
|
|
|
|
2024-02-10 13:20:41 +01:00
|
|
|
private function checkBaseCondition($condition)
|
2022-10-02 20:40:10 +02:00
|
|
|
{
|
2024-03-19 15:27:21 +01:00
|
|
|
if (!isset($condition['value'])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'missing condition body';
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:27:21 +01:00
|
|
|
if (!isset($condition['value']['property_meta'])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'missing condition property';
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:27:21 +01:00
|
|
|
if (!isset($condition['value']['property_meta']['type'])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'missing condition property type';
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2024-02-10 13:20:41 +01:00
|
|
|
|
2024-03-19 15:27:21 +01:00
|
|
|
if (!isset($condition['value']['operator'])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'missing condition operator';
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-02 20:40:10 +02:00
|
|
|
$typeField = $condition['value']['property_meta']['type'];
|
|
|
|
|
$operator = $condition['value']['operator'];
|
2024-12-16 17:17:29 +01:00
|
|
|
$this->operator = $operator;
|
2022-09-20 21:59:52 +02:00
|
|
|
|
2025-02-14 23:32:55 +01:00
|
|
|
if (!isset(self::getConditionMapping()[$typeField])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'configuration not found for condition type';
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-14 23:32:55 +01:00
|
|
|
if (!isset(self::getConditionMapping()[$typeField]['comparators'][$operator])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'configuration not found for condition operator';
|
2025-02-19 15:11:27 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2025-02-19 15:11:27 +01:00
|
|
|
$comparatorDef = self::getConditionMapping()[$typeField]['comparators'][$operator];
|
|
|
|
|
$needsValue = !empty((array)$comparatorDef);
|
|
|
|
|
|
|
|
|
|
if ($needsValue && !isset($condition['value']['value'])) {
|
|
|
|
|
$this->isConditionCorrect = false;
|
|
|
|
|
$this->conditionErrors[] = 'missing condition value';
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-19 15:11:27 +01:00
|
|
|
if ($needsValue) {
|
|
|
|
|
$type = $comparatorDef['expected_type'] ?? null;
|
|
|
|
|
$value = $condition['value']['value'];
|
2022-09-20 21:59:52 +02:00
|
|
|
|
2025-02-19 15:11:27 +01:00
|
|
|
if (is_array($type)) {
|
|
|
|
|
$foundCorrectType = false;
|
|
|
|
|
foreach ($type as $subtype) {
|
|
|
|
|
if ($this->valueHasCorrectType($subtype, $value)) {
|
|
|
|
|
$foundCorrectType = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!$foundCorrectType) {
|
|
|
|
|
$this->isConditionCorrect = false;
|
|
|
|
|
$this->conditionErrors[] = 'wrong type of condition value';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!$this->valueHasCorrectType($type, $value)) {
|
|
|
|
|
$this->isConditionCorrect = false;
|
|
|
|
|
$this->conditionErrors[] = 'wrong type of condition value';
|
2022-10-02 20:40:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function valueHasCorrectType($type, $value)
|
|
|
|
|
{
|
2025-02-14 23:32:55 +01:00
|
|
|
if ($type === 'string' && isset(self::getConditionMapping()[$this->field['type']]['comparators'][$this->operator]['format'])) {
|
|
|
|
|
$format = self::getConditionMapping()[$this->field['type']]['comparators'][$this->operator]['format'];
|
2024-12-16 17:17:29 +01:00
|
|
|
if ($format['type'] === 'regex') {
|
|
|
|
|
try {
|
|
|
|
|
preg_match('/' . $value . '/', '');
|
|
|
|
|
return true;
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->conditionErrors[] = 'invalid regex pattern';
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
if (
|
|
|
|
|
($type === 'string' && gettype($value) !== 'string') ||
|
2024-03-19 15:27:21 +01:00
|
|
|
($type === 'boolean' && !is_bool($value)) ||
|
|
|
|
|
($type === 'number' && !is_numeric($value)) ||
|
|
|
|
|
($type === 'object' && !is_array($value))
|
2022-09-20 21:59:52 +02:00
|
|
|
) {
|
2022-10-02 20:40:10 +02:00
|
|
|
return false;
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-10-02 20:40:10 +02:00
|
|
|
return true;
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-10 13:20:41 +01:00
|
|
|
private function checkConditions($conditions)
|
2022-10-02 20:40:10 +02:00
|
|
|
{
|
2023-03-15 18:11:25 +01:00
|
|
|
if (array_key_exists('operatorIdentifier', $conditions)) {
|
2022-09-20 21:59:52 +02:00
|
|
|
if (($conditions['operatorIdentifier'] !== 'and') && ($conditions['operatorIdentifier'] !== 'or')) {
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'missing operator';
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($conditions['operatorIdentifier']['children'])) {
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'extra condition';
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 15:27:21 +01:00
|
|
|
if (!is_array($conditions['children'])) {
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->conditionErrors[] = 'wrong sub-condition type';
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->isConditionCorrect = false;
|
2024-02-23 11:54:12 +01:00
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($conditions['children'] as &$child) {
|
|
|
|
|
$this->checkConditions($child);
|
|
|
|
|
}
|
2024-02-23 11:54:12 +01:00
|
|
|
} elseif (isset($conditions['identifier'])) {
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->checkBaseCondition($conditions);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-10 13:20:41 +01:00
|
|
|
private function checkActions($actions)
|
2022-10-02 20:40:10 +02:00
|
|
|
{
|
2024-02-10 13:20:41 +01:00
|
|
|
if (is_array($actions) && count($actions) > 0) {
|
2024-02-23 11:54:12 +01:00
|
|
|
foreach ($actions as $val) {
|
2024-03-19 15:27:21 +01:00
|
|
|
if (
|
|
|
|
|
!in_array($val, static::ACTIONS_VALUES) ||
|
|
|
|
|
(in_array($this->field['type'], ['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image']) && !in_array($val, ['hide-block', 'show-block'])) ||
|
|
|
|
|
(isset($this->field['hidden']) && $this->field['hidden'] && !in_array($val, ['show-block', 'require-answer'])) ||
|
|
|
|
|
(isset($this->field['required']) && $this->field['required'] && !in_array($val, ['make-it-optional', 'hide-block', 'disable-block'])) ||
|
|
|
|
|
(isset($this->field['disabled']) && $this->field['disabled'] && !in_array($val, ['enable-block', 'require-answer', 'make-it-optional']))
|
2022-09-20 21:59:52 +02:00
|
|
|
) {
|
|
|
|
|
$this->isActionCorrect = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-30 13:28:52 +02:00
|
|
|
} else {
|
|
|
|
|
$this->isActionCorrect = false;
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine if the validation rule passes.
|
|
|
|
|
*
|
|
|
|
|
* @param string $attribute
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2024-02-10 13:20:41 +01:00
|
|
|
public function passes($attribute, $value)
|
2022-10-02 20:40:10 +02:00
|
|
|
{
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->setProperty($attribute);
|
2024-02-23 11:54:12 +01:00
|
|
|
if (isset($value['conditions'])) {
|
|
|
|
|
$this->checkConditions($value['conditions']);
|
2023-08-30 13:28:52 +02:00
|
|
|
$this->checkActions($value['actions'] ?? null);
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
2023-08-30 13:28:52 +02:00
|
|
|
|
2024-02-23 11:54:12 +01:00
|
|
|
return $this->isConditionCorrect && $this->isActionCorrect;
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-10 16:10:14 +02:00
|
|
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
|
|
|
|
{
|
|
|
|
|
if (!$this->passes($attribute, $value)) {
|
|
|
|
|
$fail($this->message());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-20 21:59:52 +02:00
|
|
|
/**
|
|
|
|
|
* Get the validation error message.
|
|
|
|
|
*/
|
2024-02-10 13:20:41 +01:00
|
|
|
public function message()
|
2022-10-02 20:40:10 +02:00
|
|
|
{
|
2023-01-25 16:10:33 +01:00
|
|
|
$message = null;
|
2024-03-19 15:27:21 +01:00
|
|
|
if (!$this->isConditionCorrect) {
|
|
|
|
|
$message = 'The logic conditions for ' . $this->field['name'] . ' are not complete.';
|
|
|
|
|
} elseif (!$this->isActionCorrect) {
|
|
|
|
|
$message = 'The logic actions for ' . $this->field['name'] . ' are not valid.';
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
2023-01-25 16:10:33 +01:00
|
|
|
if (count($this->conditionErrors) > 0) {
|
2024-03-19 15:27:21 +01:00
|
|
|
return $message . ' Error detail(s): ' . implode(', ', $this->conditionErrors);
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
2023-01-25 16:10:33 +01:00
|
|
|
|
|
|
|
|
return $message;
|
2022-09-20 21:59:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the data under validation.
|
|
|
|
|
*
|
|
|
|
|
* @param array $data
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
|
|
|
|
public function setData($data)
|
|
|
|
|
{
|
|
|
|
|
$this->data = $data;
|
2023-01-25 16:10:33 +01:00
|
|
|
$this->isConditionCorrect = true;
|
|
|
|
|
$this->isActionCorrect = true;
|
|
|
|
|
$this->conditionErrors = [];
|
2022-09-20 21:59:52 +02:00
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function setProperty(string $attributeKey)
|
|
|
|
|
{
|
2022-10-02 20:40:10 +02:00
|
|
|
$attributeKey = Str::of($attributeKey)->replace('.logic', '')->toString();
|
2022-09-20 21:59:52 +02:00
|
|
|
$this->field = \Arr::get($this->data, $attributeKey);
|
|
|
|
|
}
|
|
|
|
|
}
|