[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> Preferences.php (source)

   1  <?php
   2  /**
   3   * Form to edit user preferences.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   */
  22  
  23  /**
  24   * We're now using the HTMLForm object with some customisation to generate the
  25   * Preferences form. This object handles generic submission, CSRF protection,
  26   * layout and other logic in a reusable manner. We subclass it as a PreferencesForm
  27   * to make some minor customisations.
  28   *
  29   * In order to generate the form, the HTMLForm object needs an array structure
  30   * detailing the form fields available, and that's what this class is for. Each
  31   * element of the array is a basic property-list, including the type of field,
  32   * the label it is to be given in the form, callbacks for validation and
  33   * 'filtering', and other pertinent information. Note that the 'default' field
  34   * is named for generic forms, and does not represent the preference's default
  35   * (which is stored in $wgDefaultUserOptions), but the default for the form
  36   * field, which should be whatever the user has set for that preference. There
  37   * is no need to override it unless you have some special storage logic (for
  38   * instance, those not presently stored as options, but which are best set from
  39   * the user preferences view).
  40   *
  41   * Field types are implemented as subclasses of the generic HTMLFormField
  42   * object, and typically implement at least getInputHTML, which generates the
  43   * HTML for the input field to be placed in the table.
  44   *
  45   * Once fields have been retrieved and validated, submission logic is handed
  46   * over to the tryUISubmit static method of this class.
  47   */
  48  class Preferences {
  49      /** @var array */
  50      protected static $defaultPreferences = null;
  51  
  52      /** @var array */
  53      protected static $saveFilters = array(
  54          'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
  55          'cols' => array( 'Preferences', 'filterIntval' ),
  56          'rows' => array( 'Preferences', 'filterIntval' ),
  57          'rclimit' => array( 'Preferences', 'filterIntval' ),
  58          'wllimit' => array( 'Preferences', 'filterIntval' ),
  59          'searchlimit' => array( 'Preferences', 'filterIntval' ),
  60      );
  61  
  62      // Stuff that shouldn't be saved as a preference.
  63      private static $saveBlacklist = array(
  64          'realname',
  65          'emailaddress',
  66      );
  67  
  68      /**
  69       * @return array
  70       */
  71  	static function getSaveBlacklist() {
  72          return self::$saveBlacklist;
  73      }
  74  
  75      /**
  76       * @throws MWException
  77       * @param User $user
  78       * @param IContextSource $context
  79       * @return array|null
  80       */
  81  	static function getPreferences( $user, IContextSource $context ) {
  82          if ( self::$defaultPreferences ) {
  83              return self::$defaultPreferences;
  84          }
  85  
  86          $defaultPreferences = array();
  87  
  88          self::profilePreferences( $user, $context, $defaultPreferences );
  89          self::skinPreferences( $user, $context, $defaultPreferences );
  90          self::datetimePreferences( $user, $context, $defaultPreferences );
  91          self::filesPreferences( $user, $context, $defaultPreferences );
  92          self::renderingPreferences( $user, $context, $defaultPreferences );
  93          self::editingPreferences( $user, $context, $defaultPreferences );
  94          self::rcPreferences( $user, $context, $defaultPreferences );
  95          self::watchlistPreferences( $user, $context, $defaultPreferences );
  96          self::searchPreferences( $user, $context, $defaultPreferences );
  97          self::miscPreferences( $user, $context, $defaultPreferences );
  98  
  99          wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
 100  
 101          self::loadPreferenceValues( $user, $context, $defaultPreferences );
 102          self::$defaultPreferences = $defaultPreferences;
 103          return $defaultPreferences;
 104      }
 105  
 106      /**
 107       * Loads existing values for a given array of preferences
 108       * @throws MWException
 109       * @param User $user
 110       * @param IContextSource $context
 111       * @param array $defaultPreferences Array to load values for
 112       * @return array|null
 113       */
 114  	static function loadPreferenceValues( $user, $context, &$defaultPreferences ) {
 115          ## Remove preferences that wikis don't want to use
 116          foreach ( $context->getConfig()->get( 'HiddenPrefs' ) as $pref ) {
 117              if ( isset( $defaultPreferences[$pref] ) ) {
 118                  unset( $defaultPreferences[$pref] );
 119              }
 120          }
 121  
 122          ## Make sure that form fields have their parent set. See bug 41337.
 123          $dummyForm = new HTMLForm( array(), $context );
 124  
 125          $disable = !$user->isAllowed( 'editmyoptions' );
 126  
 127          ## Prod in defaults from the user
 128          foreach ( $defaultPreferences as $name => &$info ) {
 129              $prefFromUser = self::getOptionFromUser( $name, $info, $user );
 130              if ( $disable && !in_array( $name, self::$saveBlacklist ) ) {
 131                  $info['disabled'] = 'disabled';
 132              }
 133              $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation
 134              $field->mParent = $dummyForm;
 135              $defaultOptions = User::getDefaultOptions();
 136              $globalDefault = isset( $defaultOptions[$name] )
 137                  ? $defaultOptions[$name]
 138                  : null;
 139  
 140              // If it validates, set it as the default
 141              if ( isset( $info['default'] ) ) {
 142                  // Already set, no problem
 143                  continue;
 144              } elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing
 145                      $field->validate( $prefFromUser, $user->getOptions() ) === true ) {
 146                  $info['default'] = $prefFromUser;
 147              } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) {
 148                  $info['default'] = $globalDefault;
 149              } else {
 150                  throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
 151              }
 152          }
 153  
 154          return $defaultPreferences;
 155      }
 156  
 157      /**
 158       * Pull option from a user account. Handles stuff like array-type preferences.
 159       *
 160       * @param string $name
 161       * @param array $info
 162       * @param User $user
 163       * @return array|string
 164       */
 165  	static function getOptionFromUser( $name, $info, $user ) {
 166          $val = $user->getOption( $name );
 167  
 168          // Handling for multiselect preferences
 169          if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
 170                  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
 171              $options = HTMLFormField::flattenOptions( $info['options'] );
 172              $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
 173              $val = array();
 174  
 175              foreach ( $options as $value ) {
 176                  if ( $user->getOption( "$prefix$value" ) ) {
 177                      $val[] = $value;
 178                  }
 179              }
 180          }
 181  
 182          // Handling for checkmatrix preferences
 183          if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
 184                  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
 185              $columns = HTMLFormField::flattenOptions( $info['columns'] );
 186              $rows = HTMLFormField::flattenOptions( $info['rows'] );
 187              $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
 188              $val = array();
 189  
 190              foreach ( $columns as $column ) {
 191                  foreach ( $rows as $row ) {
 192                      if ( $user->getOption( "$prefix$column-$row" ) ) {
 193                          $val[] = "$column-$row";
 194                      }
 195                  }
 196              }
 197          }
 198  
 199          return $val;
 200      }
 201  
 202      /**
 203       * @param User $user
 204       * @param IContextSource $context
 205       * @param array $defaultPreferences
 206       * @return void
 207       */
 208  	static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) {
 209          global $wgAuth, $wgContLang, $wgParser;
 210  
 211          $config = $context->getConfig();
 212          // retrieving user name for GENDER and misc.
 213          $userName = $user->getName();
 214  
 215          ## User info #####################################
 216          // Information panel
 217          $defaultPreferences['username'] = array(
 218              'type' => 'info',
 219              'label-message' => array( 'username', $userName ),
 220              'default' => $userName,
 221              'section' => 'personal/info',
 222          );
 223  
 224          # Get groups to which the user belongs
 225          $userEffectiveGroups = $user->getEffectiveGroups();
 226          $userGroups = $userMembers = array();
 227          foreach ( $userEffectiveGroups as $ueg ) {
 228              if ( $ueg == '*' ) {
 229                  // Skip the default * group, seems useless here
 230                  continue;
 231              }
 232              $groupName = User::getGroupName( $ueg );
 233              $userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName );
 234  
 235              $memberName = User::getGroupMember( $ueg, $userName );
 236              $userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName );
 237          }
 238          asort( $userGroups );
 239          asort( $userMembers );
 240  
 241          $lang = $context->getLanguage();
 242  
 243          $defaultPreferences['usergroups'] = array(
 244              'type' => 'info',
 245              'label' => $context->msg( 'prefs-memberingroups' )->numParams(
 246                  count( $userGroups ) )->params( $userName )->parse(),
 247              'default' => $context->msg( 'prefs-memberingroups-type',
 248                  $lang->commaList( $userGroups ),
 249                  $lang->commaList( $userMembers )
 250              )->plain(),
 251              'raw' => true,
 252              'section' => 'personal/info',
 253          );
 254  
 255          $editCount = Linker::link( SpecialPage::getTitleFor( "Contributions", $userName ),
 256              $lang->formatNum( $user->getEditCount() ) );
 257  
 258          $defaultPreferences['editcount'] = array(
 259              'type' => 'info',
 260              'raw' => true,
 261              'label-message' => 'prefs-edits',
 262              'default' => $editCount,
 263              'section' => 'personal/info',
 264          );
 265  
 266          if ( $user->getRegistration() ) {
 267              $displayUser = $context->getUser();
 268              $userRegistration = $user->getRegistration();
 269              $defaultPreferences['registrationdate'] = array(
 270                  'type' => 'info',
 271                  'label-message' => 'prefs-registration',
 272                  'default' => $context->msg(
 273                      'prefs-registration-date-time',
 274                      $lang->userTimeAndDate( $userRegistration, $displayUser ),
 275                      $lang->userDate( $userRegistration, $displayUser ),
 276                      $lang->userTime( $userRegistration, $displayUser )
 277                  )->parse(),
 278                  'section' => 'personal/info',
 279              );
 280          }
 281  
 282          $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
 283          $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
 284  
 285          // Actually changeable stuff
 286          $defaultPreferences['realname'] = array(
 287              // (not really "private", but still shouldn't be edited without permission)
 288              'type' => $canEditPrivateInfo && $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
 289              'default' => $user->getRealName(),
 290              'section' => 'personal/info',
 291              'label-message' => 'yourrealname',
 292              'help-message' => 'prefs-help-realname',
 293          );
 294  
 295          if ( $canEditPrivateInfo && $wgAuth->allowPasswordChange() ) {
 296              $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
 297                  $context->msg( 'prefs-resetpass' )->escaped(), array(),
 298                  array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
 299  
 300              $defaultPreferences['password'] = array(
 301                  'type' => 'info',
 302                  'raw' => true,
 303                  'default' => $link,
 304                  'label-message' => 'yourpassword',
 305                  'section' => 'personal/info',
 306              );
 307          }
 308          // Only show prefershttps if secure login is turned on
 309          if ( $config->get( 'SecureLogin' ) && wfCanIPUseHTTPS( $context->getRequest()->getIP() ) ) {
 310              $defaultPreferences['prefershttps'] = array(
 311                  'type' => 'toggle',
 312                  'label-message' => 'tog-prefershttps',
 313                  'help-message' => 'prefs-help-prefershttps',
 314                  'section' => 'personal/info'
 315              );
 316          }
 317  
 318          // Language
 319          $languages = Language::fetchLanguageNames( null, 'mw' );
 320          $languageCode = $config->get( 'LanguageCode' );
 321          if ( !array_key_exists( $languageCode, $languages ) ) {
 322              $languages[$languageCode] = $languageCode;
 323          }
 324          ksort( $languages );
 325  
 326          $options = array();
 327          foreach ( $languages as $code => $name ) {
 328              $display = wfBCP47( $code ) . ' - ' . $name;
 329              $options[$display] = $code;
 330          }
 331          $defaultPreferences['language'] = array(
 332              'type' => 'select',
 333              'section' => 'personal/i18n',
 334              'options' => $options,
 335              'label-message' => 'yourlanguage',
 336          );
 337  
 338          $defaultPreferences['gender'] = array(
 339              'type' => 'radio',
 340              'section' => 'personal/i18n',
 341              'options' => array(
 342                  $context->msg( 'parentheses',
 343                      $context->msg( 'gender-unknown' )->text()
 344                  )->text() => 'unknown',
 345                  $context->msg( 'gender-female' )->text() => 'female',
 346                  $context->msg( 'gender-male' )->text() => 'male',
 347              ),
 348              'label-message' => 'yourgender',
 349              'help-message' => 'prefs-help-gender',
 350          );
 351  
 352          // see if there are multiple language variants to choose from
 353          if ( !$config->get( 'DisableLangConversion' ) ) {
 354              foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
 355                  if ( $langCode == $wgContLang->getCode() ) {
 356                      $variants = $wgContLang->getVariants();
 357  
 358                      if ( count( $variants ) <= 1 ) {
 359                          continue;
 360                      }
 361  
 362                      $variantArray = array();
 363                      foreach ( $variants as $v ) {
 364                          $v = str_replace( '_', '-', strtolower( $v ) );
 365                          $variantArray[$v] = $lang->getVariantname( $v, false );
 366                      }
 367  
 368                      $options = array();
 369                      foreach ( $variantArray as $code => $name ) {
 370                          $display = wfBCP47( $code ) . ' - ' . $name;
 371                          $options[$display] = $code;
 372                      }
 373  
 374                      $defaultPreferences['variant'] = array(
 375                          'label-message' => 'yourvariant',
 376                          'type' => 'select',
 377                          'options' => $options,
 378                          'section' => 'personal/i18n',
 379                          'help-message' => 'prefs-help-variant',
 380                      );
 381                  } else {
 382                      $defaultPreferences["variant-$langCode"] = array(
 383                          'type' => 'api',
 384                      );
 385                  }
 386              }
 387          }
 388  
 389          // Stuff from Language::getExtraUserToggles()
 390          // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language
 391          $toggles = $wgContLang->getExtraUserToggles();
 392  
 393          foreach ( $toggles as $toggle ) {
 394              $defaultPreferences[$toggle] = array(
 395                  'type' => 'toggle',
 396                  'section' => 'personal/i18n',
 397                  'label-message' => "tog-$toggle",
 398              );
 399          }
 400  
 401          // show a preview of the old signature first
 402          $oldsigWikiText = $wgParser->preSaveTransform(
 403              '~~~',
 404              $context->getTitle(),
 405              $user,
 406              ParserOptions::newFromContext( $context )
 407          );
 408          $oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true );
 409          $defaultPreferences['oldsig'] = array(
 410              'type' => 'info',
 411              'raw' => true,
 412              'label-message' => 'tog-oldsig',
 413              'default' => $oldsigHTML,
 414              'section' => 'personal/signature',
 415          );
 416          $defaultPreferences['nickname'] = array(
 417              'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
 418              'maxlength' => $config->get( 'MaxSigChars' ),
 419              'label-message' => 'yournick',
 420              'validation-callback' => array( 'Preferences', 'validateSignature' ),
 421              'section' => 'personal/signature',
 422              'filter-callback' => array( 'Preferences', 'cleanSignature' ),
 423          );
 424          $defaultPreferences['fancysig'] = array(
 425              'type' => 'toggle',
 426              'label-message' => 'tog-fancysig',
 427              // show general help about signature at the bottom of the section
 428              'help-message' => 'prefs-help-signature',
 429              'section' => 'personal/signature'
 430          );
 431  
 432          ## Email stuff
 433  
 434          if ( $config->get( 'EnableEmail' ) ) {
 435              if ( $canViewPrivateInfo ) {
 436                  $helpMessages[] = $config->get( 'EmailConfirmToEdit' )
 437                          ? 'prefs-help-email-required'
 438                          : 'prefs-help-email';
 439  
 440                  if ( $config->get( 'EnableUserEmail' ) ) {
 441                      // additional messages when users can send email to each other
 442                      $helpMessages[] = 'prefs-help-email-others';
 443                  }
 444  
 445                  $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
 446                  if ( $canEditPrivateInfo && $wgAuth->allowPropChange( 'emailaddress' ) ) {
 447                      $link = Linker::link(
 448                          SpecialPage::getTitleFor( 'ChangeEmail' ),
 449                          $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
 450                          array(),
 451                          array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
 452  
 453                      $emailAddress .= $emailAddress == '' ? $link : (
 454                          $context->msg( 'word-separator' )->plain()
 455                          . $context->msg( 'parentheses' )->rawParams( $link )->plain()
 456                      );
 457                  }
 458  
 459                  $defaultPreferences['emailaddress'] = array(
 460                      'type' => 'info',
 461                      'raw' => true,
 462                      'default' => $emailAddress,
 463                      'label-message' => 'youremail',
 464                      'section' => 'personal/email',
 465                      'help-messages' => $helpMessages,
 466                      # 'cssclass' chosen below
 467                  );
 468              }
 469  
 470              $disableEmailPrefs = false;
 471  
 472              if ( $config->get( 'EmailAuthentication' ) ) {
 473                  $emailauthenticationclass = 'mw-email-not-authenticated';
 474                  if ( $user->getEmail() ) {
 475                      if ( $user->getEmailAuthenticationTimestamp() ) {
 476                          // date and time are separate parameters to facilitate localisation.
 477                          // $time is kept for backward compat reasons.
 478                          // 'emailauthenticated' is also used in SpecialConfirmemail.php
 479                          $displayUser = $context->getUser();
 480                          $emailTimestamp = $user->getEmailAuthenticationTimestamp();
 481                          $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
 482                          $d = $lang->userDate( $emailTimestamp, $displayUser );
 483                          $t = $lang->userTime( $emailTimestamp, $displayUser );
 484                          $emailauthenticated = $context->msg( 'emailauthenticated',
 485                              $time, $d, $t )->parse() . '<br />';
 486                          $disableEmailPrefs = false;
 487                          $emailauthenticationclass = 'mw-email-authenticated';
 488                      } else {
 489                          $disableEmailPrefs = true;
 490                          $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
 491                              Linker::linkKnown(
 492                                  SpecialPage::getTitleFor( 'Confirmemail' ),
 493                                  $context->msg( 'emailconfirmlink' )->escaped()
 494                              ) . '<br />';
 495                          $emailauthenticationclass = "mw-email-not-authenticated";
 496                      }
 497                  } else {
 498                      $disableEmailPrefs = true;
 499                      $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
 500                      $emailauthenticationclass = 'mw-email-none';
 501                  }
 502  
 503                  if ( $canViewPrivateInfo ) {
 504                      $defaultPreferences['emailauthentication'] = array(
 505                          'type' => 'info',
 506                          'raw' => true,
 507                          'section' => 'personal/email',
 508                          'label-message' => 'prefs-emailconfirm-label',
 509                          'default' => $emailauthenticated,
 510                          # Apply the same CSS class used on the input to the message:
 511                          'cssclass' => $emailauthenticationclass,
 512                      );
 513                      $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
 514                  }
 515              }
 516  
 517              if ( $config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
 518                  $defaultPreferences['disablemail'] = array(
 519                      'type' => 'toggle',
 520                      'invert' => true,
 521                      'section' => 'personal/email',
 522                      'label-message' => 'allowemail',
 523                      'disabled' => $disableEmailPrefs,
 524                  );
 525                  $defaultPreferences['ccmeonemails'] = array(
 526                      'type' => 'toggle',
 527                      'section' => 'personal/email',
 528                      'label-message' => 'tog-ccmeonemails',
 529                      'disabled' => $disableEmailPrefs,
 530                  );
 531              }
 532  
 533              if ( $config->get( 'EnotifWatchlist' ) ) {
 534                  $defaultPreferences['enotifwatchlistpages'] = array(
 535                      'type' => 'toggle',
 536                      'section' => 'personal/email',
 537                      'label-message' => 'tog-enotifwatchlistpages',
 538                      'disabled' => $disableEmailPrefs,
 539                  );
 540              }
 541              if ( $config->get( 'EnotifUserTalk' ) ) {
 542                  $defaultPreferences['enotifusertalkpages'] = array(
 543                      'type' => 'toggle',
 544                      'section' => 'personal/email',
 545                      'label-message' => 'tog-enotifusertalkpages',
 546                      'disabled' => $disableEmailPrefs,
 547                  );
 548              }
 549              if ( $config->get( 'EnotifUserTalk' ) || $config->get( 'EnotifWatchlist' ) ) {
 550                  $defaultPreferences['enotifminoredits'] = array(
 551                      'type' => 'toggle',
 552                      'section' => 'personal/email',
 553                      'label-message' => 'tog-enotifminoredits',
 554                      'disabled' => $disableEmailPrefs,
 555                  );
 556  
 557                  if ( $config->get( 'EnotifRevealEditorAddress' ) ) {
 558                      $defaultPreferences['enotifrevealaddr'] = array(
 559                          'type' => 'toggle',
 560                          'section' => 'personal/email',
 561                          'label-message' => 'tog-enotifrevealaddr',
 562                          'disabled' => $disableEmailPrefs,
 563                      );
 564                  }
 565              }
 566          }
 567      }
 568  
 569      /**
 570       * @param User $user
 571       * @param IContextSource $context
 572       * @param array $defaultPreferences
 573       * @return void
 574       */
 575  	static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 576          ## Skin #####################################
 577  
 578          // Skin selector, if there is at least one valid skin
 579          $skinOptions = self::generateSkinOptions( $user, $context );
 580          if ( $skinOptions ) {
 581              $defaultPreferences['skin'] = array(
 582                  'type' => 'radio',
 583                  'options' => $skinOptions,
 584                  'label' => '&#160;',
 585                  'section' => 'rendering/skin',
 586              );
 587          }
 588  
 589          $config = $context->getConfig();
 590          $allowUserCss = $config->get( 'AllowUserCss' );
 591          $allowUserJs = $config->get( 'AllowUserJs' );
 592          # Create links to user CSS/JS pages for all skins
 593          # This code is basically copied from generateSkinOptions().  It'd
 594          # be nice to somehow merge this back in there to avoid redundancy.
 595          if ( $allowUserCss || $allowUserJs ) {
 596              $linkTools = array();
 597              $userName = $user->getName();
 598  
 599              if ( $allowUserCss ) {
 600                  $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
 601                  $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
 602              }
 603  
 604              if ( $allowUserJs ) {
 605                  $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
 606                  $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
 607              }
 608  
 609              $defaultPreferences['commoncssjs'] = array(
 610                  'type' => 'info',
 611                  'raw' => true,
 612                  'default' => $context->getLanguage()->pipeList( $linkTools ),
 613                  'label-message' => 'prefs-common-css-js',
 614                  'section' => 'rendering/skin',
 615              );
 616          }
 617      }
 618  
 619      /**
 620       * @param User $user
 621       * @param IContextSource $context
 622       * @param array $defaultPreferences
 623       */
 624  	static function filesPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 625          ## Files #####################################
 626          $defaultPreferences['imagesize'] = array(
 627              'type' => 'select',
 628              'options' => self::getImageSizes( $context ),
 629              'label-message' => 'imagemaxsize',
 630              'section' => 'rendering/files',
 631          );
 632          $defaultPreferences['thumbsize'] = array(
 633              'type' => 'select',
 634              'options' => self::getThumbSizes( $context ),
 635              'label-message' => 'thumbsize',
 636              'section' => 'rendering/files',
 637          );
 638      }
 639  
 640      /**
 641       * @param User $user
 642       * @param IContextSource $context
 643       * @param array $defaultPreferences
 644       * @return void
 645       */
 646  	static function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
 647          ## Date and time #####################################
 648          $dateOptions = self::getDateOptions( $context );
 649          if ( $dateOptions ) {
 650              $defaultPreferences['date'] = array(
 651                  'type' => 'radio',
 652                  'options' => $dateOptions,
 653                  'label' => '&#160;',
 654                  'section' => 'rendering/dateformat',
 655              );
 656          }
 657  
 658          // Info
 659          $now = wfTimestampNow();
 660          $lang = $context->getLanguage();
 661          $nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
 662              $lang->time( $now, true ) );
 663          $nowserver = $lang->time( $now, false ) .
 664              Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
 665  
 666          $defaultPreferences['nowserver'] = array(
 667              'type' => 'info',
 668              'raw' => 1,
 669              'label-message' => 'servertime',
 670              'default' => $nowserver,
 671              'section' => 'rendering/timeoffset',
 672          );
 673  
 674          $defaultPreferences['nowlocal'] = array(
 675              'type' => 'info',
 676              'raw' => 1,
 677              'label-message' => 'localtime',
 678              'default' => $nowlocal,
 679              'section' => 'rendering/timeoffset',
 680          );
 681  
 682          // Grab existing pref.
 683          $tzOffset = $user->getOption( 'timecorrection' );
 684          $tz = explode( '|', $tzOffset, 3 );
 685  
 686          $tzOptions = self::getTimezoneOptions( $context );
 687  
 688          $tzSetting = $tzOffset;
 689          if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
 690              $minDiff = $tz[1];
 691              $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
 692          } elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
 693              !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
 694          ) {
 695              # Timezone offset can vary with DST
 696              $userTZ = timezone_open( $tz[2] );
 697              if ( $userTZ !== false ) {
 698                  $minDiff = floor( timezone_offset_get( $userTZ, date_create( 'now' ) ) / 60 );
 699                  $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
 700              }
 701          }
 702  
 703          $defaultPreferences['timecorrection'] = array(
 704              'class' => 'HTMLSelectOrOtherField',
 705              'label-message' => 'timezonelegend',
 706              'options' => $tzOptions,
 707              'default' => $tzSetting,
 708              'size' => 20,
 709              'section' => 'rendering/timeoffset',
 710          );
 711      }
 712  
 713      /**
 714       * @param User $user
 715       * @param IContextSource $context
 716       * @param array $defaultPreferences
 717       */
 718  	static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 719          ## Diffs ####################################
 720          $defaultPreferences['diffonly'] = array(
 721              'type' => 'toggle',
 722              'section' => 'rendering/diffs',
 723              'label-message' => 'tog-diffonly',
 724          );
 725          $defaultPreferences['norollbackdiff'] = array(
 726              'type' => 'toggle',
 727              'section' => 'rendering/diffs',
 728              'label-message' => 'tog-norollbackdiff',
 729          );
 730  
 731          ## Page Rendering ##############################
 732          if ( $context->getConfig()->get( 'AllowUserCssPrefs' ) ) {
 733              $defaultPreferences['underline'] = array(
 734                  'type' => 'select',
 735                  'options' => array(
 736                      $context->msg( 'underline-never' )->text() => 0,
 737                      $context->msg( 'underline-always' )->text() => 1,
 738                      $context->msg( 'underline-default' )->text() => 2,
 739                  ),
 740                  'label-message' => 'tog-underline',
 741                  'section' => 'rendering/advancedrendering',
 742              );
 743          }
 744  
 745          $stubThresholdValues = array( 50, 100, 500, 1000, 2000, 5000, 10000 );
 746          $stubThresholdOptions = array( $context->msg( 'stub-threshold-disabled' )->text() => 0 );
 747          foreach ( $stubThresholdValues as $value ) {
 748              $stubThresholdOptions[$context->msg( 'size-bytes', $value )->text()] = $value;
 749          }
 750  
 751          $defaultPreferences['stubthreshold'] = array(
 752              'type' => 'select',
 753              'section' => 'rendering/advancedrendering',
 754              'options' => $stubThresholdOptions,
 755              'label-raw' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
 756          );
 757  
 758          $defaultPreferences['showhiddencats'] = array(
 759              'type' => 'toggle',
 760              'section' => 'rendering/advancedrendering',
 761              'label-message' => 'tog-showhiddencats'
 762          );
 763  
 764          $defaultPreferences['numberheadings'] = array(
 765              'type' => 'toggle',
 766              'section' => 'rendering/advancedrendering',
 767              'label-message' => 'tog-numberheadings',
 768          );
 769      }
 770  
 771      /**
 772       * @param User $user
 773       * @param IContextSource $context
 774       * @param array $defaultPreferences
 775       */
 776  	static function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 777          ## Editing #####################################
 778          $defaultPreferences['editsectiononrightclick'] = array(
 779              'type' => 'toggle',
 780              'section' => 'editing/advancedediting',
 781              'label-message' => 'tog-editsectiononrightclick',
 782          );
 783          $defaultPreferences['editondblclick'] = array(
 784              'type' => 'toggle',
 785              'section' => 'editing/advancedediting',
 786              'label-message' => 'tog-editondblclick',
 787          );
 788  
 789          if ( $context->getConfig()->get( 'AllowUserCssPrefs' ) ) {
 790              $defaultPreferences['editfont'] = array(
 791                  'type' => 'select',
 792                  'section' => 'editing/editor',
 793                  'label-message' => 'editfont-style',
 794                  'options' => array(
 795                      $context->msg( 'editfont-default' )->text() => 'default',
 796                      $context->msg( 'editfont-monospace' )->text() => 'monospace',
 797                      $context->msg( 'editfont-sansserif' )->text() => 'sans-serif',
 798                      $context->msg( 'editfont-serif' )->text() => 'serif',
 799                  )
 800              );
 801          }
 802          $defaultPreferences['cols'] = array(
 803              'type' => 'int',
 804              'label-message' => 'columns',
 805              'section' => 'editing/editor',
 806              'min' => 4,
 807              'max' => 1000,
 808          );
 809          $defaultPreferences['rows'] = array(
 810              'type' => 'int',
 811              'label-message' => 'rows',
 812              'section' => 'editing/editor',
 813              'min' => 4,
 814              'max' => 1000,
 815          );
 816          if ( $user->isAllowed( 'minoredit' ) ) {
 817              $defaultPreferences['minordefault'] = array(
 818                  'type' => 'toggle',
 819                  'section' => 'editing/editor',
 820                  'label-message' => 'tog-minordefault',
 821              );
 822          }
 823          $defaultPreferences['forceeditsummary'] = array(
 824              'type' => 'toggle',
 825              'section' => 'editing/editor',
 826              'label-message' => 'tog-forceeditsummary',
 827          );
 828          $defaultPreferences['useeditwarning'] = array(
 829              'type' => 'toggle',
 830              'section' => 'editing/editor',
 831              'label-message' => 'tog-useeditwarning',
 832          );
 833          $defaultPreferences['showtoolbar'] = array(
 834              'type' => 'toggle',
 835              'section' => 'editing/editor',
 836              'label-message' => 'tog-showtoolbar',
 837          );
 838  
 839          $defaultPreferences['previewonfirst'] = array(
 840              'type' => 'toggle',
 841              'section' => 'editing/preview',
 842              'label-message' => 'tog-previewonfirst',
 843          );
 844          $defaultPreferences['previewontop'] = array(
 845              'type' => 'toggle',
 846              'section' => 'editing/preview',
 847              'label-message' => 'tog-previewontop',
 848          );
 849          $defaultPreferences['uselivepreview'] = array(
 850              'type' => 'toggle',
 851              'section' => 'editing/preview',
 852              'label-message' => 'tog-uselivepreview',
 853          );
 854  
 855      }
 856  
 857      /**
 858       * @param User $user
 859       * @param IContextSource $context
 860       * @param array $defaultPreferences
 861       */
 862  	static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 863          $config = $context->getConfig();
 864          $rcMaxAge = $config->get( 'RCMaxAge' );
 865          ## RecentChanges #####################################
 866          $defaultPreferences['rcdays'] = array(
 867              'type' => 'float',
 868              'label-message' => 'recentchangesdays',
 869              'section' => 'rc/displayrc',
 870              'min' => 1,
 871              'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
 872              'help' => $context->msg( 'recentchangesdays-max' )->numParams(
 873                  ceil( $rcMaxAge / ( 3600 * 24 ) ) )->text()
 874          );
 875          $defaultPreferences['rclimit'] = array(
 876              'type' => 'int',
 877              'label-message' => 'recentchangescount',
 878              'help-message' => 'prefs-help-recentchangescount',
 879              'section' => 'rc/displayrc',
 880          );
 881          $defaultPreferences['usenewrc'] = array(
 882              'type' => 'toggle',
 883              'label-message' => 'tog-usenewrc',
 884              'section' => 'rc/advancedrc',
 885          );
 886          $defaultPreferences['hideminor'] = array(
 887              'type' => 'toggle',
 888              'label-message' => 'tog-hideminor',
 889              'section' => 'rc/advancedrc',
 890          );
 891  
 892          if ( $user->useRCPatrol() ) {
 893              $defaultPreferences['hidepatrolled'] = array(
 894                  'type' => 'toggle',
 895                  'section' => 'rc/advancedrc',
 896                  'label-message' => 'tog-hidepatrolled',
 897              );
 898              $defaultPreferences['newpageshidepatrolled'] = array(
 899                  'type' => 'toggle',
 900                  'section' => 'rc/advancedrc',
 901                  'label-message' => 'tog-newpageshidepatrolled',
 902              );
 903          }
 904  
 905          if ( $config->get( 'RCShowWatchingUsers' ) ) {
 906              $defaultPreferences['shownumberswatching'] = array(
 907                  'type' => 'toggle',
 908                  'section' => 'rc/advancedrc',
 909                  'label-message' => 'tog-shownumberswatching',
 910              );
 911          }
 912      }
 913  
 914      /**
 915       * @param User $user
 916       * @param IContextSource $context
 917       * @param array $defaultPreferences
 918       */
 919  	static function watchlistPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 920          $config = $context->getConfig();
 921          $watchlistdaysMax = ceil( $config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
 922  
 923          ## Watchlist #####################################
 924          $defaultPreferences['watchlistdays'] = array(
 925              'type' => 'float',
 926              'min' => 0,
 927              'max' => $watchlistdaysMax,
 928              'section' => 'watchlist/displaywatchlist',
 929              'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
 930                  $watchlistdaysMax )->text(),
 931              'label-message' => 'prefs-watchlist-days',
 932          );
 933          $defaultPreferences['wllimit'] = array(
 934              'type' => 'int',
 935              'min' => 0,
 936              'max' => 1000,
 937              'label-message' => 'prefs-watchlist-edits',
 938              'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(),
 939              'section' => 'watchlist/displaywatchlist',
 940          );
 941          $defaultPreferences['extendwatchlist'] = array(
 942              'type' => 'toggle',
 943              'section' => 'watchlist/advancedwatchlist',
 944              'label-message' => 'tog-extendwatchlist',
 945          );
 946          $defaultPreferences['watchlisthideminor'] = array(
 947              'type' => 'toggle',
 948              'section' => 'watchlist/advancedwatchlist',
 949              'label-message' => 'tog-watchlisthideminor',
 950          );
 951          $defaultPreferences['watchlisthidebots'] = array(
 952              'type' => 'toggle',
 953              'section' => 'watchlist/advancedwatchlist',
 954              'label-message' => 'tog-watchlisthidebots',
 955          );
 956          $defaultPreferences['watchlisthideown'] = array(
 957              'type' => 'toggle',
 958              'section' => 'watchlist/advancedwatchlist',
 959              'label-message' => 'tog-watchlisthideown',
 960          );
 961          $defaultPreferences['watchlisthideanons'] = array(
 962              'type' => 'toggle',
 963              'section' => 'watchlist/advancedwatchlist',
 964              'label-message' => 'tog-watchlisthideanons',
 965          );
 966          $defaultPreferences['watchlisthideliu'] = array(
 967              'type' => 'toggle',
 968              'section' => 'watchlist/advancedwatchlist',
 969              'label-message' => 'tog-watchlisthideliu',
 970          );
 971  
 972          if ( $context->getConfig()->get( 'UseRCPatrol' ) ) {
 973              $defaultPreferences['watchlisthidepatrolled'] = array(
 974                  'type' => 'toggle',
 975                  'section' => 'watchlist/advancedwatchlist',
 976                  'label-message' => 'tog-watchlisthidepatrolled',
 977              );
 978          }
 979  
 980          $watchTypes = array(
 981              'edit' => 'watchdefault',
 982              'move' => 'watchmoves',
 983              'delete' => 'watchdeletion'
 984          );
 985  
 986          // Kinda hacky
 987          if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
 988              $watchTypes['read'] = 'watchcreations';
 989          }
 990  
 991          if ( $user->isAllowed( 'rollback' ) ) {
 992              $watchTypes['rollback'] = 'watchrollback';
 993          }
 994  
 995          foreach ( $watchTypes as $action => $pref ) {
 996              if ( $user->isAllowed( $action ) ) {
 997                  // Messages:
 998                  // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations
 999                  // tog-watchrollback
1000                  $defaultPreferences[$pref] = array(
1001                      'type' => 'toggle',
1002                      'section' => 'watchlist/advancedwatchlist',
1003                      'label-message' => "tog-$pref",
1004                  );
1005              }
1006          }
1007  
1008          if ( $config->get( 'EnableAPI' ) ) {
1009              $defaultPreferences['watchlisttoken'] = array(
1010                  'type' => 'api',
1011              );
1012              $defaultPreferences['watchlisttoken-info'] = array(
1013                  'type' => 'info',
1014                  'section' => 'watchlist/tokenwatchlist',
1015                  'label-message' => 'prefs-watchlist-token',
1016                  'default' => $user->getTokenFromOption( 'watchlisttoken' ),
1017                  'help-message' => 'prefs-help-watchlist-token2',
1018              );
1019          }
1020      }
1021  
1022      /**
1023       * @param User $user
1024       * @param IContextSource $context
1025       * @param array $defaultPreferences
1026       */
1027  	static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) {
1028          foreach ( MWNamespace::getValidNamespaces() as $n ) {
1029              $defaultPreferences['searchNs' . $n] = array(
1030                  'type' => 'api',
1031              );
1032          }
1033      }
1034  
1035      /**
1036       * Dummy, kept for backwards-compatibility.
1037       */
1038  	static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) {
1039      }
1040  
1041      /**
1042       * @param User $user The User object
1043       * @param IContextSource $context
1044       * @return array Text/links to display as key; $skinkey as value
1045       */
1046  	static function generateSkinOptions( $user, IContextSource $context ) {
1047          $ret = array();
1048  
1049          $mptitle = Title::newMainPage();
1050          $previewtext = $context->msg( 'skin-preview' )->text();
1051  
1052          # Only show skins that aren't disabled in $wgSkipSkins
1053          $validSkinNames = Skin::getAllowedSkins();
1054  
1055          # Sort by UI skin name. First though need to update validSkinNames as sometimes
1056          # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
1057          foreach ( $validSkinNames as $skinkey => &$skinname ) {
1058              $msg = $context->msg( "skinname-{$skinkey}" );
1059              if ( $msg->exists() ) {
1060                  $skinname = htmlspecialchars( $msg->text() );
1061              }
1062          }
1063          asort( $validSkinNames );
1064  
1065          $config = $context->getConfig();
1066          $defaultSkin = $config->get( 'DefaultSkin' );
1067          $allowUserCss = $config->get( 'AllowUserCss' );
1068          $allowUserJs = $config->get( 'AllowUserJs' );
1069  
1070          $foundDefault = false;
1071          foreach ( $validSkinNames as $skinkey => $sn ) {
1072              $linkTools = array();
1073  
1074              # Mark the default skin
1075              if ( $skinkey == $defaultSkin ) {
1076                  $linkTools[] = $context->msg( 'default' )->escaped();
1077                  $foundDefault = true;
1078              }
1079  
1080              # Create preview link
1081              $mplink = htmlspecialchars( $mptitle->getLocalURL( array( 'useskin' => $skinkey ) ) );
1082              $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1083  
1084              # Create links to user CSS/JS pages
1085              if ( $allowUserCss ) {
1086                  $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1087                  $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
1088              }
1089  
1090              if ( $allowUserJs ) {
1091                  $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1092                  $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
1093              }
1094  
1095              $display = $sn . ' ' . $context->msg(
1096                  'parentheses',
1097                  $context->getLanguage()->pipeList( $linkTools )
1098              )->text();
1099              $ret[$display] = $skinkey;
1100          }
1101  
1102          if ( !$foundDefault ) {
1103              // If the default skin is not available, things are going to break horribly because the
1104              // default value for skin selector will not be a valid value. Let's just not show it then.
1105              return array();
1106          }
1107  
1108          return $ret;
1109      }
1110  
1111      /**
1112       * @param IContextSource $context
1113       * @return array
1114       */
1115  	static function getDateOptions( IContextSource $context ) {
1116          $lang = $context->getLanguage();
1117          $dateopts = $lang->getDatePreferences();
1118  
1119          $ret = array();
1120  
1121          if ( $dateopts ) {
1122              if ( !in_array( 'default', $dateopts ) ) {
1123                  $dateopts[] = 'default'; // Make sure default is always valid
1124                                          // Bug 19237
1125              }
1126  
1127              // FIXME KLUGE: site default might not be valid for user language
1128              global $wgDefaultUserOptions;
1129              if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1130                  $wgDefaultUserOptions['date'] = 'default';
1131              }
1132  
1133              $epoch = wfTimestampNow();
1134              foreach ( $dateopts as $key ) {
1135                  if ( $key == 'default' ) {
1136                      $formatted = $context->msg( 'datedefault' )->escaped();
1137                  } else {
1138                      $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1139                  }
1140                  $ret[$formatted] = $key;
1141              }
1142          }
1143          return $ret;
1144      }
1145  
1146      /**
1147       * @param IContextSource $context
1148       * @return array
1149       */
1150  	static function getImageSizes( IContextSource $context ) {
1151          $ret = array();
1152          $pixels = $context->msg( 'unit-pixel' )->text();
1153  
1154          foreach ( $context->getConfig()->get( 'ImageLimits' ) as $index => $limits ) {
1155              $display = "{$limits[0]}×{$limits[1]}" . $pixels;
1156              $ret[$display] = $index;
1157          }
1158  
1159          return $ret;
1160      }
1161  
1162      /**
1163       * @param IContextSource $context
1164       * @return array
1165       */
1166  	static function getThumbSizes( IContextSource $context ) {
1167          $ret = array();
1168          $pixels = $context->msg( 'unit-pixel' )->text();
1169  
1170          foreach ( $context->getConfig()->get( 'ThumbLimits' ) as $index => $size ) {
1171              $display = $size . $pixels;
1172              $ret[$display] = $index;
1173          }
1174  
1175          return $ret;
1176      }
1177  
1178      /**
1179       * @param string $signature
1180       * @param array $alldata
1181       * @param HTMLForm $form
1182       * @return bool|string
1183       */
1184  	static function validateSignature( $signature, $alldata, $form ) {
1185          global $wgParser;
1186          $maxSigChars = $form->getConfig()->get( 'MaxSigChars' );
1187          if ( mb_strlen( $signature ) > $maxSigChars ) {
1188              return Xml::element( 'span', array( 'class' => 'error' ),
1189                  $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
1190          } elseif ( isset( $alldata['fancysig'] ) &&
1191                  $alldata['fancysig'] &&
1192                  $wgParser->validateSig( $signature ) === false
1193          ) {
1194              return Xml::element(
1195                  'span',
1196                  array( 'class' => 'error' ),
1197                  $form->msg( 'badsig' )->text()
1198              );
1199          } else {
1200              return true;
1201          }
1202      }
1203  
1204      /**
1205       * @param string $signature
1206       * @param array $alldata
1207       * @param HTMLForm $form
1208       * @return string
1209       */
1210  	static function cleanSignature( $signature, $alldata, $form ) {
1211          if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1212              global $wgParser;
1213              $signature = $wgParser->cleanSig( $signature );
1214          } else {
1215              // When no fancy sig used, make sure ~{3,5} get removed.
1216              $signature = Parser::cleanSigInSig( $signature );
1217          }
1218  
1219          return $signature;
1220      }
1221  
1222      /**
1223       * @param User $user
1224       * @param IContextSource $context
1225       * @param string $formClass
1226       * @param array $remove Array of items to remove
1227       * @return HtmlForm
1228       */
1229  	static function getFormObject(
1230          $user,
1231          IContextSource $context,
1232          $formClass = 'PreferencesForm',
1233          array $remove = array()
1234      ) {
1235          $formDescriptor = Preferences::getPreferences( $user, $context );
1236          if ( count( $remove ) ) {
1237              $removeKeys = array_flip( $remove );
1238              $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1239          }
1240  
1241          // Remove type=api preferences. They are not intended for rendering in the form.
1242          foreach ( $formDescriptor as $name => $info ) {
1243              if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1244                  unset( $formDescriptor[$name] );
1245              }
1246          }
1247  
1248          /**
1249           * @var $htmlForm PreferencesForm
1250           */
1251          $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1252  
1253          $htmlForm->setModifiedUser( $user );
1254          $htmlForm->setId( 'mw-prefs-form' );
1255          $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
1256          # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1257          $htmlForm->setSubmitTooltip( 'preferences-save' );
1258          $htmlForm->setSubmitID( 'prefsubmit' );
1259          $htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
1260  
1261          return $htmlForm;
1262      }
1263  
1264      /**
1265       * @param IContextSource $context
1266       * @return array
1267       */
1268  	static function getTimezoneOptions( IContextSource $context ) {
1269          $opt = array();
1270  
1271          $localTZoffset = $context->getConfig()->get( 'LocalTZoffset' );
1272          $timestamp = MWTimestamp::getLocalInstance();
1273          // Check that the LocalTZoffset is the same as the local time zone offset
1274          if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) {
1275              $server_tz_msg = $context->msg(
1276                  'timezoneuseserverdefault',
1277                  $timestamp->getTimezone()->getName()
1278              )->text();
1279          } else {
1280              $tzstring = sprintf(
1281                  '%+03d:%02d',
1282                  floor( $localTZoffset / 60 ),
1283                  abs( $localTZoffset ) % 60
1284              );
1285              $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1286          }
1287          $opt[$server_tz_msg] = "System|$localTZoffset";
1288          $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1289          $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1290  
1291          if ( function_exists( 'timezone_identifiers_list' ) ) {
1292              # Read timezone list
1293              $tzs = timezone_identifiers_list();
1294              sort( $tzs );
1295  
1296              $tzRegions = array();
1297              $tzRegions['Africa'] = $context->msg( 'timezoneregion-africa' )->text();
1298              $tzRegions['America'] = $context->msg( 'timezoneregion-america' )->text();
1299              $tzRegions['Antarctica'] = $context->msg( 'timezoneregion-antarctica' )->text();
1300              $tzRegions['Arctic'] = $context->msg( 'timezoneregion-arctic' )->text();
1301              $tzRegions['Asia'] = $context->msg( 'timezoneregion-asia' )->text();
1302              $tzRegions['Atlantic'] = $context->msg( 'timezoneregion-atlantic' )->text();
1303              $tzRegions['Australia'] = $context->msg( 'timezoneregion-australia' )->text();
1304              $tzRegions['Europe'] = $context->msg( 'timezoneregion-europe' )->text();
1305              $tzRegions['Indian'] = $context->msg( 'timezoneregion-indian' )->text();
1306              $tzRegions['Pacific'] = $context->msg( 'timezoneregion-pacific' )->text();
1307              asort( $tzRegions );
1308  
1309              $prefill = array_fill_keys( array_values( $tzRegions ), array() );
1310              $opt = array_merge( $opt, $prefill );
1311  
1312              $now = date_create( 'now' );
1313  
1314              foreach ( $tzs as $tz ) {
1315                  $z = explode( '/', $tz, 2 );
1316  
1317                  # timezone_identifiers_list() returns a number of
1318                  # backwards-compatibility entries. This filters them out of the
1319                  # list presented to the user.
1320                  if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) ) {
1321                      continue;
1322                  }
1323  
1324                  # Localize region
1325                  $z[0] = $tzRegions[$z[0]];
1326  
1327                  $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
1328  
1329                  $display = str_replace( '_', ' ', $z[0] . '/' . $z[1] );
1330                  $value = "ZoneInfo|$minDiff|$tz";
1331  
1332                  $opt[$z[0]][$display] = $value;
1333              }
1334          }
1335          return $opt;
1336      }
1337  
1338      /**
1339       * @param string $value
1340       * @param array $alldata
1341       * @return int
1342       */
1343  	static function filterIntval( $value, $alldata ) {
1344          return intval( $value );
1345      }
1346  
1347      /**
1348       * @param string $tz
1349       * @param array $alldata
1350       * @return string
1351       */
1352  	static function filterTimezoneInput( $tz, $alldata ) {
1353          $data = explode( '|', $tz, 3 );
1354          switch ( $data[0] ) {
1355              case 'ZoneInfo':
1356              case 'System':
1357                  return $tz;
1358              default:
1359                  $data = explode( ':', $tz, 2 );
1360                  if ( count( $data ) == 2 ) {
1361                      $data[0] = intval( $data[0] );
1362                      $data[1] = intval( $data[1] );
1363                      $minDiff = abs( $data[0] ) * 60 + $data[1];
1364                      if ( $data[0] < 0 ) {
1365                          $minDiff = - $minDiff;
1366                      }
1367                  } else {
1368                      $minDiff = intval( $data[0] ) * 60;
1369                  }
1370  
1371                  # Max is +14:00 and min is -12:00, see:
1372                  # http://en.wikipedia.org/wiki/Timezone
1373                  $minDiff = min( $minDiff, 840 );  # 14:00
1374                  $minDiff = max( $minDiff, - 720 ); # -12:00
1375                  return 'Offset|' . $minDiff;
1376          }
1377      }
1378  
1379      /**
1380       * Handle the form submission if everything validated properly
1381       *
1382       * @param array $formData
1383       * @param PreferencesForm $form
1384       * @return bool|Status|string
1385       */
1386  	static function tryFormSubmit( $formData, $form ) {
1387          global $wgAuth;
1388  
1389          $user = $form->getModifiedUser();
1390          $hiddenPrefs = $form->getConfig()->get( 'HiddenPrefs' );
1391          $result = true;
1392  
1393          if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1394              return Status::newFatal( 'mypreferencesprotected' );
1395          }
1396  
1397          // Filter input
1398          foreach ( array_keys( $formData ) as $name ) {
1399              if ( isset( self::$saveFilters[$name] ) ) {
1400                  $formData[$name] =
1401                      call_user_func( self::$saveFilters[$name], $formData[$name], $formData );
1402              }
1403          }
1404  
1405          // Fortunately, the realname field is MUCH simpler
1406          // (not really "private", but still shouldn't be edited without permission)
1407          if ( !in_array( 'realname', $hiddenPrefs )
1408              && $user->isAllowed( 'editmyprivateinfo' )
1409              && array_key_exists( 'realname', $formData )
1410          ) {
1411              $realName = $formData['realname'];
1412              $user->setRealName( $realName );
1413          }
1414  
1415          if ( $user->isAllowed( 'editmyoptions' ) ) {
1416              foreach ( self::$saveBlacklist as $b ) {
1417                  unset( $formData[$b] );
1418              }
1419  
1420              # If users have saved a value for a preference which has subsequently been disabled
1421              # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1422              # is subsequently re-enabled
1423              foreach ( $hiddenPrefs as $pref ) {
1424                  # If the user has not set a non-default value here, the default will be returned
1425                  # and subsequently discarded
1426                  $formData[$pref] = $user->getOption( $pref, null, true );
1427              }
1428  
1429              // Keep old preferences from interfering due to back-compat code, etc.
1430              $user->resetOptions( 'unused', $form->getContext() );
1431  
1432              foreach ( $formData as $key => $value ) {
1433                  $user->setOption( $key, $value );
1434              }
1435  
1436              wfRunHooks( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) );
1437              $user->saveSettings();
1438          }
1439  
1440          $wgAuth->updateExternalDB( $user );
1441  
1442          return $result;
1443      }
1444  
1445      /**
1446       * @param array $formData
1447       * @param PreferencesForm $form
1448       * @return Status
1449       */
1450  	public static function tryUISubmit( $formData, $form ) {
1451          $res = self::tryFormSubmit( $formData, $form );
1452  
1453          if ( $res ) {
1454              $urlOptions = array( 'success' => 1 );
1455  
1456              if ( $res === 'eauth' ) {
1457                  $urlOptions['eauth'] = 1;
1458              }
1459  
1460              $urlOptions += $form->getExtraSuccessRedirectParameters();
1461  
1462              $url = $form->getTitle()->getFullURL( $urlOptions );
1463  
1464              $form->getContext()->getOutput()->redirect( $url );
1465          }
1466  
1467          return Status::newGood();
1468      }
1469  
1470      /**
1471       * Try to set a user's email address.
1472       * This does *not* try to validate the address.
1473       * Caller is responsible for checking $wgAuth and 'editmyprivateinfo'
1474       * right.
1475       *
1476       * @deprecated since 1.20; use User::setEmailWithConfirmation() instead.
1477       * @param User $user
1478       * @param string $newaddr New email address
1479       * @return array (true on success or Status on failure, info string)
1480       */
1481  	public static function trySetUserEmail( User $user, $newaddr ) {
1482          wfDeprecated( __METHOD__, '1.20' );
1483  
1484          $result = $user->setEmailWithConfirmation( $newaddr );
1485          if ( $result->isGood() ) {
1486              return array( true, $result->value );
1487          } else {
1488              return array( $result, 'mailerror' );
1489          }
1490      }
1491  }
1492  
1493  /** Some tweaks to allow js prefs to work */
1494  class PreferencesForm extends HTMLForm {
1495      // Override default value from HTMLForm
1496      protected $mSubSectionBeforeFields = false;
1497  
1498      private $modifiedUser;
1499  
1500      /**
1501       * @param User $user
1502       */
1503  	public function setModifiedUser( $user ) {
1504          $this->modifiedUser = $user;
1505      }
1506  
1507      /**
1508       * @return User
1509       */
1510  	public function getModifiedUser() {
1511          if ( $this->modifiedUser === null ) {
1512              return $this->getUser();
1513          } else {
1514              return $this->modifiedUser;
1515          }
1516      }
1517  
1518      /**
1519       * Get extra parameters for the query string when redirecting after
1520       * successful save.
1521       *
1522       * @return array()
1523       */
1524  	public function getExtraSuccessRedirectParameters() {
1525          return array();
1526      }
1527  
1528      /**
1529       * @param string $html
1530       * @return string
1531       */
1532  	function wrapForm( $html ) {
1533          $html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
1534  
1535          return parent::wrapForm( $html );
1536      }
1537  
1538      /**
1539       * @return string
1540       */
1541  	function getButtons() {
1542          global $wgUseMediaWikiUIEverywhere;
1543  
1544          $attrs = array( 'id' => 'mw-prefs-restoreprefs' );
1545          if ( $wgUseMediaWikiUIEverywhere ) {
1546              $attrs['class'] = 'mw-ui-button mw-ui-quiet';
1547          }
1548  
1549          if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1550              return '';
1551          }
1552  
1553          $html = parent::getButtons();
1554  
1555          if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
1556              $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
1557  
1558              $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped(),
1559                  $attrs );
1560  
1561              $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
1562          }
1563  
1564          return $html;
1565      }
1566  
1567      /**
1568       * Separate multi-option preferences into multiple preferences, since we
1569       * have to store them separately
1570       * @param array $data
1571       * @return array
1572       */
1573  	function filterDataForSubmit( $data ) {
1574          foreach ( $this->mFlatFields as $fieldname => $field ) {
1575              if ( $field instanceof HTMLNestedFilterable ) {
1576                  $info = $field->mParams;
1577                  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
1578                  foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
1579                      $data["$prefix$key"] = $value;
1580                  }
1581                  unset( $data[$fieldname] );
1582              }
1583          }
1584  
1585          return $data;
1586      }
1587  
1588      /**
1589       * Get the whole body of the form.
1590       * @return string
1591       */
1592  	function getBody() {
1593          return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
1594      }
1595  
1596      /**
1597       * Get the "<legend>" for a given section key. Normally this is the
1598       * prefs-$key message but we'll allow extensions to override it.
1599       * @param string $key
1600       * @return string
1601       */
1602  	function getLegend( $key ) {
1603          $legend = parent::getLegend( $key );
1604          wfRunHooks( 'PreferencesGetLegend', array( $this, $key, &$legend ) );
1605          return $legend;
1606      }
1607  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1