opnform-host-nginx/api/app/Http/Requests/AnswerFormRequest.php

304 lines
11 KiB
PHP
Raw Normal View History

2022-09-20 21:59:52 +02:00
<?php
namespace App\Http\Requests;
use App\Models\Forms\Form;
use App\Rules\CustomFieldValidationRule;
use App\Rules\MatrixValidationRule;
2022-09-20 21:59:52 +02:00
use App\Rules\StorageFile;
2024-02-23 11:54:12 +01:00
use App\Rules\ValidHCaptcha;
use App\Rules\ValidPhoneInputRule;
Add reCAPTCHA support and update captcha provider handling (#647) * Add reCAPTCHA support and update captcha provider handling - Introduced reCAPTCHA as an additional captcha provider alongside hCaptcha. - Updated form request validation to handle different captcha providers based on user selection. - Added a new validation rule for reCAPTCHA. - Modified the forms model to include a 'captcha_provider' field. - Created a migration to add the 'captcha_provider' column to the forms table. - Updated frontend components to support dynamic rendering of captcha based on the selected provider. - Enhanced tests to cover scenarios for both hCaptcha and reCAPTCHA. These changes improve the flexibility of captcha options available to users, enhancing form security and user experience. * fix pint * change comment text * Refactor captcha implementation and integrate new captcha components - Removed the old RecaptchaV2 component and replaced it with a new implementation that supports both reCAPTCHA and hCaptcha through a unified CaptchaInput component. - Updated the OpenForm component to utilize the new CaptchaInput for dynamic captcha rendering based on user-selected provider. - Cleaned up the package.json by removing the deprecated @hcaptcha/vue3-hcaptcha dependency. - Enhanced form initialization to set a default captcha provider. - Improved error handling and cleanup for both reCAPTCHA and hCaptcha scripts. These changes streamline captcha integration, improve maintainability, and enhance user experience by providing a more flexible captcha solution. * Refactor captcha error messages and localization support * Refactor registration process to integrate reCAPTCHA - Replaced hCaptcha implementation with reCAPTCHA in RegisterController and related test cases. - Updated validation rules to utilize g-recaptcha-response instead of h-captcha-response. - Modified RegisterForm component to support reCAPTCHA, including changes to the form data structure and component references. - Enhanced test cases to reflect the new reCAPTCHA integration, ensuring proper validation and response handling. These changes improve security and user experience during the registration process by adopting a more widely used captcha solution. * Fix reCAPTCHA configuration and update RegisterForm styling - Corrected the configuration key for reCAPTCHA in RegisterController from 'services.recaptcha.secret_key' to 'services.re_captcha.secret_key'. - Updated the styling of the Captcha input section in RegisterForm.vue to improve layout consistency. These changes ensure proper reCAPTCHA functionality and enhance the user interface during the registration process. * Fix reCAPTCHA configuration in RegisterTest to use the correct key format - Updated the configuration key for reCAPTCHA in RegisterTest from 'services.recaptcha.secret_key' to 'services.re_captcha.secret_key' to ensure proper functionality during tests. This change aligns the test setup with the recent updates in the reCAPTCHA integration, improving the accuracy of the registration process tests. --------- Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-18 16:35:09 +01:00
use App\Rules\ValidReCaptcha;
2024-02-23 11:54:12 +01:00
use App\Rules\ValidUrl;
2022-09-20 21:59:52 +02:00
use App\Service\Forms\FormLogicPropertyResolver;
use Illuminate\Foundation\Http\FormRequest;
2024-02-23 11:54:12 +01:00
use Illuminate\Http\Request;
2022-09-20 21:59:52 +02:00
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Stevebauman\Purify\Facades\Purify;
2022-09-20 21:59:52 +02:00
class AnswerFormRequest extends FormRequest
{
public Form $form;
protected array $requestRules = [];
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
protected int $maxFileSize;
public function __construct(Request $request)
{
$this->form = $request->form;
$this->maxFileSize = $this->form->workspace->max_file_size;
2022-09-20 21:59:52 +02:00
}
private function getFieldMaxFileSize($fieldProps)
{
return array_key_exists('max_file_size', $fieldProps) ?
min($fieldProps['max_file_size'] * 1000000, $this->maxFileSize) : $this->maxFileSize;
}
2022-09-20 21:59:52 +02:00
/**
* Validate form before use it
*
* @return bool
*/
public function authorize()
{
return !$this->form->is_closed && !$this->form->max_number_of_submissions_reached && $this->form->visibility === 'public';
2022-09-20 21:59:52 +02:00
}
/**
* Get the validation rules that apply to the form.
*
* @return array
*/
public function rules()
{
$selectionFields = collect($this->form->properties)->filter(function ($pro) {
return in_array($pro['type'], ['select', 'multi_select']);
});
2022-09-20 21:59:52 +02:00
foreach ($this->form->properties as $property) {
$rules = [];
/*if (!$this->form->is_pro) { // If not pro then not check logic
2022-09-20 21:59:52 +02:00
$property['logic'] = false;
}*/
2022-09-20 21:59:52 +02:00
// For get values instead of Id for select/multi select options
$data = $this->toArray();
2024-02-23 11:54:12 +01:00
foreach ($selectionFields as $field) {
if (isset($data[$field['id']]) && is_array($data[$field['id']])) {
2022-09-20 21:59:52 +02:00
$data[$field['id']] = array_map(function ($val) use ($field) {
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) {
return isset($op['id'], $op['name']) && ($op['id'] === $val || $op['name'] === $val);
2022-09-20 21:59:52 +02:00
});
2024-11-28 10:22:33 +01:00
return isset($tmpop['name']) ? $tmpop['name'] : $val;
2022-09-20 21:59:52 +02:00
}, $data[$field['id']]);
2024-11-28 10:22:33 +01:00
} elseif (isset($data[$field['id']])) {
// Handle single select values
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($field, $data) {
return isset($op['id'], $op['name']) && ($op['id'] === $data[$field['id']] || $op['name'] === $data[$field['id']]);
2024-11-28 10:22:33 +01:00
});
$data[$field['id']] = isset($tmpop['name']) ? $tmpop['name'] : $data[$field['id']];
2022-09-20 21:59:52 +02:00
}
2024-02-23 11:54:12 +01:00
}
2022-09-20 21:59:52 +02:00
if (FormLogicPropertyResolver::isRequired($property, $data)) {
$rules[] = 'required';
if ($property['type'] == 'checkbox') {
// Required for checkboxes means true
2022-09-20 21:59:52 +02:00
$rules[] = 'accepted';
} elseif ($property['type'] == 'rating') {
// For star rating, needs a minimum of 1 star
$rules[] = 'min:1';
} elseif ($property['type'] == 'matrix') {
$rules[] = new MatrixValidationRule($property, true);
2022-09-20 21:59:52 +02:00
}
} else {
$rules[] = 'nullable';
if ($property['type'] == 'matrix') {
$rules[] = new MatrixValidationRule($property, false);
}
2022-09-20 21:59:52 +02:00
}
// Clean id to escape "."
$propertyId = $property['id'];
if (in_array($property['type'], ['multi_select'])) {
$rules[] = 'array';
$this->requestRules[$propertyId . '.*'] = $this->getPropertyRules($property);
2022-09-20 21:59:52 +02:00
} else {
$rules = array_merge($rules, $this->getPropertyRules($property));
}
// User custom validation
if (!(Str::of($property['type'])->startsWith('nf-')) && isset($property['validation'])) {
$rules[] = (new CustomFieldValidationRule($property['validation'], $data));
}
2022-09-20 21:59:52 +02:00
$this->requestRules[$propertyId] = $rules;
}
Add reCAPTCHA support and update captcha provider handling (#647) * Add reCAPTCHA support and update captcha provider handling - Introduced reCAPTCHA as an additional captcha provider alongside hCaptcha. - Updated form request validation to handle different captcha providers based on user selection. - Added a new validation rule for reCAPTCHA. - Modified the forms model to include a 'captcha_provider' field. - Created a migration to add the 'captcha_provider' column to the forms table. - Updated frontend components to support dynamic rendering of captcha based on the selected provider. - Enhanced tests to cover scenarios for both hCaptcha and reCAPTCHA. These changes improve the flexibility of captcha options available to users, enhancing form security and user experience. * fix pint * change comment text * Refactor captcha implementation and integrate new captcha components - Removed the old RecaptchaV2 component and replaced it with a new implementation that supports both reCAPTCHA and hCaptcha through a unified CaptchaInput component. - Updated the OpenForm component to utilize the new CaptchaInput for dynamic captcha rendering based on user-selected provider. - Cleaned up the package.json by removing the deprecated @hcaptcha/vue3-hcaptcha dependency. - Enhanced form initialization to set a default captcha provider. - Improved error handling and cleanup for both reCAPTCHA and hCaptcha scripts. These changes streamline captcha integration, improve maintainability, and enhance user experience by providing a more flexible captcha solution. * Refactor captcha error messages and localization support * Refactor registration process to integrate reCAPTCHA - Replaced hCaptcha implementation with reCAPTCHA in RegisterController and related test cases. - Updated validation rules to utilize g-recaptcha-response instead of h-captcha-response. - Modified RegisterForm component to support reCAPTCHA, including changes to the form data structure and component references. - Enhanced test cases to reflect the new reCAPTCHA integration, ensuring proper validation and response handling. These changes improve security and user experience during the registration process by adopting a more widely used captcha solution. * Fix reCAPTCHA configuration and update RegisterForm styling - Corrected the configuration key for reCAPTCHA in RegisterController from 'services.recaptcha.secret_key' to 'services.re_captcha.secret_key'. - Updated the styling of the Captcha input section in RegisterForm.vue to improve layout consistency. These changes ensure proper reCAPTCHA functionality and enhance the user interface during the registration process. * Fix reCAPTCHA configuration in RegisterTest to use the correct key format - Updated the configuration key for reCAPTCHA in RegisterTest from 'services.recaptcha.secret_key' to 'services.re_captcha.secret_key' to ensure proper functionality during tests. This change aligns the test setup with the recent updates in the reCAPTCHA integration, improving the accuracy of the registration process tests. --------- Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-18 16:35:09 +01:00
// Validate Captcha
if ($this->form->use_captcha) {
Add reCAPTCHA support and update captcha provider handling (#647) * Add reCAPTCHA support and update captcha provider handling - Introduced reCAPTCHA as an additional captcha provider alongside hCaptcha. - Updated form request validation to handle different captcha providers based on user selection. - Added a new validation rule for reCAPTCHA. - Modified the forms model to include a 'captcha_provider' field. - Created a migration to add the 'captcha_provider' column to the forms table. - Updated frontend components to support dynamic rendering of captcha based on the selected provider. - Enhanced tests to cover scenarios for both hCaptcha and reCAPTCHA. These changes improve the flexibility of captcha options available to users, enhancing form security and user experience. * fix pint * change comment text * Refactor captcha implementation and integrate new captcha components - Removed the old RecaptchaV2 component and replaced it with a new implementation that supports both reCAPTCHA and hCaptcha through a unified CaptchaInput component. - Updated the OpenForm component to utilize the new CaptchaInput for dynamic captcha rendering based on user-selected provider. - Cleaned up the package.json by removing the deprecated @hcaptcha/vue3-hcaptcha dependency. - Enhanced form initialization to set a default captcha provider. - Improved error handling and cleanup for both reCAPTCHA and hCaptcha scripts. These changes streamline captcha integration, improve maintainability, and enhance user experience by providing a more flexible captcha solution. * Refactor captcha error messages and localization support * Refactor registration process to integrate reCAPTCHA - Replaced hCaptcha implementation with reCAPTCHA in RegisterController and related test cases. - Updated validation rules to utilize g-recaptcha-response instead of h-captcha-response. - Modified RegisterForm component to support reCAPTCHA, including changes to the form data structure and component references. - Enhanced test cases to reflect the new reCAPTCHA integration, ensuring proper validation and response handling. These changes improve security and user experience during the registration process by adopting a more widely used captcha solution. * Fix reCAPTCHA configuration and update RegisterForm styling - Corrected the configuration key for reCAPTCHA in RegisterController from 'services.recaptcha.secret_key' to 'services.re_captcha.secret_key'. - Updated the styling of the Captcha input section in RegisterForm.vue to improve layout consistency. These changes ensure proper reCAPTCHA functionality and enhance the user interface during the registration process. * Fix reCAPTCHA configuration in RegisterTest to use the correct key format - Updated the configuration key for reCAPTCHA in RegisterTest from 'services.recaptcha.secret_key' to 'services.re_captcha.secret_key' to ensure proper functionality during tests. This change aligns the test setup with the recent updates in the reCAPTCHA integration, improving the accuracy of the registration process tests. --------- Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-18 16:35:09 +01:00
if ($this->form->captcha_provider === 'recaptcha') {
$this->requestRules['g-recaptcha-response'] = [new ValidReCaptcha()];
} elseif ($this->form->captcha_provider === 'hcaptcha') {
$this->requestRules['h-captcha-response'] = [new ValidHCaptcha()];
}
2022-09-20 21:59:52 +02:00
}
// Validate submission_id for edit mode
if ($this->form->is_pro && $this->form->editable_submissions) {
$this->requestRules['submission_id'] = 'string';
}
2022-09-20 21:59:52 +02:00
return $this->requestRules;
}
/**
* Renames validated fields (because field names are ids)
2024-02-23 11:54:12 +01:00
*
2022-09-20 21:59:52 +02:00
* @return array
*/
public function attributes()
{
$fields = [];
foreach ($this->form->properties as $property) {
$fields[$property['id']] = $property['name'];
}
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
return $fields;
}
/**
* Get the validation messages that apply to the request.
*
* @return array
*/
public function messages()
{
$messages = [];
foreach ($this->form->properties as $property) {
2024-02-23 11:54:12 +01:00
if ($property['type'] == 'date' && isset($property['date_range']) && $property['date_range']) {
$messages[$property['id'] . '.0.required_with'] = 'From date is required';
$messages[$property['id'] . '.1.required_with'] = 'To date is required';
$messages[$property['id'] . '.0.before_or_equal'] = 'From date must be before or equal To date';
}
if ($property['type'] == 'rating') {
$messages[$property['id'] . '.min'] = 'A rating must be selected';
}
}
2024-02-23 11:54:12 +01:00
return $messages;
}
2022-09-20 21:59:52 +02:00
/**
* Return validation rules for a given form property
*/
private function getPropertyRules($property): array
{
switch ($property['type']) {
case 'text':
case 'rich_text':
case 'signature':
2022-09-20 21:59:52 +02:00
return ['string'];
case 'number':
case 'rating':
case 'scale':
case 'slider':
2022-09-20 21:59:52 +02:00
return ['numeric'];
case 'select':
case 'multi_select':
if (($property['allow_creation'] ?? false)) {
2022-09-20 21:59:52 +02:00
return ['string'];
}
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
return [Rule::in($this->getSelectPropertyOptions($property))];
case 'checkbox':
return ['boolean'];
case 'url':
if (isset($property['file_upload']) && $property['file_upload']) {
$this->requestRules[$property['id'] . '.*'] = [new StorageFile($this->maxFileSize, [], $this->form)];
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
return ['array'];
}
2024-02-23 11:54:12 +01:00
return [new ValidUrl()];
2022-09-20 21:59:52 +02:00
case 'files':
$allowedFileTypes = [];
if (!empty($property['allowed_file_types'])) {
2024-02-23 11:54:12 +01:00
$allowedFileTypes = explode(',', $property['allowed_file_types']);
2022-09-20 21:59:52 +02:00
}
$this->requestRules[$property['id'] . '.*'] = [new StorageFile($this->getFieldMaxFileSize($property), $allowedFileTypes, $this->form)];
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
return ['array'];
case 'email':
return ['email:filter'];
case 'date':
if (isset($property['date_range']) && $property['date_range']) {
$this->requestRules[$property['id'] . '.*'] = $this->getRulesForDate($property);
$this->requestRules[$property['id'] . '.0'] = ['required_with:' . $property['id'] . '.1', 'before_or_equal:' . $property['id'] . '.1'];
$this->requestRules[$property['id'] . '.1'] = ['required_with:' . $property['id'] . '.0'];
2024-02-23 11:54:12 +01:00
return ['array', 'min:2'];
2022-09-20 21:59:52 +02:00
}
2024-02-23 11:54:12 +01:00
return $this->getRulesForDate($property);
case 'phone_number':
if (isset($property['use_simple_text_input']) && $property['use_simple_text_input']) {
return ['string'];
}
2024-02-23 11:54:12 +01:00
return ['string', 'min:6', new ValidPhoneInputRule()];
2022-09-20 21:59:52 +02:00
default:
return [];
}
}
private function getRulesForDate($property)
{
if (isset($property['disable_past_dates']) && $property['disable_past_dates']) {
return ['date', 'after:yesterday'];
2024-02-23 11:54:12 +01:00
} elseif (isset($property['disable_future_dates']) && $property['disable_future_dates']) {
return ['date', 'before:tomorrow'];
}
2024-02-23 11:54:12 +01:00
return ['date'];
}
2022-09-20 21:59:52 +02:00
private function getSelectPropertyOptions($property): array
{
$type = $property['type'];
if (!isset($property[$type])) {
2022-09-20 21:59:52 +02:00
return [];
}
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
return array_column($property[$type]['options'], 'name');
}
protected function prepareForValidation()
{
// Set locale based on form language
if ($this->form?->language && in_array($this->form->language, Form::LANGUAGES)) {
app()->setLocale($this->form->language);
}
2022-09-20 21:59:52 +02:00
$receivedData = $this->toArray();
$mergeData = [];
$countryCodeMapper = json_decode(file_get_contents(resource_path('data/country_code_mapper.json')), true);
collect($this->form->properties)->each(function ($property) use ($countryCodeMapper, $receivedData, &$mergeData) {
2022-09-20 21:59:52 +02:00
$receivedValue = $receivedData[$property['id']] ?? null;
// Escape all '\' in select options
if (in_array($property['type'], ['select', 'multi_select']) && !is_null($receivedValue)) {
2022-09-20 21:59:52 +02:00
if (is_array($receivedValue)) {
$mergeData[$property['id']] = collect($receivedValue)->map(function ($value) {
$value = Str::of($value);
2024-02-23 11:54:12 +01:00
2022-09-20 21:59:52 +02:00
return $value->replace(
2024-02-23 11:54:12 +01:00
["\e", "\f", "\n", "\r", "\t", "\v", '\\'],
['\\e', '\\f', '\\n', '\\r', '\\t', '\\v', '\\\\']
2022-09-20 21:59:52 +02:00
)->toString();
})->toArray();
} else {
$receivedValue = Str::of($receivedValue);
$mergeData[$property['id']] = $receivedValue->replace(
2024-02-23 11:54:12 +01:00
["\e", "\f", "\n", "\r", "\t", "\v", '\\'],
['\\e', '\\f', '\\n', '\\r', '\\t', '\\v', '\\\\']
2022-09-20 21:59:52 +02:00
)->toString();
}
}
if ($property['type'] === 'phone_number' && (!isset($property['use_simple_text_input']) || !$property['use_simple_text_input']) && $receivedValue && in_array($receivedValue, $countryCodeMapper)) {
$mergeData[$property['id']] = null;
}
if ($property['type'] === 'rich_text' && $receivedValue) {
$mergeData[$property['id']] = Purify::clean($receivedValue);
}
2022-09-20 21:59:52 +02:00
});
$this->merge($mergeData);
}
}