Initial commit

This commit is contained in:
Julien Nahum
2022-09-20 21:59:52 +02:00
commit f8e6cd4dd6
479 changed files with 77078 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
<?php
namespace App\Http\Requests;
use App\Models\Forms\Form;
use App\Rules\StorageFile;
use App\Service\Forms\FormLogicPropertyResolver;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Http\Request;
use App\Rules\ValidHCaptcha;
class AnswerFormRequest extends FormRequest
{
const MAX_FILE_SIZE_PRO = 5000000;
const MAX_FILE_SIZE_ENTERPRISE = 20000000;
public Form $form;
protected array $requestRules = [];
protected int $maxFileSize;
public function __construct(Request $request)
{
$this->form = $request->form;
$this->maxFileSize = self::MAX_FILE_SIZE_PRO;
$workspace = $this->form->workspace;
if ($workspace && $workspace->is_enterprise) {
$this->maxFileSize = self::MAX_FILE_SIZE_ENTERPRISE;
}
}
/**
* Validate form before use it
*
* @return bool
*/
public function authorize()
{
return !$this->form->is_closed && !$this->form->max_number_of_submissions_reached;
}
/**
* Get the validation rules that apply to the form.
*
* @return array
*/
public function rules()
{
foreach ($this->form->properties as $property) {
$rules = [];
if (!$this->form->is_pro) { // If not pro then not check logic
$property['logic'] = false;
}
// For get values instead of Id for select/multi select options
$data = $this->toArray();
$selectionFields = collect($this->form->properties)->filter(function ($pro) {
return in_array($pro['type'], ['select', 'multi_select']);
});
foreach ($selectionFields as $field){
if(isset($data[$field['id']]) && is_array($data[$field['id']])){
$data[$field['id']] = array_map(function ($val) use ($field) {
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) {
return ($op['id'] === $val);
});
return isset($tmpop['name']) ? $tmpop['name'] : "";
}, $data[$field['id']]);
}
};
if (FormLogicPropertyResolver::isRequired($property, $data)) {
$rules[] = 'required';
// Required for checkboxes means true
if ($property['type'] == 'checkbox') {
$rules[] = 'accepted';
}
} else {
$rules[] = 'nullable';
}
// Clean id to escape "."
$propertyId = $property['id'];
if (in_array($property['type'], ['multi_select'])) {
$rules[] = 'array';
$this->requestRules[$propertyId.'.*'] = $this->getPropertyRules($property);
} else {
$rules = array_merge($rules, $this->getPropertyRules($property));
}
$this->requestRules[$propertyId] = $rules;
}
// Validate hCaptcha
if ($this->form->is_pro && $this->form->use_captcha) {
$this->requestRules['h-captcha-response'] = [new ValidHCaptcha()];
}
return $this->requestRules;
}
/**
* Renames validated fields (because field names are ids)
* @return array
*/
public function attributes()
{
$fields = [];
foreach ($this->form->properties as $property) {
$fields[$property['id']] = $property['name'];
}
return $fields;
}
/**
* Return validation rules for a given form property
* @param $property
*/
private function getPropertyRules($property): array
{
switch ($property['type']) {
case 'text':
case 'phone_number':
return ['string'];
case 'number':
return ['numeric'];
case 'select':
case 'multi_select':
if ($this->form->is_pro && ($property['allow_creation'] ?? false)) {
return ['string'];
}
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)];
return ['array'];
}
return ['url'];
case 'files':
$allowedFileTypes = [];
if($this->form->is_pro && !empty($property['allowed_file_types'])){
$allowedFileTypes = explode(",", $property['allowed_file_types']);
}
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, $allowedFileTypes)];
return ['array'];
case 'email':
return ['email:filter'];
case 'date':
if (isset($property['date_range']) && $property['date_range']) {
$this->requestRules[$property['id'].'.*'] = ['date'];
return ['array'];
}
return ['date'];
default:
return [];
}
}
private function getSelectPropertyOptions($property): array
{
$type = $property['type'];
if (!isset($property[$type])) {
return [];
}
return array_column($property[$type]['options'], 'name');
}
protected function prepareForValidation()
{
// Escape all '\' in select options
$receivedData = $this->toArray();
$mergeData = [];
collect($this->form->properties)->filter(function ($property) {
return in_array($property['type'], ['select', 'multi_select']);
})->each(function ($property) use ($receivedData, &$mergeData) {
$receivedValue = $receivedData[$property['id']] ?? null;
if (!is_null($receivedValue)) {
if (is_array($receivedValue)) {
$mergeData[$property['id']] = collect($receivedValue)->map(function ($value) {
$value = Str::of($value);
return $value->replace(
["\e", "\f", "\n", "\r", "\t", "\v", "\\"],
["\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\\\"]
)->toString();
})->toArray();
} else {
$receivedValue = Str::of($receivedValue);
$mergeData[$property['id']] = $receivedValue->replace(
["\e", "\f", "\n", "\r", "\t", "\v", "\\"],
["\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\\\"]
)->toString();
}
}
});
$this->merge($mergeData);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Integration;
use App\Models\Forms\Form;
use App\Models\Integration\FormZapierWebhook;
use Illuminate\Foundation\Http\FormRequest;
class StoreFormZapierWebhookRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'form_slug' => 'required|exists:forms,slug',
'hook_url' => 'required|string|url'
];
}
public function instanciateHook() {
$form = Form::whereSlug($this->form_slug)->firstOrFail();
return new FormZapierWebhook([
'form_id' => $form->id,
'hook_url' => $this->hook_url,
]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests;
use App\Models\Forms\Form;
use Illuminate\Validation\Rule;
class StoreFormRequest extends UserFormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return array_merge(parent::rules(), [// Info about database
'workspace_id' => 'required|exists:workspaces,id',
]);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Requests;
class UpdateFormRequest extends UserFormRequest
{
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use App\Rules\StorageFile;
use Illuminate\Foundation\Http\FormRequest;
class UploadAssetRequest extends FormRequest
{
const FORM_ASSET_MAX_SIZE = 5000000;
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'url' => ['required',new StorageFile(self::FORM_ASSET_MAX_SIZE, [
'png',
'jpeg',
'jpg',
'bmp',
'gif'
])],
];
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace App\Http\Requests;
use App\Models\Forms\Form;
use App\Rules\OneEmailPerLine;
use Illuminate\Validation\Rule;
use App\Rules\FormPropertyLogicRule;
/**
* Abstract class to validate create/update forms
*
* Class UserFormRequest
* @package App\Http\Requests
*/
abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
// Form Info
'title' => 'required|string|max:60',
'description' => 'nullable|string|max:2000',
'tags' => 'nullable|array',
// Notifications
'notifies' => 'boolean',
'notification_emails' => ['required_if:notifies,1', new OneEmailPerLine ],
'send_submission_confirmation' => 'boolean',
'notification_sender' => 'string|max:64',
'notification_subject' => 'string|max:200',
'notification_body' => 'string|nullable',
'notifications_include_submission' => 'boolean',
'webhook_url' => 'url|nullable',
'use_captcha' => 'boolean',
// Customization
'theme' => ['required',Rule::in(Form::THEMES)],
'width' => ['required',Rule::in(Form::WIDTHS)],
'cover_picture' => 'url|nullable',
'logo_picture' => 'url|nullable',
'dark_mode' => ['required',Rule::in(Form::DARK_MODE_VALUES)],
'color' => 'required|string',
'hide_title' => 'required|boolean',
'uppercase_labels' => 'required|boolean',
'no_branding' => 'required|boolean',
'transparent_background' => 'required|boolean',
'closes_at' => 'date|nullable',
'closed_text' => 'string|nullable',
// Custom Code
'custom_code' => 'string|nullable',
// Submission
'submit_button_text' => 'string|min:1|max:50',
're_fillable' => 'boolean',
're_fill_button_text' => 'string|min:1|max:50',
'submitted_text' => 'string|max:2000',
'redirect_url' => 'nullable|active_url|max:255',
'database_fields_update' => 'nullable|array',
'max_submissions_count' => 'integer|nullable|min:1',
'max_submissions_reached_text' => 'string|nullable',
// Properties
'properties' => 'required|array',
'properties.*.id' => 'required',
'properties.*.name' => 'required',
'properties.*.type' => 'required',
'properties.*.placeholder' => 'sometimes|nullable',
'properties.*.prefill' => 'sometimes|nullable',
'properties.*.help' => 'sometimes|nullable',
'properties.*.hidden' => 'boolean|nullable',
'properties.*.required' => 'boolean|nullable',
'properties.*.multiple' => 'boolean|nullable',
'properties.*.timezone' => 'sometimes|nullable',
'properties.*.width' => ['sometimes', Rule::in(['full','1/2','1/3','2/3','1/3','3/4','1/4'])],
'properties.*.allowed_file_types' => 'sometimes|nullable',
// Logic
'properties.*.logic' => ['array', 'nullable', new FormPropertyLogicRule()],
// Form blocks
'properties.*.content' => 'sometimes|nullable',
// Text field
'properties.*.multi_lines' => 'boolean|nullable',
'properties.*.max_char_limit' => 'integer|nullable|min:1|max:2000',
'properties.*.show_char_limit ' => 'boolean|nullable',
// Date field
'properties.*.with_time' => 'boolean|nullable',
'properties.*.date_range' => 'boolean|nullable',
// Select / Multi Select field
'properties.*.allow_creation' => 'boolean|nullable',
'properties.*.without_dropdown' => 'boolean|nullable',
// Advanced Options
'properties.*.generates_uuid' => 'boolean|nullable',
'properties.*.generates_auto_increment_id' => 'boolean|nullable',
// Security & Privacy
'can_be_indexed' => 'boolean',
'password' => 'sometimes|nullable',
];
}
/**
* Get the validation messages that apply to the request.
*
* @return array
*/
public function messages()
{
return [
'properties.*.name.required' => 'The form block number :position is missing a name.',
'properties.*.type.required' => 'The form block number :position is missing a type.',
'properties.*.max_char_limit.min' => 'The form block number :position max character limit must be at least 1 OR Empty',
'properties.*.max_char_limit.max' => 'The form block number :position max character limit may not be greater than 2000.',
];
}
}