Initial commit
This commit is contained in:
262
app/Service/Forms/FormCleaner.php
Normal file
262
app/Service/Forms/FormCleaner.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Service\Forms;
|
||||
|
||||
use App\Http\Requests\UserFormRequest;
|
||||
use App\Http\Resources\FormResource;
|
||||
use App\Models\Forms\Form;
|
||||
use App\Models\Workspace;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Stevebauman\Purify\Facades\Purify;
|
||||
use function App\Service\str_starts_with;
|
||||
use function collect;
|
||||
|
||||
class FormCleaner
|
||||
{
|
||||
/**
|
||||
* All the performed cleanings
|
||||
* @var bool
|
||||
*/
|
||||
private array $cleanings = [];
|
||||
|
||||
private array $data;
|
||||
|
||||
private array $formDefaults = [
|
||||
'notifies' => false,
|
||||
'color' => '#3B82F6',
|
||||
'hide_title' => false,
|
||||
'no_branding' => false,
|
||||
'transparent_background' => false,
|
||||
'uppercase_labels' => true,
|
||||
'webhook_url' => null,
|
||||
'cover_picture' => null,
|
||||
'logo_picture' => null,
|
||||
'database_fields_update' => null,
|
||||
'theme' => 'default',
|
||||
'use_captcha' => false,
|
||||
'password' => null,
|
||||
];
|
||||
|
||||
private array $fieldDefaults = [
|
||||
// 'name' => '' TODO: prevent name changing, use alias for column and keep original name as it is
|
||||
'hide_field_name' => false,
|
||||
'prefill' => null,
|
||||
'placeholder' => null,
|
||||
'help' => null,
|
||||
'file_upload' => false,
|
||||
'with_time' => null,
|
||||
'width' => 'full',
|
||||
'generates_uuid' => false,
|
||||
'generates_auto_increment_id' => false,
|
||||
'logic' => null,
|
||||
'allow_creation' => false
|
||||
];
|
||||
|
||||
private array $cleaningMessages = [
|
||||
// For form
|
||||
'notifies' => "Email notification were disabled.",
|
||||
'color' => "Form color set to default blue.",
|
||||
'hide_title' => "Title is not hidden.",
|
||||
'no_branding' => "OpenForm branding is not hidden.",
|
||||
'transparent_background' => "Transparent background was disabled.",
|
||||
'uppercase_labels' => "Labels use uppercase letters",
|
||||
'webhook_url' => "Webhook disabled.",
|
||||
'cover_picture' => 'The cover picture was removed.',
|
||||
'logo_picture' => 'The logo was removed.',
|
||||
'database_fields_update' => 'Form submission will only create new records (no updates).',
|
||||
'theme' => 'Default theme was applied.',
|
||||
|
||||
// For fields
|
||||
'hide_field_name' => 'Hide field name removed.',
|
||||
'prefill' => "Field prefill removed.",
|
||||
'placeholder' => "Empty text (placeholder) removed",
|
||||
'help' => "Help text removed.",
|
||||
'file_upload' => "Link field is not a file upload.",
|
||||
'with_time' => "Time was removed from date input.",
|
||||
'custom_block' => 'The custom block was removed.',
|
||||
'files' => 'The file upload file was hidden.',
|
||||
'relation' => 'The relation file was hidden.',
|
||||
'width' => 'The field width was set to full width',
|
||||
'allow_creation' => 'Select option creation was disabled.',
|
||||
|
||||
// Advanced fields
|
||||
'generates_uuid' => 'ID generation disabled.',
|
||||
'generates_auto_increment_id' => 'ID generation disabled.',
|
||||
|
||||
'use_captcha' => 'Captcha form protection was disabled.',
|
||||
|
||||
// Security & Privacy
|
||||
'password' => 'Password protection was disabled',
|
||||
|
||||
'logic' => 'Logic disabled for this property'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns form data after request ingestion
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one cleaning was done
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCleaned(): bool
|
||||
{
|
||||
return count($this->cleanings) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the messages for each cleaning step performed
|
||||
*/
|
||||
public function getPerformedCleanings(): array
|
||||
{
|
||||
$cleaningMsgs = [];
|
||||
foreach ($this->cleanings as $key => $val) {
|
||||
$cleaningMsgs[$key] = collect($val)->map(function ($cleaning) {
|
||||
return $this->cleaningMessages[$cleaning];
|
||||
});
|
||||
}
|
||||
return $cleaningMsgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes form pro features from data if user isn't pro
|
||||
*/
|
||||
public function processRequest(UserFormRequest $request): FormCleaner
|
||||
{
|
||||
$data = $request->validated();
|
||||
$this->data = $this->commonCleaning($data);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create form cleaner instance from existing form
|
||||
*/
|
||||
public function processForm(Request $request, Form $form) : FormCleaner {
|
||||
$data = (new FormResource($form))->toArray($request);
|
||||
$this->data = $this->commonCleaning($data);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function isPro(Workspace $workspace) {
|
||||
return $workspace->is_pro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dry run celanings
|
||||
* @param User|null $user
|
||||
*/
|
||||
public function simulateCleaning(Workspace $workspace): FormCleaner {
|
||||
if($this->isPro($workspace)) return $this;
|
||||
|
||||
$this->data = $this->removeProFeatures($this->data, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform Cleanigns
|
||||
* @param User|null $user
|
||||
* @return $this|array
|
||||
*/
|
||||
public function performCleaning(Workspace $workspace): FormCleaner
|
||||
{
|
||||
if($this->isPro($workspace)) return $this;
|
||||
|
||||
$this->data = $this->removeProFeatures($this->data);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean all forms:
|
||||
* - Escape html of custom text block
|
||||
*/
|
||||
private function commonCleaning(array $data)
|
||||
{
|
||||
foreach ($data['properties'] as &$property) {
|
||||
if ($property['type'] == 'nf-text' && isset($property['content'])) {
|
||||
$property['content'] = Purify::clean($property['content']);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function removeProFeatures(array $data, $simulation = false)
|
||||
{
|
||||
$this->cleanForm($data, $simulation);
|
||||
$this->cleanProperties($data, $simulation);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function cleanForm(array &$data, $simulation = false): void
|
||||
{
|
||||
$this->clean($data, $this->formDefaults, $simulation);
|
||||
}
|
||||
|
||||
private function cleanProperties(array &$data, $simulation = false): void
|
||||
{
|
||||
foreach ($data['properties'] as $key => &$property) {
|
||||
// Remove pro custom blocks
|
||||
if (\Str::of($property['type'])->startsWith('nf-')) {
|
||||
$this->cleanings[$property['name']][] = 'custom_block';
|
||||
if (!$simulation) {
|
||||
unset($data['properties'][$key]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clean pro field options
|
||||
$this->cleanField($property, $this->fieldDefaults, $simulation);
|
||||
}
|
||||
}
|
||||
|
||||
private function clean(array &$data, array $defaults, $simulation = false): void
|
||||
{
|
||||
foreach ($defaults as $key => $value) {
|
||||
if (Arr::get($data, $key) !== $value) {
|
||||
if (!isset($this->cleanings['form'])) $this->cleanings['form'] = [];
|
||||
$this->cleanings['form'][] = $key;
|
||||
|
||||
// If not a simulation, do the cleaning
|
||||
if (!$simulation) {
|
||||
Arr::set($data, $key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanField(array &$data, array $defaults, $simulation = false): void
|
||||
{
|
||||
foreach ($defaults as $key => $value) {
|
||||
if (isset($data[$key]) && Arr::get($data, $key) !== $value) {
|
||||
$this->cleanings[$data['name']][] = $key;
|
||||
if (!$simulation) {
|
||||
Arr::set($data, $key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove pro types columns
|
||||
foreach (['files'] as $proType) {
|
||||
if ($data['type'] == $proType && (!isset($data['hidden']) || !$data['hidden'])) {
|
||||
$this->cleanings[$data['name']][] = $proType;
|
||||
if (!$simulation) {
|
||||
$data['hidden'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
292
app/Service/Forms/FormLogicConditionChecker.php
Normal file
292
app/Service/Forms/FormLogicConditionChecker.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Forms;
|
||||
|
||||
use Mockery\Matcher\Any;
|
||||
|
||||
use function PHPUnit\Framework\isEmpty;
|
||||
|
||||
class FormLogicConditionChecker
|
||||
{
|
||||
public function __construct(private ?array $conditions, private ?array $formData)
|
||||
{
|
||||
}
|
||||
|
||||
public static function conditionsMet(?array $conditions, array $formData): bool {
|
||||
return (new self($conditions, $formData))->conditionsAreMet($conditions, $formData);
|
||||
}
|
||||
|
||||
private function conditionsAreMet(?array $conditions, array $formData): bool {
|
||||
if (!$conditions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it's not a group, just a single condition
|
||||
if (!isset($conditions['operatorIdentifier'])) {
|
||||
return $this->propertyConditionMet($conditions['value'], $formData[$conditions['value']['property_meta']['id']]);
|
||||
}
|
||||
|
||||
if ($conditions['operatorIdentifier'] === 'and') {
|
||||
$isvalid = true;
|
||||
foreach($conditions['children'] as $childrenCondition){
|
||||
if (!$this->conditionsMet($childrenCondition, $formData)) {
|
||||
$isvalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $isvalid;
|
||||
} else if ($conditions['operatorIdentifier'] === 'or') {
|
||||
$isvalid = false;
|
||||
foreach($conditions['children'] as $childrenCondition){
|
||||
if ($this->conditionsMet($childrenCondition, $formData)) {
|
||||
$isvalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $isvalid;
|
||||
}
|
||||
|
||||
throw new \Exception('Unexcepted operatorIdentifier:'. $conditions['operatorIdentifier']);
|
||||
}
|
||||
|
||||
private function propertyConditionMet(array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['property_meta']['type']) {
|
||||
case 'text':
|
||||
case 'url':
|
||||
case 'email':
|
||||
case 'phone_number':
|
||||
return $this->textConditionMet($propertyCondition, $value);
|
||||
case 'number':
|
||||
return $this->numberConditionMet($propertyCondition, $value);
|
||||
case 'checkbox':
|
||||
return $this->checkboxConditionMet($propertyCondition, $value);
|
||||
case 'select':
|
||||
return $this->selectConditionMet($propertyCondition, $value);
|
||||
case 'date':
|
||||
return $this->dateConditionMet($propertyCondition, $value);
|
||||
case 'multi_select':
|
||||
return $this->multiSelectConditionMet($propertyCondition, $value);
|
||||
case 'files':
|
||||
return $this->filesConditionMet($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function checkEquals ($condition, $fieldValue): bool {
|
||||
return $condition['value'] === $fieldValue;
|
||||
}
|
||||
|
||||
private function checkContains ($condition, $fieldValue): bool {
|
||||
return ($fieldValue && is_array($fieldValue)) ? in_array($condition['value'], $fieldValue) : false;
|
||||
}
|
||||
|
||||
private function checkListContains ($condition, $fieldValue): bool {
|
||||
return ($fieldValue) ? (count(array_intersect($condition['value'], $fieldValue)) === count($condition['value'])) : false;
|
||||
}
|
||||
|
||||
private function checkStartsWith ($condition, $fieldValue): bool {
|
||||
return str_starts_with($fieldValue, $condition['value']);
|
||||
}
|
||||
|
||||
private function checkendsWith ($condition, $fieldValue): bool {
|
||||
return str_ends_with($fieldValue, $condition['value']);
|
||||
}
|
||||
|
||||
private function checkIsEmpty ($condition, $fieldValue): bool {
|
||||
if(is_array($fieldValue)){
|
||||
return count($fieldValue) === 0;
|
||||
}
|
||||
return (!$fieldValue || $fieldValue !== '' || $fieldValue !== null);
|
||||
}
|
||||
|
||||
private function checkGreaterThan ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && (float)$fieldValue > (float)$condition['value']);
|
||||
}
|
||||
|
||||
private function checkGreaterThanEqual ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && (float)$fieldValue >= (float)$condition['value']);
|
||||
}
|
||||
|
||||
private function checkLessThan ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && (float)$fieldValue < (float)$condition['value']);
|
||||
}
|
||||
|
||||
private function checkLessThanEqual ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && (float)$fieldValue <= (float)$condition['value']);
|
||||
}
|
||||
|
||||
private function checkBefore ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && $fieldValue > $condition['value']);
|
||||
}
|
||||
|
||||
private function checkAfter ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && $fieldValue < $condition['value']);
|
||||
}
|
||||
|
||||
private function checkOnOrBefore ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && $fieldValue >= $condition['value']);
|
||||
}
|
||||
|
||||
private function checkOnOrAfter ($condition, $fieldValue): bool {
|
||||
return ($condition['value'] && $fieldValue && $fieldValue <= $condition['value']);
|
||||
}
|
||||
|
||||
private function checkPastWeek ($condition, $fieldValue): bool {
|
||||
if(!$fieldValue) return false;
|
||||
$fieldDate = date('Y-m-d', strtotime($fieldValue));
|
||||
return ($fieldDate <= now()->toDateString() && $fieldDate >= now()->subDays(7)->toDateString());
|
||||
}
|
||||
|
||||
private function checkPastMonth ($condition, $fieldValue): bool {
|
||||
if(!$fieldValue) return false;
|
||||
$fieldDate = date('Y-m-d', strtotime($fieldValue));
|
||||
return ($fieldDate <= now()->toDateString() && $fieldDate >= now()->subMonths(1)->toDateString());
|
||||
}
|
||||
|
||||
private function checkPastYear ($condition, $fieldValue): bool {
|
||||
if(!$fieldValue) return false;
|
||||
$fieldDate = date('Y-m-d', strtotime($fieldValue));
|
||||
return ($fieldDate <= now()->toDateString() && $fieldDate >= now()->subYears(1)->toDateString());
|
||||
}
|
||||
|
||||
private function checkNextWeek ($condition, $fieldValue): bool {
|
||||
if(!$fieldValue) return false;
|
||||
$fieldDate = date('Y-m-d', strtotime($fieldValue));
|
||||
return ($fieldDate >= now()->toDateString() && $fieldDate <= now()->addDays(7)->toDateString());
|
||||
}
|
||||
|
||||
private function checkNextMonth ($condition, $fieldValue): bool {
|
||||
if(!$fieldValue) return false;
|
||||
$fieldDate = date('Y-m-d', strtotime($fieldValue));
|
||||
return ($fieldDate >= now()->toDateString() && $fieldDate <= now()->addMonths(1)->toDateString());
|
||||
}
|
||||
|
||||
private function checkNextYear ($condition, $fieldValue): bool {
|
||||
if(!$fieldValue) return false;
|
||||
$fieldDate = date('Y-m-d', strtotime($fieldValue));
|
||||
return ($fieldDate >= now()->toDateString() && $fieldDate <= now()->addYears(1)->toDateString());
|
||||
}
|
||||
|
||||
|
||||
private function textConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'equals':
|
||||
return $this->checkEquals($propertyCondition, $value);
|
||||
case 'does_not_equal':
|
||||
return !$this->checkEquals($propertyCondition, $value);
|
||||
case 'contains':
|
||||
return $this->checkContains($propertyCondition, $value);
|
||||
case 'does_not_contain':
|
||||
return !$this->checkContains($propertyCondition, $value);
|
||||
case 'starts_with':
|
||||
return $this->checkStartsWith($propertyCondition, $value);
|
||||
case 'ends_with':
|
||||
return $this->checkendsWith($propertyCondition, $value);
|
||||
case 'is_empty':
|
||||
return $this->checkIsEmpty($propertyCondition, $value);
|
||||
case 'is_not_empty':
|
||||
return !$this->checkIsEmpty($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function numberConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'equals':
|
||||
return $this->checkEquals($propertyCondition, $value);
|
||||
case 'does_not_equal':
|
||||
return !$this->checkEquals($propertyCondition, $value);
|
||||
case 'greater_than':
|
||||
return $this->checkGreaterThan($propertyCondition, $value);
|
||||
case 'less_than':
|
||||
return $this->checkLessThan($propertyCondition, $value);
|
||||
case 'greater_than_or_equal_to':
|
||||
return $this->checkGreaterThanEqual($propertyCondition, $value);
|
||||
case 'less_than_or_equal_to':
|
||||
return $this->checkLessThanEqual($propertyCondition, $value);
|
||||
case 'is_empty':
|
||||
return $this->checkIsEmpty($propertyCondition, $value);
|
||||
case 'is_not_empty':
|
||||
return !$this->checkIsEmpty($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function checkboxConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'equals':
|
||||
return $this->checkEquals($propertyCondition, $value);
|
||||
case 'does_not_equal':
|
||||
return !$this->checkEquals($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function selectConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'equals':
|
||||
return $this->checkEquals($propertyCondition, $value);
|
||||
case 'does_not_equal':
|
||||
return !$this->checkEquals($propertyCondition, $value);
|
||||
case 'is_empty':
|
||||
return $this->checkIsEmpty($propertyCondition, $value);
|
||||
case 'is_not_empty':
|
||||
return !$this->checkIsEmpty($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function dateConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'equals':
|
||||
return $this->checkEquals($propertyCondition, $value);
|
||||
case 'before':
|
||||
return $this->checkBefore($propertyCondition, $value);
|
||||
case 'after':
|
||||
return $this->checkAfter($propertyCondition, $value);
|
||||
case 'on_or_before':
|
||||
return $this->checkOnOrBefore($propertyCondition, $value);
|
||||
case 'on_or_after':
|
||||
return $this->checkOnOrAfter($propertyCondition, $value);
|
||||
case 'is_empty':
|
||||
return $this->checkIsEmpty($propertyCondition, $value);
|
||||
case 'past_week':
|
||||
return $this->checkPastWeek($propertyCondition, $value);
|
||||
case 'past_month':
|
||||
return $this->checkPastMonth($propertyCondition, $value);
|
||||
case 'past_year':
|
||||
return $this->checkPastYear($propertyCondition, $value);
|
||||
case 'next_week':
|
||||
return $this->checkNextWeek($propertyCondition, $value);
|
||||
case 'next_month':
|
||||
return $this->checkNextMonth($propertyCondition, $value);
|
||||
case 'next_year':
|
||||
return $this->checkNextYear($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function multiSelectConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'contains':
|
||||
return $this->checkListContains($propertyCondition, $value);
|
||||
case 'does_not_contain':
|
||||
return !$this->checkListContains($propertyCondition, $value);
|
||||
case 'is_empty':
|
||||
return $this->checkIsEmpty($propertyCondition, $value);
|
||||
case 'is_not_empty':
|
||||
return !$this->checkIsEmpty($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function filesConditionMet (array $propertyCondition, $value): bool {
|
||||
switch ($propertyCondition['operator']) {
|
||||
case 'is_empty':
|
||||
return $this->checkIsEmpty($propertyCondition, $value);
|
||||
case 'is_not_empty':
|
||||
return !$this->checkIsEmpty($propertyCondition, $value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
45
app/Service/Forms/FormLogicPropertyResolver.php
Normal file
45
app/Service/Forms/FormLogicPropertyResolver.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Forms;
|
||||
|
||||
use App\Service\Forms\FormLogicConditionChecker;
|
||||
|
||||
class FormLogicPropertyResolver
|
||||
{
|
||||
|
||||
private $property = [];
|
||||
private $formData = [];
|
||||
private $logic = false;
|
||||
|
||||
public function __construct(private array $prop, private array $values)
|
||||
{
|
||||
$this->property = $prop;
|
||||
$this->formData = $values;
|
||||
$this->logic = isset($this->property['logic']) ? $this->property['logic'] : false;
|
||||
}
|
||||
|
||||
public static function isRequired(array $property, array $values): bool
|
||||
{
|
||||
return (new self($property, $values))->shouldBeRequired();
|
||||
}
|
||||
|
||||
public function shouldBeRequired(): bool
|
||||
{
|
||||
if(!isset($this->property['required'])){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->logic) {
|
||||
return $this->property['required'];
|
||||
}
|
||||
|
||||
$conditionsMet = FormLogicConditionChecker::conditionsMet($this->logic['conditions'], $this->formData);
|
||||
if ($conditionsMet && $this->property['required'] && count($this->logic['actions']) > 0 && in_array('make-it-optional', $this->logic['actions'])) {
|
||||
return false;
|
||||
} else if ($conditionsMet && !$this->property['required'] && count($this->logic['actions']) > 0 && in_array('require-answer', $this->logic['actions'])) {
|
||||
return true;
|
||||
} else {
|
||||
return $this->property['required'];
|
||||
}
|
||||
}
|
||||
}
|
||||
194
app/Service/Forms/FormSubmissionFormatter.php
Normal file
194
app/Service/Forms/FormSubmissionFormatter.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Service\Forms;
|
||||
|
||||
|
||||
use App\Models\Forms\Form;
|
||||
use App\Service\WorkspaceHelper;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class FormSubmissionFormatter
|
||||
{
|
||||
|
||||
/**
|
||||
* If true, creates html <a> links for emails and urls
|
||||
* @var bool
|
||||
*/
|
||||
private $createLinks = false;
|
||||
|
||||
/**
|
||||
* If true, serialize arrays
|
||||
* @var bool
|
||||
*/
|
||||
private $outputStringsOnly = false;
|
||||
|
||||
private $showHiddenFields = false;
|
||||
|
||||
private $setEmptyForNoValue = false;
|
||||
|
||||
public function __construct(private Form $form, private array $formData)
|
||||
{
|
||||
}
|
||||
|
||||
public function createLinks()
|
||||
{
|
||||
$this->createLinks = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function showHiddenFields()
|
||||
{
|
||||
$this->showHiddenFields = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function outputStringsOnly()
|
||||
{
|
||||
$this->outputStringsOnly = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEmptyForNoValue()
|
||||
{
|
||||
$this->setEmptyForNoValue = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nice "FieldName": "Field Response" array
|
||||
* - If createLink enabled, returns html link for emails and links
|
||||
* Used for CSV exports
|
||||
*/
|
||||
public function getCleanKeyValue()
|
||||
{
|
||||
$data = $this->formData;
|
||||
$fields = $this->form->properties;
|
||||
|
||||
$returnArray = [];
|
||||
foreach ($fields as &$field) {
|
||||
// If not present skip
|
||||
if (!isset($data[$field['id']])) {
|
||||
if ($this->setEmptyForNoValue) {
|
||||
$returnArray[$field['name']] = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If should hide hidden fields
|
||||
if (!$this->showHiddenFields) {
|
||||
if (isset($field['hidden']) && $field['hidden']) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->createLinks && $field['type'] == 'url') {
|
||||
$returnArray[$field['name']] = '<a href="'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
} elseif ($this->createLinks && $field['type'] == 'email') {
|
||||
$returnArray[$field['name']] = '<a href="mailto:'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
} elseif ($field['type'] == 'multi_select') {
|
||||
$val = $data[$field['id']];
|
||||
if ($this->outputStringsOnly) {
|
||||
$returnArray[$field['name']] = implode(', ', $val);
|
||||
} else {
|
||||
$returnArray[$field['name']] = $val;
|
||||
}
|
||||
} elseif ($field['type'] == 'files') {
|
||||
if ($this->outputStringsOnly) {
|
||||
$formId = $this->form->id;
|
||||
$returnArray[$field['name']] = implode(', ',
|
||||
collect($data[$field['id']])->map(function ($file) use ($formId) {
|
||||
return route('open.forms.submissions.file', [$formId, $file]);
|
||||
})->toArray()
|
||||
);
|
||||
} else {
|
||||
$formId = $this->form->id;
|
||||
$returnArray[$field['name']] = collect($data[$field['id']])->map(function ($file) use ($formId) {
|
||||
return [
|
||||
'file_url' => route('open.forms.submissions.file', [$formId, $file]),
|
||||
'file_name' => $file,
|
||||
];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (is_array($data[$field['id']])) {
|
||||
$data[$field['id']] = implode(', ', $data[$field['id']]);
|
||||
}
|
||||
$returnArray[$field['name']] = $data[$field['id']];
|
||||
}
|
||||
}
|
||||
return $returnArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of fields, with a filled value attribute.
|
||||
* Used for humans.
|
||||
*/
|
||||
public function getFieldsWithValue()
|
||||
{
|
||||
$data = $this->formData;
|
||||
$fields = $this->form->properties;
|
||||
$transformedFields = [];
|
||||
foreach ($fields as $field) {
|
||||
if (!isset($field['id']) || !isset($data[$field['id']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If hide hidden fields
|
||||
if (!$this->showHiddenFields) {
|
||||
if (isset($field['hidden']) && $field['hidden']) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->createLinks && $field['type'] == 'url') {
|
||||
$field['value'] = '<a href="'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
} elseif ($this->createLinks && $field['type'] == 'email') {
|
||||
$field['value'] = '<a href="mailto:'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
} elseif ($field['type'] == 'checkbox') {
|
||||
$field['value'] = $data[$field['id']] ? 'Yes' : 'No';
|
||||
} elseif ($field['type'] == 'date') {
|
||||
if (is_array($data[$field['id']])) {
|
||||
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format('d/m/Y')
|
||||
.' - '.(new Carbon($data[$field['id']][1]))->format('d/m/Y') : (new Carbon($data[$field['id']][0]))->format('d/m/Y');
|
||||
} else {
|
||||
$field['value'] = (new Carbon($data[$field['id']]))->format((isset($field['with_time']) && $field['with_time']) ? 'd/m/Y H:i' : 'd/m/Y');
|
||||
}
|
||||
} elseif ($field['type'] == 'multi_select') {
|
||||
$val = $data[$field['id']];
|
||||
if ($this->outputStringsOnly) {
|
||||
$field['value'] = implode(', ', $val);
|
||||
} else {
|
||||
$field['value'] = $val;
|
||||
}
|
||||
} elseif ($field['type'] == 'files') {
|
||||
if ($this->outputStringsOnly) {
|
||||
$formId = $this->form->id;
|
||||
$field['value'] = implode(', ',
|
||||
collect($data[$field['id']])->map(function ($file) use ($formId) {
|
||||
return route('open.forms.submissions.file', [$formId, $file]);
|
||||
})->toArray()
|
||||
);
|
||||
} else {
|
||||
$formId = $this->form->id;
|
||||
$field['value'] = collect($data[$field['id']])->map(function ($file) use ($formId) {
|
||||
return [
|
||||
'file_url' => route('open.forms.submissions.file', [$formId, $file]),
|
||||
'file_name' => $file,
|
||||
];
|
||||
});
|
||||
|
||||
}
|
||||
} else {
|
||||
if (is_array($data[$field['id']]) && $this->outputStringsOnly) {
|
||||
$field['value'] = implode(', ', $data[$field['id']]);
|
||||
} else {
|
||||
$field['value'] = $data[$field['id']];
|
||||
}
|
||||
}
|
||||
$transformedFields[] = $field;
|
||||
}
|
||||
return $transformedFields;
|
||||
}
|
||||
|
||||
}
|
||||
24
app/Service/HtmlPurifier/HTMLPurifier_URIScheme_notion.php
Normal file
24
app/Service/HtmlPurifier/HTMLPurifier_URIScheme_notion.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Service\HtmlPurifier;
|
||||
|
||||
|
||||
use HTMLPurifier_Config;
|
||||
use HTMLPurifier_Context;
|
||||
use HTMLPurifier_URI;
|
||||
|
||||
class HTMLPurifier_URIScheme_notion extends \HTMLPurifier_URIScheme
|
||||
{
|
||||
public $browsable = true;
|
||||
public $may_omit_host = true;
|
||||
|
||||
public function doValidate(&$uri, $config, $context)
|
||||
{
|
||||
if ($uri->host == 'www.notion.so' || $uri->host == 'notion.so') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
app/Service/Storage/StorageFileNameParser.php
Normal file
69
app/Service/Storage/StorageFileNameParser.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Storage;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Used
|
||||
* File can have 2 formats:
|
||||
* - file_name-{uuid}.{ext}
|
||||
* - {uuid}
|
||||
*/
|
||||
class StorageFileNameParser
|
||||
{
|
||||
public ?string $uuid = null;
|
||||
public ?string $fileName = null;
|
||||
public ?string $extension = null;
|
||||
|
||||
public function __construct(string $fileName)
|
||||
{
|
||||
$this->parseFileName($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have parsed a file name and an extension, we keep the same and append uuid to avoid collisions
|
||||
* Otherwise we just return the uuid
|
||||
* @return string
|
||||
*/
|
||||
public function getMovedFileName(): ?string
|
||||
{
|
||||
if ($this->fileName && $this->extension) {
|
||||
return substr($this->fileName,0,50).'_'.$this->uuid.'.'.$this->extension;
|
||||
}
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
private function parseFileName(string $fileName)
|
||||
{
|
||||
if (Str::isUuid($fileName)) {
|
||||
$this->uuid = $fileName;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!str_contains($fileName, '_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$candidateString = substr($fileName, strrpos($fileName, '_') + 1);
|
||||
if (!str_contains($candidateString, '.')
|
||||
|| !Str::isUuid(substr($candidateString, 0, strpos($candidateString, '.')))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->uuid = substr($candidateString, 0, strpos($candidateString, '.'));
|
||||
$this->fileName = substr($fileName, 0, strrpos($fileName, '_'));
|
||||
// get everything after the last dot
|
||||
$this->extension = substr($candidateString, strrpos($candidateString, '.') + 1);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static function parse(string $fileName): self
|
||||
{
|
||||
return new self($fileName);
|
||||
}
|
||||
|
||||
}
|
||||
21
app/Service/WorkspaceHelper.php
Normal file
21
app/Service/WorkspaceHelper.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Models\Workspace;
|
||||
|
||||
class WorkspaceHelper
|
||||
{
|
||||
|
||||
public function __construct(public Workspace $workspace)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getRecords($relatedRecordIds = null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user