From 0927383b1572b4f8dbd167a31a7cd9f523017c6d Mon Sep 17 00:00:00 2001 From: Kelsey Martens Date: Fri, 2 Feb 2018 09:28:50 -0600 Subject: [PATCH] v2.0.0-beta.6 --- CHANGELOG.md | 13 +++++ composer.json | 2 +- src/Controllers/ApiController.php | 20 +++---- .../Actions/SetSubmissionStatusAction.php | 7 ++- src/Elements/Submission.php | 2 +- src/Events/Forms/AfterSubmitEvent.php | 44 +++++++++++++++ src/Events/Forms/BeforeSubmitEvent.php | 30 +++++++++++ src/Freeform.php | 18 +++---- .../Composer/Components/AbstractField.php | 53 ++++++++++++++++--- .../Components/Fields/CheckboxField.php | 25 +++++++-- .../Components/Fields/MailingListField.php | 33 +++++++++--- src/Library/Composer/Components/Form.php | 9 +++- src/Library/Database/FormHandlerInterface.php | 19 +++++++ src/Services/FormsService.php | 25 +++++++++ src/templates/settings/_crm_edit.html | 5 +- .../settings/_mailing_list_edit.html | 3 ++ 16 files changed, 263 insertions(+), 45 deletions(-) create mode 100644 src/Events/Forms/AfterSubmitEvent.php create mode 100644 src/Events/Forms/BeforeSubmitEvent.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a82c9e33..9b5689da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Solspace Freeform Changelog +## 2.0.0-beta.6 - 2018-02-02 +### Added +- Added a 'Use Double Opt-in?' setting for MailChimp integrations. +- Added `onBeforeSubmit` and `onAfterSubmit` events. +- Added an optional `renderSingleInput` method to render single Checkbox fields' input without an additional hidden input. + +### Changed +- Changed Mailing List fieldtype `renderInput` to now only output the input field (without a label). + +### Fixed +- Fixed a bug where the chart on Submissions list page inside CP was sometimes not displaying new submissions based on timezone. +- Fixed a bug where permissions weren't allowing Admins to change status of submission(s). + ## 2.0.0-beta.5 - 2018-01-31 ### Fixed - Fixed a bug where the Freeform 1.x to 2.x (Craft 2.x to 3.x) migration path could error in some cases. diff --git a/composer.json b/composer.json index ff98cbd30..a92622186 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "solspace/craft3-freeform", "description": "The most intuitive and powerful form builder for Craft.", - "version": "2.0.0-beta.5", + "version": "2.0.0-beta.6", "type": "craft-plugin", "minimum-stability": "dev", "authors": [ diff --git a/src/Controllers/ApiController.php b/src/Controllers/ApiController.php index fb11c3a0f..59b6b8cee 100644 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/ApiController.php @@ -362,7 +362,7 @@ public function actionGetSubmissionData(): Response $startDate = new Carbon($startDateParam, 'UTC'); $endDate = new Carbon($endDateParam, 'UTC'); - $endDate->modify('+1 day'); + $endDate->setTime(23, 59,59); $intervalUnit = ChartHelper::getRunChartIntervalUnit($startDate, $endDate); @@ -422,19 +422,19 @@ public function actionFinishTutorial(): Response } /** - * @param Query $query - * @param \DateTime $startDate - * @param \DateTime $endDate - * @param string $dateColumn - * @param array $options + * @param Query $query + * @param Carbon $startDate + * @param Carbon $endDate + * @param string $dateColumn + * @param array $options * * @return array * @throws Exception */ private function getRunChartDataFromQuery( Query $query, - \DateTime $startDate, - \DateTime $endDate, + Carbon $startDate, + Carbon $endDate, string $dateColumn, array $options = [] ): array { @@ -524,8 +524,8 @@ private function getRunChartDataFromQuery( // Prepare the query $condition = ['and', "{$dateColumnSql} >= :startDate", "{$dateColumnSql} < :endDate"]; $params = [ - ':startDate' => Db::prepareDateForDb($startDate), - ':endDate' => Db::prepareDateForDb($endDate), + ':startDate' => $startDate->toDateTimeString(), + ':endDate' => $endDate->toDateTimeString(), ]; $orderBy = ['date' => SORT_ASC]; diff --git a/src/Elements/Actions/SetSubmissionStatusAction.php b/src/Elements/Actions/SetSubmissionStatusAction.php index 1dd273211..2337bacd2 100644 --- a/src/Elements/Actions/SetSubmissionStatusAction.php +++ b/src/Elements/Actions/SetSubmissionStatusAction.php @@ -70,18 +70,23 @@ public function performAction(ElementQueryInterface $query): bool $failCount = 0; static $allowedFormIds; + static $isAdmin; if (null === $allowedFormIds) { $allowedFormIds = PermissionHelper::getNestedPermissionIds(Freeform::PERMISSION_SUBMISSIONS_MANAGE); } + if (null === $isAdmin) { + $isAdmin = PermissionHelper::isAdmin(); + } + foreach ($submissions as $submission) { // Skip if there's nothing to change if ((int) $submission->statusId === (int) $this->statusId) { continue; } - if (!\in_array($submission->formId, $allowedFormIds, false)) { + if (!$isAdmin && !\in_array($submission->formId, $allowedFormIds, false)) { continue; } diff --git a/src/Elements/Submission.php b/src/Elements/Submission.php index fb42a8b06..f316ab62f 100644 --- a/src/Elements/Submission.php +++ b/src/Elements/Submission.php @@ -499,7 +499,7 @@ public function getCpEditUrl() $allowedFormIds = PermissionHelper::getNestedPermissionIds(Freeform::PERMISSION_SUBMISSIONS_MANAGE); } - if (!\in_array($this->formId, $allowedFormIds, false)) { + if (!PermissionHelper::isAdmin() && !\in_array($this->formId, $allowedFormIds, false)) { return false; } diff --git a/src/Events/Forms/AfterSubmitEvent.php b/src/Events/Forms/AfterSubmitEvent.php new file mode 100644 index 000000000..42dc64401 --- /dev/null +++ b/src/Events/Forms/AfterSubmitEvent.php @@ -0,0 +1,44 @@ +form = $form; + $this->submission = $submission; + + parent::__construct(); + } + + /** + * @return Form + */ + public function getForm(): Form + { + return $this->form; + } + + /** + * @return Submission|null + */ + public function getSubmission() + { + return $this->submission; + } +} diff --git a/src/Events/Forms/BeforeSubmitEvent.php b/src/Events/Forms/BeforeSubmitEvent.php new file mode 100644 index 000000000..2ea37de52 --- /dev/null +++ b/src/Events/Forms/BeforeSubmitEvent.php @@ -0,0 +1,30 @@ +form = $form; + + parent::__construct(); + } + + /** + * @return Form + */ + public function getForm(): Form + { + return $this->form; + } +} diff --git a/src/Freeform.php b/src/Freeform.php index 34289e6f3..cbea7889a 100644 --- a/src/Freeform.php +++ b/src/Freeform.php @@ -90,15 +90,15 @@ class Freeform extends Plugin const VERSION_CACHE_TIMESTAMP_KEY = 'freeform_version_timestamp'; const VERSION_CACHE_TTL = 86400; // 24-hours - const PERMISSION_FORMS_ACCESS = 'freeform-formsAccess'; - const PERMISSION_FORMS_MANAGE = 'freeform-formsManage'; - const PERMISSION_FIELDS_ACCESS = 'freeform-fieldsAccess'; - const PERMISSION_FIELDS_MANAGE = 'freeform-fieldsManage'; - const PERMISSION_SETTINGS_ACCESS = 'freeform-settingsAccess'; - const PERMISSION_SUBMISSIONS_ACCESS = 'freeform-submissionsAccess'; - const PERMISSION_SUBMISSIONS_MANAGE = 'freeform-submissionsManage'; - const PERMISSION_NOTIFICATIONS_ACCESS = 'freeform-notificationsAccess'; - const PERMISSION_NOTIFICATIONS_MANAGE = 'freeform-notificationsManage'; + const PERMISSION_FORMS_ACCESS = 'freeform-formsAccess'; + const PERMISSION_FORMS_MANAGE = 'freeform-formsManage'; + const PERMISSION_FIELDS_ACCESS = 'freeform-fieldsAccess'; + const PERMISSION_FIELDS_MANAGE = 'freeform-fieldsManage'; + const PERMISSION_SETTINGS_ACCESS = 'freeform-settingsAccess'; + const PERMISSION_SUBMISSIONS_ACCESS = 'freeform-submissionsAccess'; + const PERMISSION_SUBMISSIONS_MANAGE = 'freeform-submissionsManage'; + const PERMISSION_NOTIFICATIONS_ACCESS = 'freeform-notificationsAccess'; + const PERMISSION_NOTIFICATIONS_MANAGE = 'freeform-notificationsManage'; const EVENT_REGISTER_SUBNAV_ITEMS = 'registerSubnavItems'; diff --git a/src/Library/Composer/Components/AbstractField.php b/src/Library/Composer/Components/AbstractField.php index 76acfae43..321819a39 100644 --- a/src/Library/Composer/Components/AbstractField.php +++ b/src/Library/Composer/Components/AbstractField.php @@ -50,15 +50,15 @@ abstract class AbstractField implements FieldInterface, \JsonSerializable /** @var bool */ protected $required = false; - /** @var array */ - protected $errors; - /** @var CustomFieldAttributes */ protected $customAttributes; /** @var int */ protected $pageIndex; + /** @var array */ + protected $errors; + /** @var array */ private $inputClasses; @@ -298,7 +298,7 @@ public function isInputOnly(): bool */ public function isValid(): bool { - $this->errors = $this->validate(); + $this->addErrors($this->validate()); return empty($this->errors); } @@ -306,10 +306,14 @@ public function isValid(): bool /** * Returns an array of error messages * - * @return array|null + * @return array */ - public function getErrors() + public function getErrors(): array { + if (null === $this->errors) { + $this->errors = []; + } + return $this->errors; } @@ -323,6 +327,39 @@ public function hasErrors(): bool return !empty($errors); } + /** + * @param array|null $errors + * + * @return $this + */ + public function addErrors(array $errors = null): AbstractField + { + if (empty($errors)) { + return $this; + } + + $existingErrors = $this->getErrors(); + $existingErrors = array_merge($existingErrors, $errors); + + $existingErrors = array_unique($existingErrors); + + $this->errors = $existingErrors; + + return $this; + } + + /** + * @param string $error + * + * @return $this + */ + public function addError(string $error): AbstractField + { + $this->addErrors([$error]); + + return $this; + } + /** * Return the field TYPE * @@ -639,7 +676,7 @@ protected function onAfterInputHtml(): string */ protected function validate(): array { - $errors = []; + $errors = $this->getErrors(); if ($this instanceof ObscureValueInterface) { $value = $this->getActualValue($this->getValue()); @@ -700,7 +737,7 @@ protected function translate(string $string = null, array $variables = []): stri * * @return \Twig_Markup */ - private function renderRaw($output): \Twig_Markup + protected function renderRaw($output): \Twig_Markup { return Template::raw($output); } diff --git a/src/Library/Composer/Components/Fields/CheckboxField.php b/src/Library/Composer/Components/Fields/CheckboxField.php index 64080ac0d..acd57228a 100644 --- a/src/Library/Composer/Components/Fields/CheckboxField.php +++ b/src/Library/Composer/Components/Fields/CheckboxField.php @@ -84,16 +84,27 @@ public function setIsCheckedByPost(bool $isChecked) public function getInputHtml(): string { $attributes = $this->getCustomAttributes(); - $output = ''; - $output .= 'getAttributeString('name', $this->getHandle()) . $this->getAttributeString('type', FieldInterface::TYPE_HIDDEN) . $this->getAttributeString('value', '', false) . $attributes->getInputAttributesAsString() . '/>'; - $output .= 'getSingleInputHtml(); + + return $output; + } + + /** + * @return string + */ + public function getSingleInputHtml(): string + { + $attributes = $this->getCustomAttributes(); + + return 'getAttributeString('name', $this->getHandle()) . $this->getAttributeString('type', $this->getType()) . $this->getAttributeString('id', $this->getIdAttribute()) @@ -103,8 +114,14 @@ public function getInputHtml(): string . $attributes->getInputAttributesAsString() . ($this->isChecked() ? 'checked ' : '') . '/>'; + } - return $output; + /** + * @return \Twig_Markup + */ + public function renderSingleInput(): \Twig_Markup + { + return $this->renderRaw($this->getSingleInputHtml()); } /** diff --git a/src/Library/Composer/Components/Fields/MailingListField.php b/src/Library/Composer/Components/Fields/MailingListField.php index 426798921..27fec9948 100644 --- a/src/Library/Composer/Components/Fields/MailingListField.php +++ b/src/Library/Composer/Components/Fields/MailingListField.php @@ -65,15 +65,9 @@ public function getMapping(): array public function getInputHtml(): string { $attributes = $this->getCustomAttributes(); - $output = ''; - $isSelected = (bool)$this->getValue(); - $output .= 'getAttributeString('class', $attributes->getLabelClass()) - . '>'; - - $output .= 'getAttributeString('name', $this->getHash()) . $this->getAttributeString('type', 'checkbox') . $this->getAttributeString('id', $this->getIdAttribute()) @@ -83,7 +77,30 @@ public function getInputHtml(): string . $attributes->getInputAttributesAsString() . ($isSelected ? 'checked ' : '') . '/>'; - $output .= $this->getLabel(); + } + + /** + * Output something before an input HTML is output + * + * @return string + */ + protected function onBeforeInputHtml(): string + { + $attributes = $this->getCustomAttributes(); + + return 'getAttributeString('class', $attributes->getLabelClass()) + . '>'; + } + + /** + * Output something after an input HTML is output + * + * @return string + */ + protected function onAfterInputHtml(): string + { + $output = $this->getLabel(); $output .= ''; return $output; diff --git a/src/Library/Composer/Components/Form.php b/src/Library/Composer/Components/Form.php index f5e404da5..f16d2e1eb 100644 --- a/src/Library/Composer/Components/Form.php +++ b/src/Library/Composer/Components/Form.php @@ -436,6 +436,7 @@ public function isSubmittedSuccessfully(): bool public function submit() { $formValueContext = $this->getFormValueContext(); + $onBeforeSave = $this->formHandler->onBeforeSubmit($this); if ($formValueContext->shouldFormWalkToPreviousPage()) { $formValueContext->retreatToPreviousPage(); @@ -466,7 +467,11 @@ public function submit() $formValueContext->appendStoredValues($submittedValues); - if ($formValueContext->getCurrentPageIndex() === (count($this->getPages()) - 1)) { + if ($formValueContext->getCurrentPageIndex() === (\count($this->getPages()) - 1)) { + if (!$onBeforeSave) { + return false; + } + if ($this->storeData) { $submission = $this->saveStoredStateToDatabase(); } else { @@ -480,6 +485,8 @@ public function submit() $formValueContext->cleanOutCurrentSession(); + $this->formHandler->onAfterSubmit($this, $submission); + return $submission; } diff --git a/src/Library/Database/FormHandlerInterface.php b/src/Library/Database/FormHandlerInterface.php index d574d838f..ba5a2fcb0 100644 --- a/src/Library/Database/FormHandlerInterface.php +++ b/src/Library/Database/FormHandlerInterface.php @@ -11,6 +11,7 @@ namespace Solspace\freeform\Library\Database; +use Solspace\Freeform\Elements\Submission; use Solspace\Freeform\Library\Composer\Components\Form; interface FormHandlerInterface @@ -48,4 +49,22 @@ public function addScriptsToPage(Form $form); * @return string */ public function getScriptOutput(Form $form): string; + + /** + * Do something before the form is saved + * Return bool determines whether the form should be saved or not + * + * @param Form $form + * + * @return bool + */ + public function onBeforeSubmit(Form $form): bool; + + /** + * Do something after the form is saved + * + * @param Form $form + * @param Submission|null $submission + */ + public function onAfterSubmit(Form $form, Submission $submission = null); } diff --git a/src/Services/FormsService.php b/src/Services/FormsService.php index 661175e10..12d33ad9f 100644 --- a/src/Services/FormsService.php +++ b/src/Services/FormsService.php @@ -14,6 +14,9 @@ use craft\db\Query; use craft\helpers\Template; use Solspace\Commons\Helpers\PermissionHelper; +use Solspace\Freeform\Elements\Submission; +use Solspace\Freeform\Events\Forms\AfterSubmitEvent; +use Solspace\Freeform\Events\Forms\BeforeSubmitEvent; use Solspace\Freeform\Events\Forms\DeleteEvent; use Solspace\Freeform\Events\Forms\SaveEvent; use Solspace\Freeform\Freeform; @@ -31,6 +34,8 @@ class FormsService extends Component implements FormHandlerInterface { + const EVENT_BEFORE_SUBMIT = 'beforeSubmit'; + const EVENT_AFTER_SUBMIT = 'afterSubmit'; const EVENT_BEFORE_SAVE = 'beforeSave'; const EVENT_AFTER_SAVE = 'afterSave'; const EVENT_BEFORE_DELETE = 'beforeDelete'; @@ -496,6 +501,26 @@ public function getScriptOutput(Form $form): string return $output; } + /** + * @inheritDoc + */ + public function onBeforeSubmit(Form $form): bool + { + $event = new BeforeSubmitEvent($form); + $this->trigger(self::EVENT_BEFORE_SUBMIT, $event); + + return $event->isValid; + } + + /** + * @inheritDoc + */ + public function onAfterSubmit(Form $form, Submission $submission = null) + { + $event = new AfterSubmitEvent($form, $submission); + $this->trigger(self::EVENT_AFTER_SUBMIT, $event); + } + /** * @return Query */ diff --git a/src/templates/settings/_crm_edit.html b/src/templates/settings/_crm_edit.html index 4d08e69bc..089756721 100644 --- a/src/templates/settings/_crm_edit.html +++ b/src/templates/settings/_crm_edit.html @@ -133,10 +133,11 @@

{{ blockTitle }}

{% if setting.type == "text" %} {{ forms.textField(options) }} - {% elseif setting.type == "password" %} - {{ forms.passwordField(options) }} {% elseif setting.type == "bool" %} + {% set options = options|merge({on: value, value: 1}) %} {{ forms.lightswitchField(options) }} + {% elseif setting.type == "password" %} + {{ forms.passwordField(options) }} {% endif %} {% endfor %} diff --git a/src/templates/settings/_mailing_list_edit.html b/src/templates/settings/_mailing_list_edit.html index cdd072d27..6dc1460be 100644 --- a/src/templates/settings/_mailing_list_edit.html +++ b/src/templates/settings/_mailing_list_edit.html @@ -124,6 +124,9 @@

{{ blockTitle }}

{% if setting.type == "text" %} {{ forms.textField(options) }} + {% elseif setting.type == "bool" %} + {% set options = options|merge({on: value, value: 1}) %} + {{ forms.lightswitchField(options) }} {% elseif setting.type == "password" %} {{ forms.passwordField(options) }} {% endif %}