From 1b15597e0e1d4afdebf2581d83d86ded7bb49db5 Mon Sep 17 00:00:00 2001
From: Chirag <103994754+chiragnotionforms@users.noreply.github.com>
Date: Wed, 25 Jan 2023 20:40:33 +0530
Subject: [PATCH] Fix logic on hidden, loading on edit submission (#75)
---
app/Jobs/Form/StoreFormSubmissionJob.php | 2 +-
app/Mail/Forms/SubmissionConfirmationMail.php | 2 +-
app/Rules/FormPropertyLogicRule.php | 34 ++++++++---
.../Forms/FormLogicConditionChecker.php | 4 +-
.../Forms/FormLogicPropertyResolver.php | 25 ++++++++
app/Service/Forms/FormSubmissionFormatter.php | 24 +++++++-
.../js/components/open/forms/OpenForm.vue | 18 +++---
resources/js/pages/forms/show-public.vue | 17 ++++--
resources/js/store/modules/open/records.js | 58 +++++++++++++++++++
tests/Feature/Forms/FormPropertyLogicTest.php | 2 +-
10 files changed, 159 insertions(+), 27 deletions(-)
create mode 100644 resources/js/store/modules/open/records.js
diff --git a/app/Jobs/Form/StoreFormSubmissionJob.php b/app/Jobs/Form/StoreFormSubmissionJob.php
index a280bf0..1dda78d 100644
--- a/app/Jobs/Form/StoreFormSubmissionJob.php
+++ b/app/Jobs/Form/StoreFormSubmissionJob.php
@@ -45,7 +45,7 @@ class StoreFormSubmissionJob implements ShouldQueue
$this->storeSubmission($formData);
- $formData["submission_id"] = $this->submissionData['submission_id'] ?? '';
+ $formData["submission_id"] = $this->submissionData['submission_id'] ?? null;
FormSubmitted::dispatch($this->form, $formData);
}
diff --git a/app/Mail/Forms/SubmissionConfirmationMail.php b/app/Mail/Forms/SubmissionConfirmationMail.php
index f3a2d3f..9c2f30e 100644
--- a/app/Mail/Forms/SubmissionConfirmationMail.php
+++ b/app/Mail/Forms/SubmissionConfirmationMail.php
@@ -43,7 +43,7 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
'fields' => $formatter->getFieldsWithValue(),
'form' => $form,
'noBranding' => $form->no_branding,
- 'submission_id' => isset($this->event->data['submission_id']) ? $this->event->data['submission_id'] : ''
+ 'submission_id' => $this->event->data['submission_id'] ?? null
]);
}
diff --git a/app/Rules/FormPropertyLogicRule.php b/app/Rules/FormPropertyLogicRule.php
index 5d3d300..fae01c0 100644
--- a/app/Rules/FormPropertyLogicRule.php
+++ b/app/Rules/FormPropertyLogicRule.php
@@ -339,7 +339,7 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
]
],
'does_not_contain' => [
- 'expected_type' => 'object',
+ 'expected_type' => ['object', 'string'],
'format' => [
'type' => 'uuid',
]
@@ -472,6 +472,7 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
private $isConditionCorrect = true;
private $isActionCorrect = true;
+ private $conditionErrors = [];
private $field = [];
private $data = [];
@@ -480,26 +481,31 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
if (!isset($condition['value'])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'missing condition body';
return;
}
if (!isset($condition['value']['property_meta'])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'missing condition property';
return;
}
if (!isset($condition['value']['property_meta']['type'])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'missing condition property type';
return;
}
if (!isset($condition['value']['operator'])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'missing condition operator';
return;
}
if (!isset($condition['value']['value'])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'missing condition value';
return;
}
@@ -509,14 +515,18 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
if (!isset(self::CONDITION_MAPPING[$typeField])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'configuration not found for condition type';
return;
}
if (!isset(self::CONDITION_MAPPING[$typeField]['comparators'][$operator])) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'configuration not found for condition operator';
return;
}
+ // TODO: find what's causing the issue when saving this validation rule :(
+
$type = self::CONDITION_MAPPING[$typeField]['comparators'][$operator]['expected_type'];
if (is_array($type)) {
@@ -532,6 +542,7 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
} else {
if (!$this->valueHasCorrectType($type, $value)) {
$this->isConditionCorrect = false;
+ $this->conditionErrors[] = 'wrong type of condition value';
}
}
}
@@ -553,16 +564,19 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
{
if (isset($conditions['operatorIdentifier'])) {
if (($conditions['operatorIdentifier'] !== 'and') && ($conditions['operatorIdentifier'] !== 'or')) {
+ $this->conditionErrors[] = 'missing operator';
$this->isConditionCorrect = false;
return;
}
if (isset($conditions['operatorIdentifier']['children'])) {
+ $this->conditionErrors[] = 'extra condition';
$this->isConditionCorrect = false;
return;
}
if (!is_array($conditions['children'])) {
+ $this->conditionErrors[] = 'wrong sub-condition type';
$this->isConditionCorrect = false;
return;
}
@@ -616,14 +630,17 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
*/
public function message()
{
- $errorList = [];
- if(!$this->isConditionCorrect){
- $errorList[] = "The logic conditions for ".$this->field['name']." are not complete.";
+ $message = null;
+ if (! $this->isConditionCorrect) {
+ $message = 'The logic conditions for '.$this->field['name'].' are not complete.';
+ } else if (! $this->isActionCorrect) {
+ $message = 'The logic actions for '.$this->field['name'].' are not valid.';
}
- if(!$this->isActionCorrect){
- $errorList[] = "The logic actions for ".$this->field['name']." are not valid.";
+ if (count($this->conditionErrors) > 0) {
+ return $message . ' Error detail(s): '.implode(', ', $this->conditionErrors);
}
- return $errorList;
+
+ return $message;
}
/**
@@ -635,6 +652,9 @@ class FormPropertyLogicRule implements Rule, DataAwareRule
public function setData($data)
{
$this->data = $data;
+ $this->isConditionCorrect = true;
+ $this->isActionCorrect = true;
+ $this->conditionErrors = [];
return $this;
}
diff --git a/app/Service/Forms/FormLogicConditionChecker.php b/app/Service/Forms/FormLogicConditionChecker.php
index d2edd7f..fef7625 100644
--- a/app/Service/Forms/FormLogicConditionChecker.php
+++ b/app/Service/Forms/FormLogicConditionChecker.php
@@ -94,7 +94,7 @@ class FormLogicConditionChecker
return str_starts_with($fieldValue, $condition['value']);
}
- private function checkendsWith ($condition, $fieldValue): bool {
+ private function checkEndsWith ($condition, $fieldValue): bool {
return str_ends_with($fieldValue, $condition['value']);
}
@@ -206,7 +206,7 @@ class FormLogicConditionChecker
case 'starts_with':
return $this->checkStartsWith($propertyCondition, $value);
case 'ends_with':
- return $this->checkendsWith($propertyCondition, $value);
+ return $this->checkEndsWith($propertyCondition, $value);
case 'is_empty':
return $this->checkIsEmpty($propertyCondition, $value);
case 'is_not_empty':
diff --git a/app/Service/Forms/FormLogicPropertyResolver.php b/app/Service/Forms/FormLogicPropertyResolver.php
index 0897525..6e3f6f8 100644
--- a/app/Service/Forms/FormLogicPropertyResolver.php
+++ b/app/Service/Forms/FormLogicPropertyResolver.php
@@ -23,6 +23,11 @@ class FormLogicPropertyResolver
return (new self($property, $values))->shouldBeRequired();
}
+ public static function isHidden(array $property, array $values): bool
+ {
+ return (new self($property, $values))->shouldBeHidden();
+ }
+
public function shouldBeRequired(): bool
{
if(!isset($this->property['required'])){
@@ -42,4 +47,24 @@ class FormLogicPropertyResolver
return $this->property['required'];
}
}
+
+ public function shouldBeHidden(): bool
+ {
+ if (! isset($this->property['hidden'])) {
+ return false;
+ }
+
+ if (!$this->logic) {
+ return $this->property['hidden'];
+ }
+
+ $conditionsMet = FormLogicConditionChecker::conditionsMet($this->logic['conditions'], $this->formData);
+ if ($conditionsMet && $this->property['hidden'] && count($this->logic['actions']) > 0 && in_array('show-block', $this->logic['actions'])) {
+ return false;
+ } elseif ($conditionsMet && !$this->property['hidden'] && count($this->logic['actions']) > 0 && in_array('hide-block', $this->logic['actions'])) {
+ return true;
+ } else {
+ return $this->property['hidden'];
+ }
+ }
}
diff --git a/app/Service/Forms/FormSubmissionFormatter.php b/app/Service/Forms/FormSubmissionFormatter.php
index e4416d4..c13bb25 100644
--- a/app/Service/Forms/FormSubmissionFormatter.php
+++ b/app/Service/Forms/FormSubmissionFormatter.php
@@ -29,8 +29,14 @@ class FormSubmissionFormatter
private $showRemovedFields = false;
+ /**
+ * Logic resolver needs an array id => value, so we create it here
+ */
+ private $idFormData = null;
+
public function __construct(private Form $form, private array $formData)
{
+ $this->initIdFormData();
}
public function createLinks()
@@ -88,9 +94,9 @@ class FormSubmissionFormatter
continue;
}
- // If should hide hidden fields
+ // If hide hidden fields
if (!$this->showHiddenFields) {
- if (isset($field['hidden']) && $field['hidden']) {
+ if (FormLogicPropertyResolver::isHidden($field, $this->idFormData)) {
continue;
}
}
@@ -149,7 +155,7 @@ class FormSubmissionFormatter
// If hide hidden fields
if (!$this->showHiddenFields) {
- if (isset($field['hidden']) && $field['hidden']) {
+ if (FormLogicPropertyResolver::isHidden($field, $this->idFormData)) {
continue;
}
}
@@ -204,4 +210,16 @@ class FormSubmissionFormatter
return $transformedFields;
}
+ private function initIdFormData() {
+ $formProperties = collect($this->form->properties);
+ foreach ($this->formData as $key => $value) {
+ $property = $formProperties->first(function ($item) use ($key) {
+ return $item['id'] == $key;
+ });
+ if ($property) {
+ $this->idFormData[$property['id']] = $value;
+ }
+ }
+ }
+
}
diff --git a/resources/js/components/open/forms/OpenForm.vue b/resources/js/components/open/forms/OpenForm.vue
index 3a4f1d4..4d3e8c2 100644
--- a/resources/js/components/open/forms/OpenForm.vue
+++ b/resources/js/components/open/forms/OpenForm.vue
@@ -256,7 +256,7 @@ export default {
}
if (this.form.editable_submissions && this.form.submission_id) {
- this.dataForm["submission_id"] = this.form.submission_id
+ this.dataForm.submission_id = this.form.submission_id
}
this.$emit('submit', this.dataForm, this.onSubmissionFailure)
@@ -290,18 +290,20 @@ export default {
}
},
async getSubmissionData() {
- if (!this.form || !this.form.editable_submissions || !this.form.submission_id) {
- return null
- }
- const response = await axios.get('/api/forms/' + this.form.slug + '/submissions/' + this.form.submission_id)
- return response.data
+ if (!this.form || !this.form.editable_submissions || !this.form.submission_id) { return null }
+ await this.$store.dispatch('open/records/loadRecord',
+ axios.get('/api/forms/' + this.form.slug + '/submissions/' + this.form.submission_id).then((response) => {
+ return { submission_id: this.form.submission_id, ...response.data.data }
+ })
+ )
+ return this.$store.getters['open/records/getById'](this.form.submission_id)
},
async initForm() {
if (this.isPublicFormPage && this.form.editable_submissions) {
- let urlParam = new URLSearchParams(window.location.search)
+ const urlParam = new URLSearchParams(window.location.search)
if (urlParam && urlParam.get('submission_id')) {
this.form.submission_id = urlParam.get('submission_id')
- const {data} = await this.getSubmissionData()
+ const data = await this.getSubmissionData()
if (data !== null && data) {
this.dataForm = new Form(data)
return
diff --git a/resources/js/pages/forms/show-public.vue b/resources/js/pages/forms/show-public.vue
index 8d50af1..d252881 100644
--- a/resources/js/pages/forms/show-public.vue
+++ b/resources/js/pages/forms/show-public.vue
@@ -34,7 +34,16 @@
+