Hoist validation errors from hidden fields to the top of the form
[lhc/web/wiklou.git] / includes / htmlform / HTMLForm.php
index 43cac88..11eb04d 100644 (file)
  *    'size'                -- the length of text fields
  *    'filter-callback      -- a function name to give you the chance to
  *                             massage the inputted value before it's processed.
- *                             @see HTMLForm::filter()
+ *                             @see HTMLFormField::filter()
  *    'validation-callback' -- a function name to give you the chance
  *                             to impose extra validation on the field input.
- *                             @see HTMLForm::validate()
+ *                             @see HTMLFormField::validate()
  *    'name'                -- By default, the 'name' attribute of the input field
  *                             is "wp{$fieldname}".  If you want a different name
  *                             (eg one without the "wp" prefix), specify it here and
@@ -153,6 +153,8 @@ class HTMLForm extends ContextSource {
                'email' => 'HTMLTextField',
                'password' => 'HTMLTextField',
                'url' => 'HTMLTextField',
+               'title' => 'HTMLTitleTextField',
+               'user' => 'HTMLUserTextField',
        );
 
        public $mFieldData;
@@ -165,7 +167,7 @@ class HTMLForm extends ContextSource {
        protected $mFieldTree;
        protected $mShowReset = false;
        protected $mShowSubmit = true;
-       protected $mSubmitFlag = 'constructive';
+       protected $mSubmitFlags = array( 'constructive', 'primary' );
 
        protected $mSubmitCallback;
        protected $mValidationErrorMessage;
@@ -535,6 +537,12 @@ class HTMLForm extends ContextSource {
         *       params) or strings (message keys)
         */
        function trySubmit() {
+               $valid = true;
+               $hoistedErrors = array();
+               $hoistedErrors[] = isset( $this->mValidationErrorMessage )
+                       ? $this->mValidationErrorMessage
+                       : array( 'htmlform-invalid-input' );
+
                $this->mWasSubmitted = true;
 
                # Check for cancelled submission
@@ -556,15 +564,20 @@ class HTMLForm extends ContextSource {
                        if ( $field->isHidden( $this->mFieldData ) ) {
                                continue;
                        }
-                       if ( $field->validate(
-                                       $this->mFieldData[$fieldname],
-                                       $this->mFieldData )
-                               !== true
-                       ) {
-                               return isset( $this->mValidationErrorMessage )
-                                       ? $this->mValidationErrorMessage
-                                       : array( 'htmlform-invalid-input' );
+                       $res = $field->validate( $this->mFieldData[$fieldname], $this->mFieldData );
+                       if ( $res !== true ) {
+                               $valid = false;
+                               if ( $res !== false && !$field->canDisplayErrors() ) {
+                                       $hoistedErrors[] = array( 'rawmessage', $res );
+                               }
+                       }
+               }
+
+               if ( !$valid ) {
+                       if ( count( $hoistedErrors ) === 1 ) {
+                               $hoistedErrors = $hoistedErrors[0];
                        }
+                       return $hoistedErrors;
                }
 
                $callback = $this->mSubmitCallback;
@@ -708,6 +721,21 @@ class HTMLForm extends ContextSource {
                return $this;
        }
 
+       /**
+        * Get header text.
+        *
+        * @param string|null $section The section to get the header text for
+        * @since 1.26
+        * @return string
+        */
+       function getHeaderText( $section = null ) {
+               if ( is_null( $section ) ) {
+                       return $this->mHeader;
+               } else {
+                       return isset( $this->mSectionHeaders[$section] ) ? $this->mSectionHeaders[$section] : '';
+               }
+       }
+
        /**
         * Add footer text, inside the form.
         *
@@ -748,6 +776,21 @@ class HTMLForm extends ContextSource {
                return $this;
        }
 
+       /**
+        * Get footer text.
+        *
+        * @param string|null $section The section to get the footer text for
+        * @since 1.26
+        * @return string
+        */
+       function getFooterText( $section = null ) {
+               if ( is_null( $section ) ) {
+                       return $this->mFooter;
+               } else {
+                       return isset( $this->mSectionFooters[$section] ) ? $this->mSectionFooters[$section] : '';
+               }
+       }
+
        /**
         * Add text to the end of the display.
         *
@@ -869,12 +912,11 @@ class HTMLForm extends ContextSource {
 
                $html = ''
                        . $this->getErrors( $submitResult )
-                       // In OOUI forms, we handle mHeader elsewhere. FIXME This is horrible.
-                       . ( $this->getDisplayFormat() === 'ooui' ? '' : $this->mHeader )
+                       . $this->getHeaderText()
                        . $this->getBody()
                        . $this->getHiddenFields()
                        . $this->getButtons()
-                       . $this->mFooter;
+                       . $this->getFooterText();
 
                $html = $this->wrapForm( $html );
 
@@ -973,7 +1015,10 @@ class HTMLForm extends ContextSource {
                        $attribs['class'] = array( 'mw-htmlform-submit' );
 
                        if ( $useMediaWikiUIEverywhere ) {
-                               array_push( $attribs['class'], 'mw-ui-button', 'mw-ui-' . $this->mSubmitFlag );
+                               foreach ( $this->mSubmitFlags as $flag ) {
+                                       array_push( $attribs['class'], 'mw-ui-' . $flag );
+                               }
+                               array_push( $attribs['class'], 'mw-ui-button' );
                        }
 
                        $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
@@ -1100,7 +1145,7 @@ class HTMLForm extends ContextSource {
         * @since 1.24
         */
        public function setSubmitDestructive() {
-               $this->mSubmitFlag = 'destructive';
+               $this->mSubmitFlags = array( 'destructive', 'primary' );
        }
 
        /**
@@ -1108,7 +1153,7 @@ class HTMLForm extends ContextSource {
         * @since 1.25
         */
        public function setSubmitProgressive() {
-               $this->mSubmitFlag = 'progressive';
+               $this->mSubmitFlags = array( 'progressive', 'primary' );
        }
 
        /**
@@ -1297,11 +1342,14 @@ class HTMLForm extends ContextSource {
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
        public function setMethod( $method = 'post' ) {
-               $this->mMethod = $method;
+               $this->mMethod = strtolower( $method );
 
                return $this;
        }
 
+       /**
+        * @return string Always lowercase
+        */
        public function getMethod() {
                return $this->mMethod;
        }
@@ -1325,7 +1373,7 @@ class HTMLForm extends ContextSource {
                &$hasUserVisibleFields = false ) {
                $displayFormat = $this->getDisplayFormat();
 
-               $html = '';
+               $html = array();
                $subsectionHtml = '';
                $hasLabel = false;
 
@@ -1337,7 +1385,7 @@ class HTMLForm extends ContextSource {
                                $v = empty( $value->mParams['nodata'] )
                                        ? $this->mFieldData[$key]
                                        : $value->getDefault();
-                               $html .= $value->$getFieldHtmlMethod( $v );
+                               $html[] = $value->$getFieldHtmlMethod( $v );
 
                                $labelValue = trim( $value->getLabel() );
                                if ( $labelValue != ' ' && $labelValue !== '' ) {
@@ -1364,12 +1412,9 @@ class HTMLForm extends ContextSource {
 
                                        $legend = $this->getLegend( $key );
 
-                                       if ( isset( $this->mSectionHeaders[$key] ) ) {
-                                               $section = $this->mSectionHeaders[$key] . $section;
-                                       }
-                                       if ( isset( $this->mSectionFooters[$key] ) ) {
-                                               $section .= $this->mSectionFooters[$key];
-                                       }
+                                       $section = $this->getHeaderText( $key ) .
+                                               $section .
+                                               $this->getFooterText( $key );
 
                                        $attributes = array();
                                        if ( $fieldsetIDPrefix ) {
@@ -1383,45 +1428,7 @@ class HTMLForm extends ContextSource {
                        }
                }
 
-               if ( $displayFormat !== 'raw' ) {
-                       $classes = array();
-
-                       if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
-                               $classes[] = 'mw-htmlform-nolabel';
-                       }
-
-                       $attribs = array(
-                               'class' => implode( ' ', $classes ),
-                       );
-
-                       if ( $sectionName ) {
-                               $attribs['id'] = Sanitizer::escapeId( $sectionName );
-                       }
-
-                       if ( $displayFormat === 'table' ) {
-                               $html = Html::rawElement( 'table',
-                                               $attribs,
-                                               Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
-                       } elseif ( $displayFormat === 'inline' ) {
-                               $html = Html::rawElement( 'span', $attribs, "\n$html\n" );
-                       } elseif ( $displayFormat === 'ooui' ) {
-                               $config = array(
-                                       'classes' => $classes,
-                               );
-                               if ( $sectionName ) {
-                                       $config['id'] = Sanitizer::escapeId( $sectionName );
-                               }
-                               if ( is_string( $this->mWrapperLegend ) ) {
-                                       $config['label'] = $this->mWrapperLegend;
-                               }
-                               $fieldset = new OOUI\FieldsetLayout( $config );
-                               // Ewww. We should pass this as $config['items'], but there might be string snippets.
-                               $fieldset->group->appendContent( new OOUI\HtmlSnippet( $html ) );
-                               $html = $fieldset;
-                       } else {
-                               $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
-                       }
-               }
+               $html = $this->formatSection( $html, $sectionName, $hasLabel );
 
                if ( $subsectionHtml ) {
                        if ( $this->mSubSectionBeforeFields ) {
@@ -1434,6 +1441,46 @@ class HTMLForm extends ContextSource {
                }
        }
 
+       /**
+        * Put a form section together from the individual fields' HTML, merging it and wrapping.
+        * @param array $fieldsHtml
+        * @param string $sectionName
+        * @param bool $anyFieldHasLabel
+        * @return string HTML
+        */
+       protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) {
+               $displayFormat = $this->getDisplayFormat();
+               $html = implode( '', $fieldsHtml );
+
+               if ( $displayFormat === 'raw' ) {
+                       return $html;
+               }
+
+               $classes = array();
+
+               if ( !$anyFieldHasLabel ) { // Avoid strange spacing when no labels exist
+                       $classes[] = 'mw-htmlform-nolabel';
+               }
+
+               $attribs = array(
+                       'class' => implode( ' ', $classes ),
+               );
+
+               if ( $sectionName ) {
+                       $attribs['id'] = Sanitizer::escapeId( $sectionName );
+               }
+
+               if ( $displayFormat === 'table' ) {
+                       return Html::rawElement( 'table',
+                                       $attribs,
+                                       Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
+               } elseif ( $displayFormat === 'inline' ) {
+                       return Html::rawElement( 'span', $attribs, "\n$html\n" );
+               } else {
+                       return Html::rawElement( 'div', $attribs, "\n$html\n" );
+               }
+       }
+
        /**
         * Construct the form fields from the Descriptor array
         */