check(); } /** * Prepare the data for validation. * * @return void */ protected function prepareForValidation(): void { $this->configModel = PageConfig::query()->whereKey($this->input('config_id'))->firstOrFail(); } /** * Get the validation rules that apply to the request. * * @return array * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh * @phpcs:disable Generic.Metrics.NestingLevel.TooHigh */ public function rules(): array { // phpcs:enable $rules = []; foreach ($this->configModel->config as $input) { $rules = array_merge($rules, $this->recursiveRules($input, $this->fieldName)); } //end foreach return $rules; } private function recursiveRules(&$input, $prefix): array { $rules = []; $ruleKey = "$prefix.$input[key]"; switch ($input['type']) { case DynamicFieldTypes::string->value: $rules = array_merge($rules, [ $ruleKey => $this->compileValidate(DynamicFieldTypes::string->getValidation(), $input) ]); break; case DynamicFieldTypes::wysiwyg->value: $mergeRules = $this->compileValidate(DynamicFieldTypes::wysiwyg->getValidation(), $input); $mergeRules = collect($mergeRules) ->map(function ($rule) { // TODO: check pour utiliser la rule wysiwyg length. if (\strpos($rule, 'max:') !== false) { $max = \intval(str_replace('max:', '', $rule)); return function ($attribute, $value, $fail) use ($max) { if (\strlen(\strip_tags($value)) > $max) { $fail("$attribute ne doit pas excéder $max caractères"); } }; } return $rule; })->all(); $rules = array_merge($rules, [ $ruleKey => $mergeRules ]); break; case DynamicFieldTypes::number->value: $rules = array_merge($rules, [ $ruleKey => $this->compileValidate(DynamicFieldTypes::number->getValidation(), $input) ]); break; case DynamicFieldTypes::boolean->value: $all = $this->all(); $modified = $this->castBooleanFields($all); $this->merge($modified); $rules = array_merge($rules, [ $ruleKey => $this->compileValidate(DynamicFieldTypes::boolean->getValidation(), $input) ]); break; case DynamicFieldTypes::url->value: $rules = array_merge($rules, [ $ruleKey => array_merge(explode("|", DynamicFieldTypes::url->getValidation()), [ // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed function ($attribute, $value, $fail) { try { ToolboxHelper::assertNoInternalUrls($attribute, $value); ToolboxHelper::assertNoInternalBoUrls($attribute, $value); } catch (ValidationException $e) { $fail(collect($e->errors())->first()[0]); } } ]), ]); break; case DynamicFieldTypes::email->value: $rules = array_merge($rules, [ $ruleKey => $this->compileValidate(DynamicFieldTypes::email->getValidation(), $input) ]); break; case DynamicFieldTypes::image->value: $imgRules = $this->compileValidate(DynamicFieldTypes::image->getValidation(), $input); $rules = array_merge($rules, [ $ruleKey => $this->pictureRules(implode("|", $imgRules)), ]); break; case DynamicFieldTypes::list->value: $rules = array_merge($rules, [ "$ruleKey.*.order" => "required|numeric|min:1", $ruleKey => $this->compileValidate(DynamicFieldTypes::list->getValidation(), $input), ]); foreach ($input['children'] as $key => $children) { $rules = array_merge($rules, $this->recursiveRules($children, $ruleKey . ".*")); } break; case DynamicFieldTypes::date->value: $rules = array_merge($rules, [ $ruleKey => $this->compileValidate(DynamicFieldTypes::date->getValidation(), $input) ]); break; case DynamicFieldTypes::color->value: $rules = array_merge($rules, [ $ruleKey => $this->compileValidate(DynamicFieldTypes::color->getValidation(), $input) ]); break; default: throw new \UnexpectedValueException("Unrecognized dynamic type: " . ($input['type'] ?? 'null')); break; } //end switch return $rules; } /** * Get custom attributes for validator errors. * * @return array */ public function attributes(): array { $attributes = []; foreach ($this->configModel->config as $input) { $attributes = array_merge($attributes, $this->recursiveAttributes($input, $this->fieldName)); } return $attributes; } public function recursiveAttributes(&$input, $prefix): array { $attributes = []; $attributeKey = sprintf("%s.%s", $prefix, $input['key']); $attributes = array_merge($attributes, [$attributeKey => Str::lower($input['label'])]); if (isset($input['children'])) { foreach ($input['children'] as $key => $children) { $attributes = array_merge($attributes, $this->recursiveAttributes($children, $attributeKey . ".*")); } } return $attributes; } /** * Merge native validations and custom validations. * * @param string|array $defaultValidations * @param array $input * @return array */ private function compileValidate(string|array $defaultValidations, array $input): array { $newValidations = $this->mergeValidations($defaultValidations, $input); // Get unique rule and priorise important rules first. $rules = \collect($newValidations)->unique()->sort(function ($valueA, $valueB) { $valueA = $this->ruleHasToComeFirst($valueA); $valueB = $this->ruleHasToComeFirst($valueB); return $valueA - $valueB; }); // Conditions the rules. $hasRequire = $rules->first(fn($rule) => $rule === 'required') ? true : false; $hasRequireCond = $rules->first(fn($rule) => \strpos($rule, 'required_') !== false) ? true : false; $hasNullable = $rules->first(fn($rule) => \strpos($rule, 'nullable') !== false) ? true : false; $hasSometime = $rules->first(fn($rule) => \strpos($rule, 'sometimes') !== false) ? true : false; $hasPresent = $rules->first(fn($rule) => \strpos($rule, 'present') !== false) ? true : false; if ($hasRequire and ($hasRequireCond or $hasNullable or $hasSometime or $hasPresent)) { $rules = $rules->reject(fn($rule) => $rule === 'required'); } return $rules->all(); } /** * Merge new validations into default validations. * * @param string|array $defaultValidations * @param array $input * @return array */ private function mergeValidations($defaultValidations, $input): array { if (\is_string($defaultValidations)) { $defaultValidations = explode('|', $defaultValidations); } /** @var array|string $inputValidations */ $inputValidations = \collect($input)->get('validation', $input); $inputValidations = \is_string($inputValidations) ? explode('|', $inputValidations) : $inputValidations; foreach ($inputValidations as $validate) { // Split a string by a special character. $parts = explode(':', $validate, 2); // Check if there is a special character. if (count($parts) > 1) { [$ruleName, $ruleValue] = $parts; // Deletes any old rule that starts with “ruleName:”. $defaultValidations = collect($defaultValidations) ->reject(function ($existingRule) use ($ruleName) { return is_string($existingRule) && str_starts_with($existingRule, sprintf('%s:', $ruleName)); }) ->values() ->all(); // Add the new rule. $defaultValidations[] = $validate; } else { // Check if rule without special character already exist. if (!in_array($validate, $defaultValidations)) { // Add the input rule. $defaultValidations[] = $validate; } } } return $defaultValidations; } /** * Does the rule need to come first ? * * @param string $rule * @return integer */ private function ruleHasToComeFirst(string $rule): int { $rule = \collect(\explode(':', $rule, 2))->filter()->first(null, ''); $rules = \collect([ 'required_if', 'required_unless', 'required_with', 'required_with_all', 'required_without', 'required_without_all', 'required_with_all', 'missing', 'missing_if', 'missing_unless', 'missing_with', 'missing_with_all', 'nullable', 'sometimes', 'present', 'required' ]); $itemKey = $rules->search(fn(string $firstRule) => \strpos($rule, $firstRule) === 0); if (\is_bool($itemKey)) { return $rules->count() + 1; } return $itemKey; } /** * Recursively traverses an array and converts it to Boolean * all values whose key begins with “boolean_”. * * @param array $data * @return array */ private function castBooleanFields(array $data): array { foreach ($data as $key => $value) { // If the value is an array, recursive method. if (is_array($value)) { $data[$key] = $this->castBooleanFields($value); continue; } // Check if the key start with the pattern enum_. if (str_starts_with($key, sprintf("%s_", DynamicFieldTypes::boolean->name))) { // Manages values "true", "false", "1", "0", 1, 0, "on", "off". $data[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN); } } return $data; } }