From b1d2ef9977127a698ecd81bf5bc1b9c3084aeac7 Mon Sep 17 00:00:00 2001 From: Merel Jossart Date: Mon, 17 Jul 2023 22:26:37 +0200 Subject: [PATCH] [Forms] add custom status field to form and allow for filtering in backend --- src/elements/Form.php | 69 ++++++++++--- src/elements/actions/SetFormStatus.php | 97 +++++++++++++++++++ src/elements/db/FormQuery.php | 26 ++++- .../m230716_000000_add_status_to_form.php | 34 +++++++ src/services/Forms.php | 7 +- .../actions/form-set-status/trigger.html | 9 ++ src/templates/forms/_panes/settings.html | 21 ++++ 7 files changed, 246 insertions(+), 17 deletions(-) create mode 100644 src/elements/actions/SetFormStatus.php create mode 100644 src/migrations/m230716_000000_add_status_to_form.php create mode 100644 src/templates/_components/actions/form-set-status/trigger.html diff --git a/src/elements/Form.php b/src/elements/Form.php index 3781ace06..047147c96 100644 --- a/src/elements/Form.php +++ b/src/elements/Form.php @@ -1,4 +1,5 @@ Craft::t('app', 'Active'), + self::STATUS_INACTIVE => Craft::t('app', 'Inactive'), + ]; + } + /** * @inheritDoc */ @@ -155,7 +173,7 @@ public static function defineSources(string $context = null): array return $sources; } - + /** * @inheritDoc */ @@ -196,7 +214,6 @@ protected static function defineFieldLayouts(string $source): array protected static function defineActions(string $source = null): array { $actions = []; - $canDeleteForms = Craft::$app->getUser()->checkPermission('formie-deleteForms'); $actions[] = DuplicateForm::class; @@ -215,6 +232,10 @@ protected static function defineActions(string $source = null): array 'partialSuccessMessage' => Craft::t('formie', 'Some forms restored.'), 'failMessage' => Craft::t('formie', 'Forms not restored.'), ]; + $actions[] = [ + 'type' => SetFormStatus::class, + 'statuses' => self::STATUSES, + ]; return $actions; } @@ -228,6 +249,7 @@ protected static function defineTableAttributes(): array 'title' => ['label' => Craft::t('app', 'Title')], 'id' => ['label' => Craft::t('app', 'ID')], 'handle' => ['label' => Craft::t('app', 'Handle')], + 'formStatus' => ['label' => Craft::t('formie', 'Status')], 'template' => ['label' => Craft::t('app', 'Template')], 'usageCount' => ['label' => Craft::t('formie', 'Usage Count')], 'dateCreated' => ['label' => Craft::t('app', 'Date Created')], @@ -288,6 +310,7 @@ protected static function defineSortOptions(): array // Properties // ========================================================================= + private ?string $formStatus = self::STATUS_ACTIVE; public ?string $handle = null; public ?string $oldHandle = null; public ?string $fieldContentTable = null; @@ -385,7 +408,7 @@ public function behaviors(): array return $behaviors; } - + /** * @inheritdoc */ @@ -555,6 +578,23 @@ public function getDefaultStatus(): ?Status return $this->_defaultStatus; } + public function setFormStatus(string $status = null): void + { + if ($status !== null) { + $this->formStatus = $status; + } + } + + public function getFormStatus(): string + { + return $this->formStatus ?? self::STATUS_ACTIVE; + } + + public function getStatus(): string + { + return $this->getFormStatus(); + } + /** * Sets the default status. * @@ -1296,7 +1336,7 @@ public function getPageFieldErrors($submission): array public function renderTemplate(array|string $components, array $variables = []): string { $view = Craft::$app->getView(); - + // Normalise the components to allow for a single component if (!is_array($components)) { $components = [$components]; @@ -1579,7 +1619,7 @@ public function defineHtmlTag(string $key, array $context = []): ?HtmlTag $page = $context['page'] ?? null; $inputAttributes = $page->settings->getInputAttributes() ?? []; $saveButtonStyle = $page->settings->saveButtonStyle ?? 'link'; - + return new HtmlTag('button', [ 'class' => [ 'fui-btn fui-save', @@ -1899,7 +1939,7 @@ public function setSettings($settings, $updateSnapshot = true): void public function setFieldSettings($handle, $settings, $updateSnapshot = true): void { $field = null; - + // Check for nested fields so we can use `group.dropdown` or `dropdown`. $handles = explode('.', $handle); @@ -1930,7 +1970,7 @@ public function setIntegrationSettings(string $handle, array $settings, $updateS { // Get the integration settings so we only override what we want $integrationSettings = $this->settings->integrations[$handle] ?? []; - + // Update the integration settings $this->settings->integrations[$handle] = array_merge($integrationSettings, $settings); @@ -2018,7 +2058,7 @@ public function isBeforeSchedule(): bool if ($this->settings->scheduleForm && $this->settings->scheduleFormStart) { return !DateTimeHelper::isInThePast($this->settings->scheduleFormStart); } - + return false; } @@ -2027,7 +2067,7 @@ public function isAfterSchedule(): bool if ($this->settings->scheduleForm && $this->settings->scheduleFormEnd) { return DateTimeHelper::isInThePast($this->settings->scheduleFormEnd); } - + return false; } @@ -2065,7 +2105,7 @@ public function isWithinSubmissionsLimit(): bool return false; } } - + return true; } @@ -2149,6 +2189,7 @@ public function afterSave(bool $isNew): void $record->handle = $this->handle; $record->fieldContentTable = $this->fieldContentTable; + $record->formStatus = $this->formStatus; $record->settings = $this->settings; $record->templateId = $this->templateId; $record->submitActionEntryId = $this->submitActionEntryId; @@ -2286,7 +2327,7 @@ protected function defineRules(): array ]; $rules[] = [ - 'handle', function($attribute, $params, Validator $validator): void { + 'handle', function ($attribute, $params, Validator $validator): void { $query = static::find()->handle($this->$attribute); if ($this->id) { $query = $query->id("not {$this->id}"); diff --git a/src/elements/actions/SetFormStatus.php b/src/elements/actions/SetFormStatus.php new file mode 100644 index 000000000..eb225bca8 --- /dev/null +++ b/src/elements/actions/SetFormStatus.php @@ -0,0 +1,97 @@ +getView()->renderTemplate('formie/_components/actions/form-set-status/trigger', [ + 'statuses' => $this->statuses, + ]); + } + + /** + * @inheritdoc + */ + public function performAction(ElementQueryInterface $query): bool + { + $elementsService = Craft::$app->getElements(); + + $elements = $query->all(); + $failCount = 0; + + /** @var Form $element */ + foreach ($elements as $element) { + if ($element) { + $element->setFormStatus($this->formStatus); + + if ($elementsService->saveElement($element) === false) { + Formie::error('Unable to set status: {error}', ['error' => Json::encode($element->getErrors())]); + + // Validation error + $failCount++; + } + } + } + + // Did all of them fail? + if ($failCount === count($elements)) { + if (count($elements) === 1) { + $this->setMessage(Craft::t('app', 'Could not update status due to a validation error.')); + } else { + $this->setMessage(Craft::t('app', 'Could not update statuses due to validation errors.')); + } + + return false; + } + + if ($failCount !== 0) { + $this->setMessage(Craft::t('app', 'Status updated, with some failures due to validation errors.')); + } else if (count($elements) === 1) { + $this->setMessage(Craft::t('app', 'Status updated.')); + } else { + $this->setMessage(Craft::t('app', 'Statuses updated.')); + } + + return true; + } + + + // Protected Methods + // ========================================================================= + + protected function defineRules(): array + { + // Don't include the parent rules from `SetStatus` + $rules = []; + + $rules[] = [['formStatus'], 'required']; + $rules[] = [['formStatus'], 'in', 'range' => $this->statuses]; + + return $rules; + } +} diff --git a/src/elements/db/FormQuery.php b/src/elements/db/FormQuery.php index 806da0198..96e94fe4c 100644 --- a/src/elements/db/FormQuery.php +++ b/src/elements/db/FormQuery.php @@ -1,8 +1,9 @@ SORT_DESC]; @@ -50,6 +52,23 @@ public function templateId($value): static return $this; } + public function status(array|string|null $value): static + { + $this->formStatus = $value; + + return $this; + } + + + protected function statusCondition(string $status): mixed + { + if (in_array($status, FORM::STATUSES, true)) { + return ['formie_forms.formStatus' => $status]; + } + + return []; + } + // Protected Methods // ========================================================================= @@ -60,6 +79,7 @@ protected function beforePrepare(): bool $this->query->select([ 'formie_forms.id', + 'formie_forms.formStatus', 'formie_forms.handle', 'formie_forms.fieldContentTable', 'formie_forms.settings', @@ -83,6 +103,10 @@ protected function beforePrepare(): bool $this->subQuery->andWhere(Db::parseParam('formie_forms.templateId', $this->templateId)); } + if ($this->formStatus) { + $this->subQuery->andWhere(Db::parseParam('formie_forms.formStatus', $this->formStatus)); + } + return parent::beforePrepare(); } } diff --git a/src/migrations/m230716_000000_add_status_to_form.php b/src/migrations/m230716_000000_add_status_to_form.php new file mode 100644 index 000000000..672f10ea4 --- /dev/null +++ b/src/migrations/m230716_000000_add_status_to_form.php @@ -0,0 +1,34 @@ +db->columnExists('{{%formie_forms}}', 'formStatus')) { + $this->addColumn('{{%formie_forms}}', 'formStatus', $this->string()); + $this->update('{{%formie_forms}}', ['formStatus' => Form::STATUS_ACTIVE]); + } + + return true; + } + + /** + * @inheritdoc + */ + public function safeDown(): bool + { + if ($this->db->columnExists('{{%formie_forms}}', 'formStatus')) { + $this->dropColumn('{{%formie_forms}}', 'formStatus'); + } + + return true; + } +} diff --git a/src/services/Forms.php b/src/services/Forms.php index 3c1444d4a..bf6129291 100644 --- a/src/services/Forms.php +++ b/src/services/Forms.php @@ -1,4 +1,5 @@ settings->integrations[$captcha->handle]['enabled'] ?? null; - + if ($captcha->getEnabled() && $integrationEnabled === null) { $form->settings->integrations[$captcha->handle]['enabled'] = true; } @@ -261,7 +262,7 @@ public function saveForm(Form $form, bool $runValidation = true): bool $transaction->rollBack(); $form->addErrors(['general' => $e->getMessage()]); - + Formie::error('Unable to save form “' . $form->handle . '”: ' . $e->getMessage()); return false; @@ -380,6 +381,7 @@ public function buildFormFromPost(): Form $request = Craft::$app->getRequest(); $formId = $request->getParam('formId'); $siteId = $request->getParam('siteId'); + $status = $request->getParam('status'); $duplicate = (bool)$request->getParam('duplicate'); if ($formId) { @@ -406,6 +408,7 @@ public function buildFormFromPost(): Form } $form->siteId = $siteId ?? $form->siteId; + $form->setFormStatus($status ?? $form->getFormStatus()); $form->handle = $request->getParam('handle', $form->handle); $form->templateId = \verbb\formie\helpers\StringHelper::toId($request->getParam('templateId', $form->templateId)); $form->defaultStatusId = \verbb\formie\helpers\StringHelper::toId($request->getParam('defaultStatusId', $form->defaultStatusId)); diff --git a/src/templates/_components/actions/form-set-status/trigger.html b/src/templates/_components/actions/form-set-status/trigger.html new file mode 100644 index 000000000..cf06e9ecc --- /dev/null +++ b/src/templates/_components/actions/form-set-status/trigger.html @@ -0,0 +1,9 @@ + + diff --git a/src/templates/forms/_panes/settings.html b/src/templates/forms/_panes/settings.html index 1ee2cff8e..cdeef336f 100644 --- a/src/templates/forms/_panes/settings.html +++ b/src/templates/forms/_panes/settings.html @@ -17,6 +17,27 @@

{{ 'Form Settings' | t('formie') }}


+{{ forms.selectField({ + label: 'Status' | t('formie'), + instructions: 'What is the current status of this form' | t('formie'), + id: 'status', + name: 'status', + required: true, + options: [ + { + label: 'Active' | t('formie'), + value: 'active', + }, + { + label: 'Inactive' | t('formie'), + value: 'inactive', + }, + ], + value: form.getFormStatus(), +}) }} + +
+

{{ 'Submissions' | t('formie') }}

{{ forms.hidden({