| @@ -1,36 +1,67 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace FluentForm\App\Services; |
| 4 | 4 | |
| 5 | +use FluentForm\App\Helpers\Helper; | |
| 5 | 6 | use FluentForm\App\Helpers\Str; |
| 7 | +use FluentForm\App\Services\FormBuilder\EditorShortcodeParser; | |
| 6 | 8 | use FluentForm\Framework\Helpers\ArrayHelper as Arr; |
| 7 | 9 | |
| 8 | 10 | class ConditionAssesor |
| 9 | 11 | { |
| 10 | - public static function evaluate(&$field, &$inputs) | |
| 12 | + public static function evaluate(&$field, &$inputs, $form = null) | |
| 11 | 13 | { |
| 12 | 14 | $status = Arr::get($field, 'conditionals.status'); |
| 15 | + if (!$status) { | |
| 16 | + return true; | |
| 17 | + } | |
| 13 | 18 | |
| 14 | - $conditionals = $status ? Arr::get($field, 'conditionals.conditions') : false; | |
| 19 | + $type = Arr::get($field, 'conditionals.type', 'any'); | |
| 15 | 20 | |
| 21 | + // Handle group conditions | |
| 22 | + if ($type === 'group' && $conditionGroups = Arr::get($field, 'conditionals.condition_groups')) { | |
| 23 | + return self::evaluateGroupConditions($conditionGroups, $inputs, $form); | |
| 24 | + } | |
| 16 | 25 | |
| 26 | + // Handle 'any', 'all' conditions | |
| 27 | + if ($type !== 'group' && $conditions = Arr::get($field, 'conditionals.conditions')) { | |
| 28 | + return self::evaluateConditions($conditions, $inputs, $type, $form); | |
| 29 | + } | |
| 30 | + return true; | |
| 31 | + } | |
| 32 | + | |
| 33 | + private static function evaluateGroupConditions($conditionGroups, &$inputs, $form = null) | |
| 34 | + { | |
| 35 | + $hasGroupConditionsMet = true; | |
| 36 | + foreach ($conditionGroups as $group) { | |
| 37 | + if ($conditions = Arr::get($group, 'rules')) { | |
| 38 | + $hasGroupConditionsMet = self::evaluateConditions($conditions, $inputs, 'all', $form); | |
| 39 | + if ($hasGroupConditionsMet) { | |
| 40 | + return true; | |
| 41 | + } | |
| 42 | + } | |
| 43 | + } | |
| 44 | + return $hasGroupConditionsMet; | |
| 45 | + } | |
| 46 | + | |
| 47 | + private static function evaluateConditions($conditions, &$inputs, $type, $form = null) | |
| 48 | + { | |
| 17 | 49 | $hasConditionMet = true; |
| 18 | 50 | |
| 19 | - if ($conditionals) { | |
| 20 | - $toMatch = Arr::get($field, 'conditionals.type'); | |
| 21 | - | |
| 22 | - foreach ($conditionals as $conditional) { | |
| 51 | + foreach ($conditions as $condition) { | |
| 52 | + if (!Arr::get($condition, 'field') || !Arr::get($condition, 'operator')) { | |
| 53 | + continue; | |
| 54 | + } | |
| 23 | 55 | |
| 24 | - $hasConditionMet = static::assess($conditional, $inputs); | |
| 56 | + $hasConditionMet = static::assess($condition, $inputs, $form); | |
| 25 | 57 | |
| 26 | - if($hasConditionMet && $toMatch == 'any') { | |
| 27 | - return true; | |
| 28 | - } | |
| 58 | + if ($hasConditionMet && $type == 'any') { | |
| 59 | + return true; | |
| 60 | + } | |
| 29 | 61 | |
| 30 | - if ($toMatch === 'all' && !$hasConditionMet) { | |
| 31 | - return false; | |
| 32 | - } | |
| 62 | + if ($type === 'all' && !$hasConditionMet) { | |
| 63 | + return false; | |
| 33 | 64 | } |
| 34 | 65 | } |
| 35 | 66 | |
| 36 | 67 | return $hasConditionMet; |
| @@ -35,73 +66,160 @@ | ||
| 35 | 66 | |
| 36 | 67 | return $hasConditionMet; |
| 37 | 68 | } |
| 38 | 69 | |
| 39 | - public static function assess(&$conditional, &$inputs) | |
| 70 | + public static function assess(&$conditional, &$inputs, $form = null) | |
| 40 | 71 | { |
| 41 | 72 | if ($conditional['field']) { |
| 42 | - $inputValue = Arr::get($inputs, $conditional['field']); | |
| 73 | + $accessor = rtrim(str_replace(['[', ']', '*'], ['.'], $conditional['field']), '.'); | |
| 43 | 74 | |
| 75 | + if (!Arr::has($inputs, $accessor)) { | |
| 76 | + return false; | |
| 77 | + } | |
| 78 | + $inputValue = Arr::get($inputs, $accessor); | |
| 79 | + | |
| 80 | + if ($numericFormatter = Arr::get($conditional, 'numeric_formatter')) { | |
| 81 | + $inputValue = Helper::getNumericValue($inputValue, $numericFormatter); | |
| 82 | + } | |
| 83 | + $conditionValue = Arr::get($conditional, 'value'); | |
| 84 | + $isArrayAcceptable = in_array($conditional['operator'], ['=', '!=']); | |
| 85 | + if (!empty($conditionValue) && is_string($conditionValue)) { | |
| 86 | + $conditionValue = self::processSmartCodesInValue($conditionValue, $inputs, $form, $isArrayAcceptable); | |
| 87 | + } | |
| 88 | + | |
| 89 | + $conditionValue = is_null($conditionValue) ? '' : $conditionValue; | |
| 90 | + | |
| 44 | 91 | switch ($conditional['operator']) { |
| 45 | 92 | case '=': |
| 46 | - if(is_array($inputValue)) { | |
| 47 | - return in_array($conditional['value'], $inputValue); | |
| 93 | + if (is_array($inputValue) && is_array($conditionValue)) { | |
| 94 | + $flatInput = Arr::flatten($inputValue); | |
| 95 | + $flatCondition = Arr::flatten($conditionValue); | |
| 96 | + sort($flatInput); | |
| 97 | + sort($flatCondition); | |
| 98 | + return $flatInput == $flatCondition; | |
| 48 | 99 | } |
| 49 | - return $inputValue == $conditional['value']; | |
| 50 | - break; | |
| 100 | + | |
| 101 | + if (is_array($conditionValue)) { | |
| 102 | + return in_array($inputValue, Arr::flatten($conditionValue)); | |
| 103 | + } | |
| 104 | + if (is_array($inputValue)) { | |
| 105 | + return in_array($conditionValue, Arr::flatten($inputValue)); | |
| 106 | + } | |
| 107 | + return $inputValue == $conditionValue; | |
| 51 | 108 | case '!=': |
| 52 | - if(is_array($inputValue)) { | |
| 53 | - return !in_array($conditional['value'], $inputValue); | |
| 109 | + if (is_array($inputValue) && is_array($conditionValue)) { | |
| 110 | + return count(array_intersect(Arr::flatten($inputValue), Arr::flatten($conditionValue))) == 0; | |
| 54 | 111 | } |
| 55 | - return $inputValue != $conditional['value']; | |
| 56 | - break; | |
| 112 | + if (is_array($conditionValue)) { | |
| 113 | + return !in_array($inputValue, Arr::flatten($conditionValue)); | |
| 114 | + } | |
| 115 | + if (is_array($inputValue)) { | |
| 116 | + return !in_array($conditionValue, Arr::flatten($inputValue)); | |
| 117 | + } | |
| 118 | + return $inputValue != $conditionValue; | |
| 57 | 119 | case '>': |
| 58 | - return $inputValue > $conditional['value']; | |
| 59 | - break; | |
| 120 | + return $inputValue > $conditionValue; | |
| 60 | 121 | case '<': |
| 61 | - return $inputValue < $conditional['value']; | |
| 62 | - break; | |
| 122 | + return $inputValue < $conditionValue; | |
| 63 | 123 | case '>=': |
| 64 | - return $inputValue >= $conditional['value']; | |
| 65 | - break; | |
| 124 | + return $inputValue >= $conditionValue; | |
| 66 | 125 | case '<=': |
| 67 | - return $inputValue <= $conditional['value']; | |
| 68 | - break; | |
| 126 | + return $inputValue <= $conditionValue; | |
| 69 | 127 | case 'startsWith': |
| 70 | - return Str::startsWith($inputValue, $conditional['value']); | |
| 71 | - break; | |
| 128 | + return Str::startsWith($inputValue, $conditionValue); | |
| 72 | 129 | case 'endsWith': |
| 73 | - return Str::endsWith($inputValue, $conditional['value']); | |
| 74 | - break; | |
| 130 | + return Str::endsWith($inputValue, $conditionValue); | |
| 75 | 131 | case 'contains': |
| 76 | - return Str::contains($inputValue, $conditional['value']); | |
| 77 | - break; | |
| 132 | + return Str::contains($inputValue, $conditionValue); | |
| 78 | 133 | case 'doNotContains': |
| 79 | - return !Str::contains($inputValue, $conditional['value']); | |
| 80 | - break; | |
| 134 | + return !Str::contains($inputValue, $conditionValue); | |
| 81 | 135 | case 'length_equal': |
| 82 | - if(is_array($inputValue)) { | |
| 83 | - return count($inputValue) == $conditional['value']; | |
| 136 | + if (is_array($inputValue)) { | |
| 137 | + return count($inputValue) == $conditionValue; | |
| 84 | 138 | } |
| 85 | - $inputValue = strval($inputValue); | |
| 86 | - return strlen($inputValue) == $conditional['value']; | |
| 87 | - break; | |
| 139 | + $inputValue = (string)$inputValue; | |
| 140 | + return strlen($inputValue) == $conditionValue; | |
| 88 | 141 | case 'length_less_than': |
| 89 | - if(is_array($inputValue)) { | |
| 90 | - return count($inputValue) < $conditional['value']; | |
| 142 | + if (is_array($inputValue)) { | |
| 143 | + return count($inputValue) < $conditionValue; | |
| 91 | 144 | } |
| 92 | - $inputValue = strval($inputValue); | |
| 93 | - return strlen($inputValue) < $conditional['value']; | |
| 94 | - break; | |
| 145 | + $inputValue = (string)$inputValue; | |
| 146 | + return strlen($inputValue) < $conditionValue; | |
| 95 | 147 | case 'length_greater_than': |
| 96 | - if(is_array($inputValue)) { | |
| 97 | - return count($inputValue) > $conditional['value']; | |
| 148 | + if (is_array($inputValue)) { | |
| 149 | + return count($inputValue) > $conditionValue; | |
| 98 | 150 | } |
| 99 | - $inputValue = strval($inputValue); | |
| 100 | - return strlen($inputValue) > $conditional['value']; | |
| 101 | - break; | |
| 151 | + $inputValue = (string)$inputValue; | |
| 152 | + return strlen($inputValue) > $conditionValue; | |
| 153 | + case 'test_regex': | |
| 154 | + if (is_array($inputValue)) { | |
| 155 | + $inputValue = implode(' ', $inputValue); | |
| 156 | + } | |
| 157 | + $pattern = '/' . $conditionValue . '/'; | |
| 158 | + $result = @preg_match($pattern, $inputValue); | |
| 159 | + if ($result === false) { | |
| 160 | + // Invalid regex pattern, handle gracefully | |
| 161 | + return false; | |
| 162 | + } | |
| 163 | + return (bool) $result; | |
| 102 | 164 | } |
| 103 | 165 | } |
| 104 | 166 | |
| 105 | 167 | return false; |
| 168 | + } | |
| 169 | + | |
| 170 | + private static function processSmartCodesInValue($value, &$inputs, $form = null, $isArrayAcceptable = true) | |
| 171 | + { | |
| 172 | + if (strpos($value, '{') === false) { | |
| 173 | + return $value; | |
| 174 | + } | |
| 175 | + | |
| 176 | + if (preg_match('/^{inputs\.([^}]+)}$/', $value, $inputMatches)) { | |
| 177 | + $fieldName = $inputMatches[1]; | |
| 178 | + $fieldKey = str_replace(['[', ']'], ['.', ''], $fieldName); | |
| 179 | + | |
| 180 | + $resolvedValue = Arr::get($inputs, $fieldKey); | |
| 181 | + | |
| 182 | + if ($resolvedValue === null && $fieldKey !== $fieldName) { | |
| 183 | + $resolvedValue = Arr::get($inputs, $fieldName); | |
| 184 | + } | |
| 185 | + | |
| 186 | + // Return array if it's an array | |
| 187 | + if (is_array($resolvedValue) && $isArrayAcceptable) { | |
| 188 | + return $resolvedValue; | |
| 189 | + } | |
| 190 | + } | |
| 191 | + | |
| 192 | + try { | |
| 193 | + $processedValue = preg_replace_callback('/{+(.*?)}/', function ($matches) use ($inputs, $form) { | |
| 194 | + $smartCode = $matches[1]; | |
| 195 | + | |
| 196 | + if (false !== strpos($smartCode, 'inputs.')) { | |
| 197 | + $fieldName = substr($smartCode, strlen('inputs.')); | |
| 198 | + | |
| 199 | + $fieldKey = str_replace(['[', ']'], ['.', ''], $fieldName); | |
| 200 | + | |
| 201 | + $value = Arr::get($inputs, $fieldKey, ''); | |
| 202 | + | |
| 203 | + if ($value === '' && $fieldKey !== $fieldName) { | |
| 204 | + $value = Arr::get($inputs, $fieldName, ''); | |
| 205 | + } | |
| 206 | + | |
| 207 | + if (is_array($value)) { | |
| 208 | + $value = fluentImplodeRecursive(', ', $value); | |
| 209 | + } | |
| 210 | + | |
| 211 | + return $value !== null && $value !== '' ? $value : ''; | |
| 212 | + } | |
| 213 | + | |
| 214 | + // @todo Support general shortcodes in future | |
| 215 | + | |
| 216 | + // Always return the original value if we don't have a match | |
| 217 | + return $matches[0]; | |
| 218 | + }, $value); | |
| 219 | + | |
| 220 | + return $processedValue; | |
| 221 | + } catch (\Exception $e) { | |
| 222 | + return $value; | |
| 223 | + } | |
| 106 | 224 | } |
| 107 | 225 | } |