ldc-groupe/app/Http/Requests/Back/UpdateDynamicFieldRequest.php
2026-02-19 14:06:28 +01:00

420 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Requests\Back;
use App\Enums\Contact\ContactFormType;
use App\Enums\ElementPositionEnum;
use App\Enums\DynamicFieldTypes;
use App\Enums\StaticRoutesEnum;
use App\Models\PageConfig;
use App\Traits\Requests\HasInertiaPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Rules\Enum;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
use Uwil\LaravelCommon\Rules\WysiwygLength;
abstract class UpdateDynamicFieldRequest extends FormRequest
{
use HasInertiaPicture;
/** @var \App\Models\PageConfig|null $configModel */
private $configModel = null;
/** @var string $fieldName */
private $fieldName = "content";
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->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
*/
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;
}
/**
* Get rules from a specific input type.
*
* @param array $input
* @param string $prefix
* @return array
* @throws \UnexpectedValueException Unrecognized dynamic type.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity
*/
private function recursiveRules(array &$input, string $prefix): array
{
// phpcs:enable
$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::textarea->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::textarea->getValidation(), $input)
]);
break;
case DynamicFieldTypes::wysiwyg->value:
$defaultRules = explode('|', DynamicFieldTypes::wysiwyg->getValidation());
$wysiwygRules = $this->compileValidate(DynamicFieldTypes::wysiwyg->getValidation(), $input);
// Get the minimum and maximum values of the ‘between’ rule and
// remove them from the rules so that our custom rule WysiwygLength can apply it.
$defaults = $this->extractMinMax($defaultRules);
['min' => $min, 'max' => $max] = $this->extractMinMax($wysiwygRules, $defaults);
$newWysiwygRules = array_values(array_filter($wysiwygRules, fn($rule) => !(
is_string($rule) && preg_match('/^(min|max|between):/', $rule)
)));
$rules = array_merge($rules, [
$ruleKey => array_merge($newWysiwygRules, [
new WysiwygLength($min, $max),
])
]);
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::file->value:
$imgRules = $this->compileValidate(DynamicFieldTypes::file->getValidation(), $input);
$rules = array_merge($rules, [
$ruleKey => $this->pictureRules(implode("|", $imgRules)),
]);
break;
case DynamicFieldTypes::vimeoid->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::vimeoid->getValidation(), $input)
]);
break;
case DynamicFieldTypes::youtubeid->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::youtubeid->getValidation(), $input)
]);
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;
case DynamicFieldTypes::model->value:
// Set a new rule key for the model name.
$ruleKeyReserved = preg_replace('/([^.\s]+)$/', 'reserved_$1', $ruleKey);
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::model->getValidation(), $input),
$ruleKeyReserved => 'required|string|between:3,255',
]);
break;
case DynamicFieldTypes::staticroute->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(
$this->compileValidate(DynamicFieldTypes::staticroute->getValidation(), $input),
[new Enum(StaticRoutesEnum::class)],
),
]);
break;
// Specific inputs.
case DynamicFieldTypes::position->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(
$this->compileValidate(DynamicFieldTypes::position->getValidation(), $input),
[new Enum(ElementPositionEnum::class)],
),
]);
break;
case DynamicFieldTypes::contactform->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(
$this->compileValidate(DynamicFieldTypes::position->getValidation(), $input),
[new Enum(ContactFormType::class)],
),
]);
break;
default:
throw new \UnexpectedValueException("Unrecognized dynamic type: " . ($input['type'] ?? 'null'));
} //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;
}
/**
* Get attributes from a specific input type.
*
* @param array $input
* @param string $prefix
* @return array
*/
private function recursiveAttributes(array &$input, string $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(string|array $defaultValidations, array $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;
}
}
} //end foreach
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))) {
// Accepts boolean-like values (true/false, one/zero, on/off) as strings or numbers.
$data[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
}
return $data;
}
/**
* Extract min and max values from laravel validation.
*
* @param array $rules
* @param array|null $fallback
* @return array
*/
private function extractMinMax(array $rules, ?array $fallback = null): array
{
$min = $fallback['min'] ?? null;
$max = $fallback['max'] ?? null;
foreach ($rules as $rule) {
if (str_starts_with($rule, 'between:')) {
[$min, $max] = array_map(
'intval',
explode(',', substr($rule, 8))
);
return compact('min', 'max');
}
if (str_starts_with($rule, 'min:')) {
$min = (int) substr($rule, 4);
}
if (str_starts_with($rule, 'max:')) {
$max = (int) substr($rule, 4);
}
}
return compact('min', 'max');
}
}