Merge "HTMLCheckMatrix support for forcing options on/off"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 17 May 2013 00:34:32 +0000 (00:34 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 17 May 2013 00:34:32 +0000 (00:34 +0000)
1  2 
includes/HTMLForm.php
includes/Preferences.php

diff --combined includes/HTMLForm.php
@@@ -83,8 -83,9 +83,8 @@@
   * $form = new HTMLForm( $someFields );
   * $form->setMethod( 'get' )
   *      ->setWrapperLegendMsg( 'message-key' )
 - *      ->suppressReset()
   *      ->prepareForm()
 - *      ->displayForm();
 + *      ->displayForm( '' );
   * @endcode
   * Note that you will have prepareForm and displayForm at the end. Other
   * methods call done after that would simply not be part of the form :(
@@@ -1093,6 -1094,7 +1093,6 @@@ class HTMLForm extends ContextSource 
                $this->mAction = $action;
                return $this;
        }
 -
  }
  
  /**
@@@ -1110,12 -1112,6 +1110,12 @@@ abstract class HTMLFormField 
        protected $mClass = '';
        protected $mDefault;
  
 +      /**
 +       * @var bool If true will generate an empty div element with no label
 +       * @since 1.22
 +       */
 +      protected $mShowEmptyLabels = true;
 +
        /**
         * @var HTMLForm
         */
        /**
         * Initialise the object
         * @param array $params Associative Array. See HTMLForm doc for syntax.
 +       *
 +       * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead
         * @throws MWException
         */
        function __construct( $params ) {
  
                        $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
                } elseif ( isset( $params['label'] ) ) {
 -                      $this->mLabel = $params['label'];
 +                      if ( $params['label'] === '&#160;' ) {
 +                              // Apparently some things set &nbsp directly and in an odd format
 +                              $this->mLabel = '&#160;';
 +                      } else {
 +                              $this->mLabel = htmlspecialchars( $params['label'] );
 +                      }
 +              } elseif ( isset( $params['label-raw'] ) ) {
 +                      $this->mLabel = $params['label-raw'];
                }
  
                $this->mName = "wp{$params['fieldname']}";
                if ( isset( $params['flatlist'] ) ) {
                        $this->mClass .= ' mw-htmlform-flatlist';
                }
 +
 +              if ( isset( $params['hidelabel'] ) ) {
 +                      $this->mShowEmptyLabels = false;
 +              }
        }
  
        /**
                $cellAttributes = array();
                $label = $this->getLabelHtml( $cellAttributes );
  
 +              $outerDivClass = array(
 +                      'mw-input',
 +                      'mw-htmlform-nolabel' => ( $label === '' )
 +              );
 +
                $field = Html::rawElement(
                        'div',
 -                      array( 'class' => 'mw-input' ) + $cellAttributes,
 +                      array( 'class' => $outerDivClass ) + $cellAttributes,
                        $inputHtml . "\n$errors"
                );
                $html = Html::rawElement( 'div',
        }
  
        function getLabel() {
 -              return $this->mLabel;
 +              return is_null( $this->mLabel ) ? '' : $this->mLabel;
        }
  
        function getLabelHtml( $cellAttributes = array() ) {
                        $for['for'] = $this->mID;
                }
  
 +              $labelValue = trim( $this->getLabel() );
 +              $hasLabel = false;
 +              if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
 +                      $hasLabel = true;
 +              }
 +
                $displayFormat = $this->mParent->getDisplayFormat();
 -              $labelElement = Html::rawElement( 'label', $for, $this->getLabel() );
 +              $html = '';
  
 -              if ( $displayFormat == 'table' ) {
 -                      return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
 -                              Html::rawElement( 'label', $for, $this->getLabel() )
 +              if ( $displayFormat === 'table' ) {
 +                      $html = Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
 +                              Html::rawElement( 'label', $for, $labelValue )
                        );
 -              } elseif ( $displayFormat == 'div' ) {
 -                      return Html::rawElement( 'div', array( 'class' => 'mw-label' ) + $cellAttributes,
 -                              Html::rawElement( 'label', $for, $this->getLabel() )
 -                      );
 -              } else {
 -                      return $labelElement;
 +              } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
 +                      if ( $displayFormat === 'div' ) {
 +                              $html = Html::rawElement(
 +                                      'div',
 +                                      array( 'class' => 'mw-label' ) + $cellAttributes,
 +                                      Html::rawElement( 'label', $for, $labelValue )
 +                              );
 +                      } else {
 +                              $html = Html::rawElement( 'label', $for, $labelValue );
 +                      }
                }
 +
 +              return $html;
        }
  
        function getDefault() {
@@@ -1657,19 -1623,16 +1657,19 @@@ class HTMLTextField extends HTMLFormFie
        }
  }
  class HTMLTextAreaField extends HTMLFormField {
 +      const DEFAULT_COLS = 80;
 +      const DEFAULT_ROWS = 25;
 +
        function getCols() {
                return isset( $this->mParams['cols'] )
                        ? $this->mParams['cols']
 -                      : 80;
 +                      : static::DEFAULT_COLS;
        }
  
        function getRows() {
                return isset( $this->mParams['rows'] )
                        ? $this->mParams['rows']
 -                      : 25;
 +                      : static::DEFAULT_ROWS;
        }
  
        function getInputHTML( $value ) {
@@@ -1844,9 -1807,33 +1844,33 @@@ class HTMLCheckField extends HTMLFormFi
   * A checkbox matrix
   * Operates similarly to HTMLMultiSelectField, but instead of using an array of
   * options, uses an array of rows and an array of columns to dynamically
-  * construct a matrix of options.
+  * construct a matrix of options. The tags used to identify a particular cell
+  * are of the form "columnName-rowName"
+  *
+  * Options:
+  *   columns:           Required list of columns in the matrix.
+  *   rows:              Required list of rows in the matrix.
+  *   force-options-on:  Accepts array of column-row tags to be displayed as enabled
+  *                      but unavailable to change
+  *   force-options-off: Accepts array of column-row tags to be displayed as disabled
+  *                      but unavailable to change.
   */
- class HTMLCheckMatrix extends HTMLFormField {
+ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
+       static private $requiredParams = array(
+               // Required by underlying HTMLFormField
+               'fieldname',
+               // Required by HTMLCheckMatrix
+               'rows', 'columns'
+       );
+       public function __construct( $params ) {
+               $missing = array_diff( self::$requiredParams, array_keys( $params ) );
+               if ( $missing ) {
+                       throw HTMLFormFieldRequiredOptionsException::create( $this, $missing );
+               }
+               parent::__construct( $params );
+       }
  
        function validate( $value, $alldata ) {
                $rows = $this->mParams['rows'];
                foreach ( $rows as $rowLabel => $rowTag ) {
                        $rowContents = Html::rawElement( 'td', array(), $rowLabel );
                        foreach ( $columns as $columnTag ) {
-                               // Knock out any options that are not wanted
-                               if ( isset( $this->mParams['remove-options'] )
-                                       && in_array( "$columnTag-$rowTag", $this->mParams['remove-options'] ) )
-                               {
-                                       $rowContents .= Html::rawElement( 'td', array(), '&#160;' );
-                               } else {
-                                       // Construct the checkbox
-                                       $thisAttribs = array(
-                                               'id' => "{$this->mID}-$columnTag-$rowTag",
-                                               'value' => $columnTag . '-' . $rowTag
-                                       );
-                                       $checkbox = Xml::check(
-                                               $this->mName . '[]',
-                                               in_array( $columnTag . '-' . $rowTag, (array)$value, true ),
-                                               $attribs + $thisAttribs );
-                                       $rowContents .= Html::rawElement( 'td', array(), $checkbox );
+                               $thisTag = "$columnTag-$rowTag";
+                               // Construct the checkbox
+                               $thisAttribs = array(
+                                       'id' => "{$this->mID}-$thisTag",
+                                       'value' => $thisTag,
+                               );
+                               $checked = in_array( $thisTag, (array)$value, true);
+                               if ( $this->isTagForcedOff( $thisTag ) ) {
+                                       $checked = false;
+                                       $thisAttribs['disabled'] = 1;
+                               } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+                                       $checked = true;
+                                       $thisAttribs['disabled'] = 1;
                                }
+                               $rowContents .= Html::rawElement(
+                                       'td',
+                                       array(),
+                                       Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs )
+                               );
                        }
                        $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
                }
                return $html;
        }
  
+       protected function isTagForcedOff( $tag ) {
+               return isset( $this->mParams['force-options-off'] )
+                       && in_array( $tag, $this->mParams['force-options-off'] );
+       }
+       protected function isTagForcedOn( $tag ) {
+               return isset( $this->mParams['force-options-on'] )
+                       && in_array( $tag, $this->mParams['force-options-on'] );
+       }
        /**
         * Get the complete table row for the input, including help text,
         * labels, and whatever.
                        return array();
                }
        }
+       function filterDataForSubmit( $data ) {
+               $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
+               $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
+               $res = array();
+               foreach ( $columns as $column ) {
+                       foreach ( $rows as $row ) {
+                               // Make sure option hasn't been forced
+                               $thisTag = "$column-$row";
+                               if ( $this->isTagForcedOff( $thisTag ) ) {
+                                       $res[$thisTag] = false;
+                               } elseif ($this->isTagForcedOn( $thisTag ) ) {
+                                       $res[$thisTag] = true;
+                               } else {
+                                       $res[$thisTag] = in_array( $thisTag, $data );
+                               }
+                       }
+               }
+               return $res;
+       }
  }
  
  /**
@@@ -2143,7 -2163,7 +2200,7 @@@ class HTMLSelectOrOtherField extends HT
  /**
   * Multi-select field
   */
- class HTMLMultiSelectField extends HTMLFormField {
+ class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
  
        function validate( $value, $alldata ) {
                $p = parent::validate( $value, $alldata );
                }
        }
  
+       function filterDataForSubmit( $data ) {
+               $options = HTMLFormField::flattenOptions( $this->mParams['options'] );
+               $res = array();
+               foreach ( $options as $opt ) {
+                       $res["$opt"] = in_array( $opt, $data );
+               }
+               return $res;
+       }
        protected function needsLabel() {
                return false;
        }
@@@ -2579,19 -2610,7 +2647,19 @@@ class HTMLHiddenField extends HTMLFormF
   * Add a submit button inline in the form (as opposed to
   * HTMLForm::addButton(), which will add it at the end).
   */
 -class HTMLSubmitField extends HTMLFormField {
 +class HTMLSubmitField extends HTMLButtonField {
 +      protected $buttonType = 'submit';
 +}
 +
 +/**
 + * Adds a generic button inline to the form. Does not do anything, you must add
 + * click handling code in JavaScript. Use a HTMLSubmitField if you merely
 + * wish to add a submit button to a form.
 + *
 + * @since 1.22
 + */
 +class HTMLButtonField extends HTMLFormField {
 +      protected $buttonType = 'button';
  
        public function __construct( $info ) {
                $info['nodata'] = true;
        public function getInputHTML( $value ) {
                $attr = array(
                        'class' => 'mw-htmlform-submit ' . $this->mClass,
 -                      'name' => $this->mName,
                        'id' => $this->mID,
                );
  
                        $attr['disabled'] = 'disabled';
                }
  
 -              return Xml::submitButton( $value, $attr );
 +              return Html::input(
 +                      $this->mName,
 +                      $value,
 +                      $this->buttonType,
 +                      $attr
 +              );
        }
  
        protected function needsLabel() {
@@@ -2691,3 -2706,22 +2759,22 @@@ class HTMLApiField extends HTMLFormFiel
                return '';
        }
  }
+ interface HTMLNestedFilterable {
+       /**
+        * Support for seperating multi-option preferences into multiple preferences
+        * Due to lack of array support.
+        * @param $data array
+        */
+       function filterDataForSubmit( $data );
+ }
+ class HTMLFormFieldRequiredOptionsException extends MWException {
+       static public function create( HTMLFormField $field, array $missing ) {
+               return new self( sprintf(
+                       "Form type `%s` expected the following parameters to be set: %s",
+                       get_class( $field ),
+                       implode( ', ', $missing )
+               ) );
+       }
+ }
diff --combined includes/Preferences.php
@@@ -90,14 -90,10 +90,14 @@@ class Preferences 
                        }
                }
  
 +              ## Make sure that form fields have their parent set. See bug 41337.
 +              $dummyForm = new HTMLForm( array(), $context );
 +
                ## Prod in defaults from the user
                foreach ( $defaultPreferences as $name => &$info ) {
                        $prefFromUser = self::getOptionFromUser( $name, $info, $user );
                        $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation
 +                      $field->mParent = $dummyForm;
                        $defaultOptions = User::getDefaultOptions();
                        $globalDefault = isset( $defaultOptions[$name] )
                                ? $defaultOptions[$name]
                        'label-message' => 'yourlanguage',
                );
  
 -              /* see if there are multiple language variants to choose from*/
 -              $variantArray = array();
 +              // see if there are multiple language variants to choose from
                if ( !$wgDisableLangConversion ) {
                        $variants = $wgContLang->getVariants();
  
 -                      foreach ( $variants as $v ) {
 -                              $v = str_replace( '_', '-', strtolower( $v ) );
 -                              $variantArray[$v] = $wgContLang->getVariantname( $v, false );
 -                      }
 +                      if ( count( $variants ) > 1 ) {
 +                              $variantArray = array();
 +                              foreach ( $variants as $v ) {
 +                                      $v = str_replace( '_', '-', strtolower( $v ) );
 +                                      $variantArray[$v] = $wgContLang->getVariantname( $v, false );
 +                              }
  
 -                      $options = array();
 -                      foreach ( $variantArray as $code => $name ) {
 -                              $display = wfBCP47( $code ) . ' - ' . $name;
 -                              $options[$display] = $code;
 -                      }
 +                              $options = array();
 +                              foreach ( $variantArray as $code => $name ) {
 +                                      $display = wfBCP47( $code ) . ' - ' . $name;
 +                                      $options[$display] = $code;
 +                              }
  
 -                      if ( count( $variantArray ) > 1 ) {
                                $defaultPreferences['variant'] = array(
                                        'label-message' => 'yourvariant',
                                        'type' => 'select',
                                        'section' => 'personal/i18n',
                                        'help-message' => 'prefs-help-variant',
                                );
 -                      }
 -              }
  
 -              if ( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
 -                      $defaultPreferences['noconvertlink'] =
 -                              array(
 -                              'type' => 'toggle',
 -                              'section' => 'personal/i18n',
 -                              'label-message' => 'tog-noconvertlink',
 -                      );
 +                              if ( !$wgDisableTitleConversion ) {
 +                                      $defaultPreferences['noconvertlink'] =
 +                                              array(
 +                                              'type' => 'toggle',
 +                                              'section' => 'personal/i18n',
 +                                              'label-message' => 'tog-noconvertlink',
 +                                      );
 +                              }
 +                      }
                }
  
                // show a preview of the old signature first
  
                        $disableEmailPrefs = false;
  
 -                      $emailauthenticationclass = 'mw-email-not-authenticated';
                        if ( $wgEmailAuthentication ) {
 +                              $emailauthenticationclass = 'mw-email-not-authenticated';
                                if ( $user->getEmail() ) {
                                        if ( $user->getEmailAuthenticationTimestamp() ) {
                                                // date and time are separate parameters to facilitate localisation.
                                        # Apply the same CSS class used on the input to the message:
                                        'cssclass' => $emailauthenticationclass,
                                );
 +                              $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
                        }
 -                      $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
  
                        if ( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
                                $defaultPreferences['disablemail'] = array(
                        'section' => 'rendering/advancedrendering',
                        'options' => $stubThresholdOptions,
                        'size' => 20,
 -                      'label' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
 +                      'label-raw' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
                );
  
                if ( $wgAllowUserCssPrefs ) {
                        }
  
                        # Create preview link
 -                      $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) );
 +                      $mplink = htmlspecialchars( $mptitle->getLocalURL( array( 'useskin' => $skinkey ) ) );
                        $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
  
                        # Create links to user CSS/JS pages
        }
  
        /**
 -       * @deprecated in 1.19; will be removed in 1.20.
 +       * @deprecated in 1.19
         * @param $user User
         * @return array
         */
@@@ -1562,40 -1558,19 +1562,19 @@@ class PreferencesForm extends HTMLForm 
        }
  
        /**
+        * Separate multi-option preferences into multiple preferences, since we
+        * have to store them separately
         * @param $data array
         * @return array
         */
        function filterDataForSubmit( $data ) {
-               // Support for separating multi-option preferences into multiple preferences
-               // Due to lack of array support.
                foreach ( $this->mFlatFields as $fieldname => $field ) {
-                       $info = $field->mParams;
-                       if ( $field instanceof HTMLMultiSelectField ) {
-                               $options = HTMLFormField::flattenOptions( $info['options'] );
+                       if ( $field instanceof HTMLNestedFilterable ) {
+                               $info = $field->mParams;
                                $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
-                               foreach ( $options as $opt ) {
-                                       $data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
+                               foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
+                                       $data["$prefix-$key"] = $value;
                                }
-                               unset( $data[$fieldname] );
-                       } elseif ( $field instanceof HTMLCheckMatrix ) {
-                               $columns = HTMLFormField::flattenOptions( $info['columns'] );
-                               $rows = HTMLFormField::flattenOptions( $info['rows'] );
-                               $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
-                               foreach ( $columns as $column ) {
-                                       foreach ( $rows as $row ) {
-                                               // Make sure option hasn't been removed
-                                               if ( !isset( $info['remove-options'] )
-                                                       || !in_array( "$column-$row", $info['remove-options'] ) )
-                                               {
-                                                       $data["$prefix-$column-$row"] = in_array( "$column-$row", $data[$fieldname] );
-                                               }
-                                       }
-                               }
                                unset( $data[$fieldname] );
                        }
                }