Handle global form error message directly in form builder API
authorMatthias Schmidt <gravatronics@live.com>
Sat, 16 Mar 2019 14:44:54 +0000 (15:44 +0100)
committerMatthias Schmidt <gravatronics@live.com>
Sat, 16 Mar 2019 14:44:54 +0000 (15:44 +0100)
This way, there is no need to include the `formError` template manually anymore.

See #2509

com.woltlab.wcf/templates/__form.tpl
wcfsetup/install/files/acp/templates/__form.tpl
wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl
wcfsetup/install/files/acp/templates/devtoolsProjectPipEntryAdd.tpl
wcfsetup/install/files/acp/templates/languageItemAdd.tpl
wcfsetup/install/files/acp/templates/reactionTypeAdd.tpl
wcfsetup/install/files/lib/form/AbstractForm.class.php
wcfsetup/install/files/lib/form/AbstractFormBuilderForm.class.php
wcfsetup/install/files/lib/system/form/builder/FormDocument.class.php
wcfsetup/install/files/lib/system/form/builder/IFormDocument.class.php

index 0d9f06e45aa7ac8da84aebaa9af64c44efb9446b..bacb6c0db45ea7fd15e30cb39edd07b6d9addb4b 100644 (file)
@@ -5,6 +5,10 @@
        });
 </script>
 
+{if $form->hasValidationErrors() && $form->showsErrorMessage()}
+       <p class="error" role="alert">{@$form->getErrorMessage()}</p>
+{/if}
+
 {if $form->isAjax()}
        <section id="{@$form->getId()}"{*
                *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
                        {/foreach}
                </div>
        {/if}
-       
-       {@SECURITY_TOKEN_INPUT_TAG}
+
 {if $form->isAjax()}
        </section>
 {else}
+               {@SECURITY_TOKEN_INPUT_TAG}
        </form>
 {/if}
 
index 0d9f06e45aa7ac8da84aebaa9af64c44efb9446b..bacb6c0db45ea7fd15e30cb39edd07b6d9addb4b 100644 (file)
@@ -5,6 +5,10 @@
        });
 </script>
 
+{if $form->hasValidationErrors() && $form->showsErrorMessage()}
+       <p class="error" role="alert">{@$form->getErrorMessage()}</p>
+{/if}
+
 {if $form->isAjax()}
        <section id="{@$form->getId()}"{*
                *}{if !$form->getClasses()|empty} class="{implode from=$form->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
                        {/foreach}
                </div>
        {/if}
-       
-       {@SECURITY_TOKEN_INPUT_TAG}
+
 {if $form->isAjax()}
        </section>
 {else}
+               {@SECURITY_TOKEN_INPUT_TAG}
        </form>
 {/if}
 
index c0bde7f525e8eeff8ea87d9c6055d80933768083..8150f51d301395230f6b86012934e1c98ad955ce 100644 (file)
@@ -20,8 +20,6 @@
        <p class="warning">{lang}wcf.acp.devtools.project.edit.warning{/lang}</p>
 {/if}
 
-{include file='formError'}
-
 {if $success|isset}
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
index ecf5b5eda99d51075c07e27f176b8cbd7af330e1..b5b59f9ddc2bfbf614e3620400457456b347bd43 100644 (file)
@@ -16,8 +16,6 @@
        </nav>
 </header>
 
-{include file='formError'}
-
 {if $success|isset}
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
index eb1487a0c466b08a1e4774e190984f2f2ce751f7..f8e560276167d338e90d2992aad40d28c05fec66 100644 (file)
@@ -14,8 +14,6 @@
        </nav>
 </header>
 
-{include file='formError'}
-
 {if $success|isset}
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
index 62a6a9182954094d97122f276e14bc7e55439d66..5b13f86a773c0adb3e90e10ac44522c43d65970d 100644 (file)
@@ -14,8 +14,6 @@
        </nav>
 </header>
 
-{include file='formError'}
-
 {if $success|isset}
        <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
 {/if}
index 0d6ad3ec3e3e96123a97f86204cc05314bb77e4f..cc1e898dc81b7f507a829c1732da9842343f09ce 100644 (file)
@@ -84,6 +84,16 @@ abstract class AbstractForm extends AbstractPage implements IForm {
                // call validate event
                EventHandler::getInstance()->fireAction($this, 'validate');
                
+               $this->validateSecurityToken();
+       }
+       
+       /**
+        * Validates the form security token.
+        * 
+        * @throws      UserInputException      if the security token is invalid
+        * @since       5.2
+        */
+       protected function validateSecurityToken() {
                if (!isset($_POST['t']) || !WCF::getSession()->checkSecurityToken($_POST['t'])) {
                        throw new UserInputException('__securityToken');
                }
index bc51930f04311de8dbc80f4d40e4077771bd0da9..179c563a6bd09d9eb5c351a3398f5f377ffc2748 100644 (file)
@@ -224,4 +224,11 @@ abstract class AbstractFormBuilderForm extends AbstractForm {
                        throw new UserInputException($this->form->getPrefixedId());
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function validateSecurityToken() {
+               // does nothing, is handled by `IFormDocument` object
+       }
 }
index c137720ab7e3979259329652f306925a7e55a7a8..1c9b724c700e7da93fe89e4236a1acc4bc545953 100644 (file)
@@ -27,7 +27,9 @@ class FormDocument implements IFormDocument {
        use TFormNode;
        use TFormParentNode {
                TFormParentNode::cleanup insteadof TFormNode;
-               readValues as protected defaultReadValues;
+               hasValidationErrors as protected traitHasValidationErrors;
+               readValues as protected traitReadValues;
+               validate as protected traitValidate;
        }
        
        /**
@@ -67,12 +69,24 @@ class FormDocument implements IFormDocument {
         */
        protected $enctype = '';
        
+       /**
+        * global form error message
+        * @var null|string
+        */
+       protected $errorMessage;
+       
        /**
         * is `true` if form document has already been built and is `false` otherwise
         * @var bool
         */
        protected $isBuilt = false;
        
+       /**
+        * is `true` if form document is in invalid due to external factors and is `false` otherwise
+        * @var boolean
+        */
+       protected $invalid = false;
+       
        /**
         * form mode (see `self::FORM_MODE_*` constants)
         * @var null|string
@@ -98,6 +112,12 @@ class FormDocument implements IFormDocument {
         */
        protected $requestData;
        
+       /**
+        * TODO
+        * @var boolean
+        */
+       protected $showErrorMessage = true;
+       
        /**
         * Cleans up the form document before the form document object is destroyed.
         */
@@ -207,10 +227,33 @@ class FormDocument implements IFormDocument {
                        FormButton::create('submitButton')
                                ->label('wcf.global.button.submit')
                                ->accessKey('s')
+                               ->submit(!$this->isAjax())
                                ->addClass('buttonPrimary')
                );
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function errorMessage($languageItem = null, array $variables = []) {
+               if ($languageItem === null) {
+                       if (!empty($variables)) {
+                               throw new \InvalidArgumentException("Cannot use variables when unsetting error element of form '{$this->getId()}'");
+                       }
+                       
+                       $this->errorMessage = null;
+               }
+               else {
+                       if (!is_string($languageItem)) {
+                               throw new \InvalidArgumentException("Given error message language item is no string, " . gettype($languageItem) . " given.");
+                       }
+                       
+                       $this->errorMessage = WCF::getLanguage()->getDynamicVariable($languageItem, $variables);
+               }
+               
+               return $this;
+       }
+       
        /**
         * @inheritDoc
         */
@@ -292,6 +335,17 @@ class FormDocument implements IFormDocument {
                return $this->enctype;
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function getErrorMessage() {
+               if ($this->errorMessage === null) {
+                       $this->errorMessage = WCF::getLanguage()->getDynamicVariable('wcf.global.form.error');
+               }
+               
+               return $this->errorMessage;
+       }
+       
        /**
         * @inheritDoc
         */
@@ -358,6 +412,13 @@ class FormDocument implements IFormDocument {
                return $this->addDefaultButton;
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function hasValidationErrors() {
+               return $this->isInvalid() || $this->traitHasValidationErrors();
+       }
+       
        /**
         * @inheritDoc
         */
@@ -371,6 +432,15 @@ class FormDocument implements IFormDocument {
                return !empty($requestData);
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function invalid($invalid = true) {
+               $this->invalid = $invalid;
+               
+               return $this;
+       }
+       
        /**
         * @inheritDoc
         */
@@ -378,6 +448,13 @@ class FormDocument implements IFormDocument {
                return $this->ajax;
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function isInvalid() {
+               return $this->invalid;
+       }
+       
        /**
         * @inheritDoc
         */
@@ -445,7 +522,7 @@ class FormDocument implements IFormDocument {
                        $this->requestData = $_POST;
                }
                
-               return $this->defaultReadValues();
+               return $this->traitReadValues();
        }
        
        /**
@@ -460,4 +537,35 @@ class FormDocument implements IFormDocument {
                
                return $this;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function showErrorMessage($showErrorMessage = true) {
+               $this->showErrorMessage = $showErrorMessage;
+               
+               return $this;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function showsErrorMessage() {
+               return $this->showErrorMessage;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               // check security token
+               if (!isset($_POST['t']) || !WCF::getSession()->checkSecurityToken($_POST['t'])) {
+                       $this->invalid();
+                       
+                       $this->errorMessage('wcf.global.form.error.securityToken');
+               }
+               else {
+                       $this->traitValidate();
+               }
+       }
 }
index ba9314b8ce84c5ab8ab68ecfbd5dd9b9d8f8e80a..8491729104e1d8d782d3863152ad615db3e3f766 100644 (file)
@@ -75,6 +75,22 @@ interface IFormDocument extends IFormParentNode {
         * @throws      \BadMethodCallException         if this document has already been built
         */
        public function build();
+
+       /**
+        * Sets the error message of this form using the given language item and returns this
+        * document. If `null` is passed, the error message is unset.
+        *
+        * Unsetting the current error message causes `IFormDocument::getErrorMessage()` to
+        * return the default error message.
+        * 
+        * @param       null|string     $languageItem   language item containing the error message or `null` to unset error message
+        * @param       array           $variables      additional variables used when resolving the language item
+        *
+        * @return      static                          this document
+        * 
+        * @throws      \InvalidArgumentException       if the given form mode is invalid
+        */
+       public function errorMessage($languageItem = null, array $variables = []);
        
        /**
         * Sets the form mode (see `self::FORM_MODE_*` constants).
@@ -132,6 +148,18 @@ interface IFormDocument extends IFormParentNode {
         */
        public function getEnctype();
        
+       /**
+        * Returns the error message for the whole form.
+        * 
+        * By default, `wcf.global.form.error` in the active user's language is returned.
+        * This method always returns the error message! To check, if the error message should
+        * be displayed, use `IParentFormNode::hasValidationErrors()` and
+        * `IFormDocument::showsErrorMessage()`.
+        * 
+        * @return      string
+        */
+       public function getErrorMessage();
+       
        /**
         * Returns the form mode (see `self::FORM_MODE_*` constants).
         *
@@ -191,7 +219,7 @@ interface IFormDocument extends IFormParentNode {
         * If no request data is set, `$_POST` will be set as the request data.
         * 
         * @param       null|string     $index          array index of the returned data
-        * @return      bool                            `tu
+        * @return      bool
         */
        public function hasRequestData($index = null);
        
@@ -205,6 +233,24 @@ interface IFormDocument extends IFormParentNode {
         */
        public function isAjax();
        
+       /**
+        * Returns `true` if the form document is in invalid due to external factors and is `false`
+        * otherwise.
+        * 
+        * By default, the form document is not invalid.
+        * 
+        * @return      boolean
+        */
+       public function isInvalid();
+       
+       /**
+        * Sets if the form document is in invalid due to external factors.
+        * 
+        * @param       boolean         $invalid
+        * @return      static                          this document
+        */
+       public function invalid($invalid = true);
+       
        /**
         * Loads the field values from the given object and returns this document.
         * 
@@ -244,9 +290,27 @@ interface IFormDocument extends IFormParentNode {
         * Sets the request data of the form's fields.
         * 
         * @param       array           $requestData    request data of the form's fields
-        * @return      static                          this field
+        * @return      static                          this document
         * 
         * @throws      \BadMethodCallException         if request data has already been set
         */
        public function requestData(array $requestData);
+       
+       /**
+        * Sets if the global form error message should be shown if the form has validation errors.
+        * 
+        * @param       boolean         $showErrorMessage
+        * @return      static                                  this document
+        */
+       public function showErrorMessage($showErrorMessage = true);
+       
+       /**
+        * Returns `true` if the global form error message should be shown if the form has validation
+        * errors.
+        * 
+        * By default, the global form error message is shown.
+        * 
+        * @return      boolean
+        */
+       public function showsErrorMessage();
 }