isConditionCorrect = false; $this->conditionErrors[] = 'missing condition body'; return; } if (!isset($condition['value']['property_meta'])) { $this->isConditionCorrect = false; $this->conditionErrors[] = 'missing condition property'; return; } if (!isset($condition['value']['property_meta']['type'])) { $this->isConditionCorrect = false; $this->conditionErrors[] = 'missing condition property type'; return; } if (!isset($condition['value']['operator'])) { $this->isConditionCorrect = false; $this->conditionErrors[] = 'missing condition operator'; return; } $typeField = $condition['value']['property_meta']['type']; $operator = $condition['value']['operator']; $this->operator = $operator; if (!isset(self::getConditionMapping()[$typeField])) { $this->isConditionCorrect = false; $this->conditionErrors[] = 'configuration not found for condition type'; return; } if (!isset(self::getConditionMapping()[$typeField]['comparators'][$operator])) { $this->isConditionCorrect = false; $this->conditionErrors[] = 'configuration not found for condition operator'; return; } $comparatorDef = self::getConditionMapping()[$typeField]['comparators'][$operator]; $needsValue = !empty((array)$comparatorDef); if ($needsValue && !isset($condition['value']['value'])) { $this->isConditionCorrect = false; $this->conditionErrors[] = 'missing condition value'; return; } if ($needsValue) { $type = $comparatorDef['expected_type'] ?? null; $value = $condition['value']['value']; 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'; } } } } private function valueHasCorrectType($type, $value) { if ($type === 'string' && isset(self::getConditionMapping()[$this->field['type']]['comparators'][$this->operator]['format'])) { $format = self::getConditionMapping()[$this->field['type']]['comparators'][$this->operator]['format']; if ($format['type'] === 'regex') { try { preg_match('/' . $value . '/', ''); return true; } catch (\Exception $e) { $this->conditionErrors[] = 'invalid regex pattern'; return false; } } } if ( ($type === 'string' && gettype($value) !== 'string') || ($type === 'boolean' && !is_bool($value)) || ($type === 'number' && !is_numeric($value)) || ($type === 'object' && !is_array($value)) ) { return false; } return true; } private function checkConditions($conditions) { if (array_key_exists('operatorIdentifier', $conditions)) { if (($conditions['operatorIdentifier'] !== 'and') && ($conditions['operatorIdentifier'] !== 'or')) { $this->conditionErrors[] = 'missing operator'; $this->isConditionCorrect = false; return; } if (isset($conditions['operatorIdentifier']['children'])) { $this->conditionErrors[] = 'extra condition'; $this->isConditionCorrect = false; return; } if (!is_array($conditions['children'])) { $this->conditionErrors[] = 'wrong sub-condition type'; $this->isConditionCorrect = false; return; } foreach ($conditions['children'] as &$child) { $this->checkConditions($child); } } elseif (isset($conditions['identifier'])) { $this->checkBaseCondition($conditions); } } private function checkActions($actions) { if (is_array($actions) && count($actions) > 0) { foreach ($actions as $val) { 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'])) ) { $this->isActionCorrect = false; break; } } } else { $this->isActionCorrect = false; } } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { $this->setProperty($attribute); if (isset($value['conditions'])) { $this->checkConditions($value['conditions']); $this->checkActions($value['actions'] ?? null); } return $this->isConditionCorrect && $this->isActionCorrect; } public function validate(string $attribute, mixed $value, Closure $fail): void { if (!$this->passes($attribute, $value)) { $fail($this->message()); } } /** * Get the validation error message. */ public function message() { $message = null; 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.'; } if (count($this->conditionErrors) > 0) { return $message . ' Error detail(s): ' . implode(', ', $this->conditionErrors); } return $message; } /** * Set the data under validation. * * @param array $data * @return $this */ public function setData($data) { $this->data = $data; $this->isConditionCorrect = true; $this->isActionCorrect = true; $this->conditionErrors = []; return $this; } private function setProperty(string $attributeKey) { $attributeKey = Str::of($attributeKey)->replace('.logic', '')->toString(); $this->field = \Arr::get($this->data, $attributeKey); } }