Merge "Unify HTMLForm message handling"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 2 May 2016 20:52:46 +0000 (20:52 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 2 May 2016 20:52:46 +0000 (20:52 +0000)
1  2 
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/OOUIHTMLForm.php

@@@ -74,7 -74,7 +74,7 @@@
   *    'required'            -- passed through to the object, indicating that it
   *                             is a required field.
   *    'size'                -- the length of text fields
 - *    'filter-callback      -- a function name to give you the chance to
 + *    'filter-callback'     -- a function name to give you the chance to
   *                             massage the inputted value before it's processed.
   *                             @see HTMLFormField::filter()
   *    'validation-callback' -- a function name to give you the chance
@@@ -295,7 -295,7 +295,7 @@@ class HTMLForm extends ContextSource 
                        $this->setContext( $context );
                        $this->mTitle = false; // We don't need them to set a title
                        $this->mMessagePrefix = $messagePrefix;
 -              } elseif ( is_null( $context ) && $messagePrefix !== '' ) {
 +              } elseif ( $context === null && $messagePrefix !== '' ) {
                        $this->mMessagePrefix = $messagePrefix;
                } elseif ( is_string( $context ) && $messagePrefix === '' ) {
                        // B/C since 1.18
                                ? $info['section']
                                : '';
  
 -                      if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
 +                      if ( isset( $info['type'] ) && $info['type'] === 'file' ) {
                                $this->mUseMultipart = true;
                        }
  
         */
        public function setDisplayFormat( $format ) {
                if (
 -                      in_array( $format, $this->availableSubclassDisplayFormats ) ||
 -                      in_array( $this->displayFormat, $this->availableSubclassDisplayFormats )
 +                      in_array( $format, $this->availableSubclassDisplayFormats, true ) ||
 +                      in_array( $this->displayFormat, $this->availableSubclassDisplayFormats, true )
                ) {
                        throw new MWException( 'Cannot change display format after creation, ' .
                                'use HTMLForm::factory() instead' );
                }
  
 -              if ( !in_array( $format, $this->availableDisplayFormats ) ) {
 +              if ( !in_array( $format, $this->availableDisplayFormats, true ) ) {
                        throw new MWException( 'Display format must be one of ' .
                                print_r( $this->availableDisplayFormats, true ) );
                }
                # @todo This will throw a fatal error whenever someone try to use
                # 'class' to feed a CSS class instead of 'cssclass'. Would be
                # great to avoid the fatal error and show a nice error.
 -              $obj = new $class( $descriptor );
 -
 -              return $obj;
 +              return new $class( $descriptor );
        }
  
        /**
         * @throws MWException
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function prepareForm() {
 +      public function prepareForm() {
                # Check if we have the info we need
                if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
 -                      throw new MWException( "You must call setTitle() on an HTMLForm" );
 +                      throw new MWException( 'You must call setTitle() on an HTMLForm' );
                }
  
                # Load data from the request.
         * Try submitting, with edit token check first
         * @return Status|bool
         */
 -      function tryAuthorizedSubmit() {
 +      public function tryAuthorizedSubmit() {
                $result = false;
  
                $submit = false;
 -              if ( $this->getMethod() != 'post' ) {
 +              if ( $this->getMethod() !== 'post' ) {
                        $submit = true; // no session check needed
                } elseif ( $this->getRequest()->wasPosted() ) {
                        $editToken = $this->getRequest()->getVal( 'wpEditToken' );
 -                      if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
 +                      if ( $this->getUser()->isLoggedIn() || $editToken !== null ) {
                                // Session tokens for logged-out users have no security value.
                                // However, if the user gave one, check it in order to give a nice
                                // "session expired" error instead of "permission denied" or such.
         * errors
         * @return bool|Status Whether submission was successful.
         */
 -      function show() {
 +      public function show() {
                $this->prepareForm();
  
                $result = $this->tryAuthorizedSubmit();
         * added to the output, no matter, if the validation was good or not.
         * @return bool|Status Whether submission was successful.
         */
 -      function showAlways() {
 +      public function showAlways() {
                $this->prepareForm();
  
                $result = $this->tryAuthorizedSubmit();
         *       object, an HTML string, or an array of arrays (message keys and
         *       params) or strings (message keys)
         */
 -      function trySubmit() {
 +      public function trySubmit() {
                $valid = true;
                $hoistedErrors = [];
                $hoistedErrors[] = isset( $this->mValidationErrorMessage )
         * @since 1.23
         * @return bool
         */
 -      function wasSubmitted() {
 +      public function wasSubmitted() {
                return $this->mWasSubmitted;
        }
  
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setSubmitCallback( $cb ) {
 +      public function setSubmitCallback( $cb ) {
                $this->mSubmitCallback = $cb;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setValidationErrorMessage( $msg ) {
 +      public function setValidationErrorMessage( $msg ) {
                $this->mValidationErrorMessage = $msg;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setIntro( $msg ) {
 +      public function setIntro( $msg ) {
                $this->setPreText( $msg );
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setPreText( $msg ) {
 +      public function setPreText( $msg ) {
                $this->mPre = $msg;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function addPreText( $msg ) {
 +      public function addPreText( $msg ) {
                $this->mPre .= $msg;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function addHeaderText( $msg, $section = null ) {
 -              if ( is_null( $section ) ) {
 +      public function addHeaderText( $msg, $section = null ) {
 +              if ( $section === null ) {
                        $this->mHeader .= $msg;
                } else {
                        if ( !isset( $this->mSectionHeaders[$section] ) ) {
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setHeaderText( $msg, $section = null ) {
 -              if ( is_null( $section ) ) {
 +      public function setHeaderText( $msg, $section = null ) {
 +              if ( $section === null ) {
                        $this->mHeader = $msg;
                } else {
                        $this->mSectionHeaders[$section] = $msg;
         * @since 1.26
         * @return string HTML
         */
 -      function getHeaderText( $section = null ) {
 -              if ( is_null( $section ) ) {
 +      public function getHeaderText( $section = null ) {
 +              if ( $section === null ) {
                        return $this->mHeader;
                } else {
                        return isset( $this->mSectionHeaders[$section] ) ? $this->mSectionHeaders[$section] : '';
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function addFooterText( $msg, $section = null ) {
 -              if ( is_null( $section ) ) {
 +      public function addFooterText( $msg, $section = null ) {
 +              if ( $section === null ) {
                        $this->mFooter .= $msg;
                } else {
                        if ( !isset( $this->mSectionFooters[$section] ) ) {
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setFooterText( $msg, $section = null ) {
 -              if ( is_null( $section ) ) {
 +      public function setFooterText( $msg, $section = null ) {
 +              if ( $section === null ) {
                        $this->mFooter = $msg;
                } else {
                        $this->mSectionFooters[$section] = $msg;
         * @since 1.26
         * @return string
         */
 -      function getFooterText( $section = null ) {
 -              if ( is_null( $section ) ) {
 +      public function getFooterText( $section = null ) {
 +              if ( $section === null ) {
                        return $this->mFooter;
                } else {
                        return isset( $this->mSectionFooters[$section] ) ? $this->mSectionFooters[$section] : '';
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function addPostText( $msg ) {
 +      public function addPostText( $msg ) {
                $this->mPost .= $msg;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setPostText( $msg ) {
 +      public function setPostText( $msg ) {
                $this->mPost = $msg;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      public function addHiddenField( $name, $value, $attribs = [] ) {
 +      public function addHiddenField( $name, $value, array $attribs = [] ) {
                $attribs += [ 'name' => $name ];
                $this->mHiddenFields[] = [ $value, $attribs ];
  
         *
         * @return void Nothing, should be last call
         */
 -      function displayForm( $submitResult ) {
 +      public function displayForm( $submitResult ) {
                $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
        }
  
         *
         * @return string HTML
         */
 -      function getHTML( $submitResult ) {
 +      public function getHTML( $submitResult ) {
                # For good measure (it is the default)
                $this->getOutput()->preventClickjacking();
                $this->getOutput()->addModules( 'mediawiki.htmlform' );
                        'method' => $this->getMethod(),
                        'enctype' => $encType,
                ];
 -              if ( !empty( $this->mId ) ) {
 +              if ( $this->mId ) {
                        $attribs['id'] = $this->mId;
                }
 -              if ( !empty( $this->mAutocomplete ) ) {
 +              if ( $this->mAutocomplete ) {
                        $attribs['autocomplete'] = $this->mAutocomplete;
                }
 -              if ( !empty ( $this->mName ) ) {
 +              if ( $this->mName ) {
                        $attribs['name'] = $this->mName;
                }
                return $attribs;
         *
         * @return string Wrapped HTML.
         */
 -      function wrapForm( $html ) {
 +      public function wrapForm( $html ) {
                # Include a <fieldset> wrapper for style, if requested.
                if ( $this->mWrapperLegend !== false ) {
                        $legend = is_string( $this->mWrapperLegend ) ? $this->mWrapperLegend : false;
         * Get the hidden fields that should go inside the form.
         * @return string HTML.
         */
 -      function getHiddenFields() {
 +      public function getHiddenFields() {
                $html = '';
 -              if ( $this->getMethod() == 'post' ) {
 +              if ( $this->getMethod() === 'post' ) {
                        $html .= Html::hidden(
                                'wpEditToken',
                                $this->getUser()->getEditToken( $this->mTokenSalt ),
                }
  
                $articlePath = $this->getConfig()->get( 'ArticlePath' );
 -              if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() == 'get' ) {
 +              if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
                        $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
                }
  
         * Get the submit and (potentially) reset buttons.
         * @return string HTML.
         */
 -      function getButtons() {
 +      public function getButtons() {
                $buttons = '';
                $useMediaWikiUIEverywhere = $this->getConfig()->get( 'UseMediaWikiUIEverywhere' );
  
  
                        if ( $useMediaWikiUIEverywhere ) {
                                foreach ( $this->mSubmitFlags as $flag ) {
 -                                      array_push( $attribs['class'], 'mw-ui-' . $flag );
 +                                      $attribs['class'][] = 'mw-ui-' . $flag;
                                }
 -                              array_push( $attribs['class'], 'mw-ui-button' );
 +                              $attribs['class'][] = 'mw-ui-button';
                        }
  
                        $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
                                [
                                        'type' => 'reset',
                                        'value' => $this->msg( 'htmlform-reset' )->text(),
 -                                      'class' => ( $useMediaWikiUIEverywhere ? 'mw-ui-button' : null ),
 +                                      'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null,
                                ]
                        ) . "\n";
                }
                        ];
  
                        if ( isset( $button['label-message'] ) ) {
-                               $label = $this->msg( $button['label-message'] )->parse();
+                               $label = $this->getMessage( $button['label-message'] )->parse();
                        } elseif ( isset( $button['label'] ) ) {
                                $label = htmlspecialchars( $button['label'] );
                        } elseif ( isset( $button['label-raw'] ) ) {
                        }
                }
  
 -              $html = Html::rawElement( 'span',
 -                      [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
 +              if ( !$buttons ) {
 +                      return '';
 +              }
  
 -              return $html;
 +              return Html::rawElement( 'span',
 +                      [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
        }
  
        /**
         * Get the whole body of the form.
         * @return string
         */
 -      function getBody() {
 +      public function getBody() {
                return $this->displaySection( $this->mFieldTree, $this->mTableId );
        }
  
         *
         * @return string
         */
 -      function getErrors( $errors ) {
 +      public function getErrors( $errors ) {
                if ( $errors instanceof Status ) {
                        if ( $errors->isOK() ) {
                                $errorstr = '';
                $errorstr = '';
  
                foreach ( $errors as $error ) {
-                       if ( is_array( $error ) ) {
-                               $msg = array_shift( $error );
-                       } else {
-                               $msg = $error;
-                               $error = [];
-                       }
                        $errorstr .= Html::rawElement(
                                'li',
                                [],
-                               $this->msg( $msg, $error )->parse()
+                               $this->getMessage( $error )->parse()
                        );
                }
  
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setSubmitText( $t ) {
 +      public function setSubmitText( $t ) {
                $this->mSubmitText = $t;
  
                return $this;
         * Get the text for the submit button, either customised or a default.
         * @return string
         */
 -      function getSubmitText() {
 -              return $this->mSubmitText
 -                      ? $this->mSubmitText
 -                      : $this->msg( 'htmlform-submit' )->text();
 +      public function getSubmitText() {
 +              return $this->mSubmitText ?: $this->msg( 'htmlform-submit' )->text();
        }
  
        /**
         * @todo FIXME: Integrity of $t is *not* validated
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setSubmitID( $t ) {
 +      public function setSubmitID( $t ) {
                $this->mSubmitID = $t;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls
         */
 -      function suppressDefaultSubmit( $suppressSubmit = true ) {
 +      public function suppressDefaultSubmit( $suppressSubmit = true ) {
                $this->mShowSubmit = !$suppressSubmit;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setMessagePrefix( $p ) {
 +      public function setMessagePrefix( $p ) {
                $this->mMessagePrefix = $p;
  
                return $this;
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function setTitle( $t ) {
 +      public function setTitle( $t ) {
                $this->mTitle = $t;
  
                return $this;
         * Get the title
         * @return Title
         */
 -      function getTitle() {
 +      public function getTitle() {
                return $this->mTitle === false
                        ? $this->getContext()->getTitle()
                        : $this->mTitle;
         * @param string $fieldsetIDPrefix ID prefix for the "<fieldset>" tag of
         *   each subsection, ignored if empty.
         * @param bool &$hasUserVisibleFields Whether the section had user-visible fields.
 +       * @throws LogicException When called on uninitialized field data, e.g. When
 +       *  HTMLForm::displayForm was called without calling HTMLForm::prepareForm
 +       *  first.
         *
         * @return string
         */
        public function displaySection( $fields,
                $sectionName = '',
                $fieldsetIDPrefix = '',
 -              &$hasUserVisibleFields = false ) {
 +              &$hasUserVisibleFields = false
 +      ) {
 +              if ( $this->mFieldData === null ) {
 +                      throw new LogicException( 'HTMLForm::displaySection() called on uninitialized field data. '
 +                              . 'You probably called displayForm() without calling prepareForm() first.' );
 +              }
 +
                $displayFormat = $this->getDisplayFormat();
  
                $html = [];
  
                // Conveniently, PHP method names are case-insensitive.
                // For grep: this can call getDiv, getRaw, getInline, getVForm, getOOUI
 -              $getFieldHtmlMethod = $displayFormat == 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
 +              $getFieldHtmlMethod = $displayFormat === 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
  
                foreach ( $fields as $key => $value ) {
                        if ( $value instanceof HTMLFormField ) {
 -                              $v = empty( $value->mParams['nodata'] )
 +                              $v = array_key_exists( $key, $this->mFieldData )
                                        ? $this->mFieldData[$key]
                                        : $value->getDefault();
  
                                        $html[] = $retval;
  
                                        $labelValue = trim( $value->getLabel() );
 -                                      if ( $labelValue != '&#160;' && $labelValue !== '' ) {
 +                                      if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
                                                $hasLabel = true;
                                        }
  
        /**
         * Construct the form fields from the Descriptor array
         */
 -      function loadData() {
 +      public function loadData() {
                $fieldData = [];
  
                foreach ( $this->mFlatFields as $fieldname => $field ) {
 -                      if ( !empty( $field->mParams['nodata'] ) ) {
 +                      $request = $this->getRequest();
 +                      if ( $field->skipLoadData( $request ) ) {
                                continue;
                        } elseif ( !empty( $field->mParams['disabled'] ) ) {
                                $fieldData[$fieldname] = $field->getDefault();
                        } else {
 -                              $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
 +                              $fieldData[$fieldname] = $field->loadDataFromRequest( $request );
                        }
                }
  
         *
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
 -      function suppressReset( $suppressReset = true ) {
 +      public function suppressReset( $suppressReset = true ) {
                $this->mShowReset = !$suppressReset;
  
                return $this;
         *
         * @return array
         */
 -      function filterDataForSubmit( $data ) {
 +      public function filterDataForSubmit( $data ) {
                return $data;
        }
  
  
                return $this;
        }
+       /**
+        * Turns a *-message parameter (which could be a MessageSpecifier, or a message name, or a
+        * name + parameters array) into a Message.
+        * @param mixed $value
+        * @return Message
+        */
+       protected function getMessage( $value ) {
+               return Message::newFromSpecifier( $value )->setContext( $this );
+       }
  }
@@@ -117,8 -117,8 +117,8 @@@ abstract class HTMLFormField 
                        $tmp = $m[1];
                }
                if ( substr( $tmp, 0, 2 ) == 'wp' &&
 -                      !isset( $alldata[$tmp] ) &&
 -                      isset( $alldata[substr( $tmp, 2 )] )
 +                      !array_key_exists( $tmp, $alldata ) &&
 +                      array_key_exists( substr( $tmp, 2 ), $alldata )
                ) {
                        // Adjust for name mangling.
                        $tmp = substr( $tmp, 2 );
                        $data = $alldata;
                        while ( $keys ) {
                                $key = array_shift( $keys );
 -                              if ( !is_array( $data ) || !isset( $data[$key] ) ) {
 +                              if ( !is_array( $data ) || !array_key_exists( $key, $data ) ) {
                                        continue 2;
                                }
                                $data = $data[$key];
         * @return Message
         */
        protected function getMessage( $value ) {
-               if ( $value instanceof Message ) {
-                       return $value;
-               } elseif ( $value instanceof MessageSpecifier ) {
-                       return Message::newFromKey( $value );
-               } elseif ( is_array( $value ) ) {
-                       $msg = array_shift( $value );
-                       return $this->msg( $msg, $value );
-               } else {
-                       return $this->msg( $value, [] );
-               }
+               return Message::newFromSpecifier( $value )->setContext( $this->mParent );
        }
 +
 +      /**
 +       * Skip this field when collecting data.
 +       * @param WebRequest $request
 +       * @return bool
 +       * @since 1.27
 +       */
 +      public function skipLoadData( $request ) {
 +              return !empty( $this->mParams['nodata'] );
 +      }
  }
@@@ -100,7 -100,7 +100,7 @@@ class OOUIHTMLForm extends HTMLForm 
                        if ( $isBadIE ) {
                                $label = $button['value'];
                        } elseif ( isset( $button['label-message'] ) ) {
-                               $label = new OOUI\HtmlSnippet( $this->msg( $button['label-message'] )->parse() );
+                               $label = new OOUI\HtmlSnippet( $this->getMessage( $button['label-message'] )->parse() );
                        } elseif ( isset( $button['label'] ) ) {
                                $label = $button['label'];
                        } elseif ( isset( $button['label-raw'] ) ) {
                        ] + $attrs );
                }
  
 -              $html = Html::rawElement( 'div',
 -                      [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
 +              if ( !$buttons ) {
 +                      return '';
 +              }
  
 -              return $html;
 +              return Html::rawElement( 'div',
 +                      [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
        }
  
        protected function wrapFieldSetSection( $legend, $section, $attributes ) {
                }
  
                foreach ( $errors as &$error ) {
-                       if ( is_array( $error ) ) {
-                               $msg = array_shift( $error );
-                       } else {
-                               $msg = $error;
-                               $error = [];
-                       }
-                       // if the error is already a message object, don't use it as a message key
-                       if ( !$msg instanceof Message ) {
-                               $error = $this->msg( $msg, $error )->parse();
-                       } else {
-                               $error = $msg->parse();
-                       }
+                       $error = $this->getMessage( $error )->parse();
                        $error = new OOUI\HtmlSnippet( $error );
                }