Merge "Rename Skin::getUsableSkins() to Skin::getAllowedSkins()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 13 Apr 2014 00:30:53 +0000 (00:30 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 13 Apr 2014 00:30:53 +0000 (00:30 +0000)
1  2 
includes/Preferences.php
includes/Skin.php
includes/api/ApiQuerySiteinfo.php

diff --combined includes/Preferences.php
@@@ -56,19 -56,6 +56,19 @@@ class Preferences 
                        'searchlimit' => array( 'Preferences', 'filterIntval' ),
        );
  
 +      // Stuff that shouldn't be saved as a preference.
 +      private static $saveBlacklist = array(
 +              'realname',
 +              'emailaddress',
 +      );
 +
 +      /**
 +       * @return array
 +       */
 +      static function getSaveBlacklist() {
 +              return self::$saveBlacklist;
 +      }
 +
        /**
         * @throws MWException
         * @param $user User
@@@ -84,8 -71,8 +84,8 @@@
  
                self::profilePreferences( $user, $context, $defaultPreferences );
                self::skinPreferences( $user, $context, $defaultPreferences );
 -              self::filesPreferences( $user, $context, $defaultPreferences );
                self::datetimePreferences( $user, $context, $defaultPreferences );
 +              self::filesPreferences( $user, $context, $defaultPreferences );
                self::renderingPreferences( $user, $context, $defaultPreferences );
                self::editingPreferences( $user, $context, $defaultPreferences );
                self::rcPreferences( $user, $context, $defaultPreferences );
                ## Make sure that form fields have their parent set. See bug 41337.
                $dummyForm = new HTMLForm( array(), $context );
  
 +              $disable = !$user->isAllowed( 'editmyoptions' );
 +
                ## Prod in defaults from the user
                foreach ( $defaultPreferences as $name => &$info ) {
                        $prefFromUser = self::getOptionFromUser( $name, $info, $user );
 +                      if ( $disable && !in_array( $name, self::$saveBlacklist ) ) {
 +                              $info['disabled'] = 'disabled';
 +                      }
                        $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation
                        $field->mParent = $dummyForm;
                        $defaultOptions = User::getDefaultOptions();
         */
        static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) {
                global $wgAuth, $wgContLang, $wgParser, $wgCookieExpiration, $wgLanguageCode,
 -                      $wgDisableTitleConversion, $wgDisableLangConversion, $wgMaxSigChars,
 +                      $wgDisableLangConversion, $wgMaxSigChars,
                        $wgEnableEmail, $wgEmailConfirmToEdit, $wgEnableUserEmail, $wgEmailAuthentication,
 -                      $wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress;
 +                      $wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress,
 +                      $wgSecureLogin;
  
                // retrieving user name for GENDER and misc.
                $userName = $user->getName();
                        'section' => 'personal/info',
                );
  
 +              $editCount = Linker::link( SpecialPage::getTitleFor( "Contributions", $userName ),
 +                      $lang->formatNum( $user->getEditCount() ) );
 +
                $defaultPreferences['editcount'] = array(
                        'type' => 'info',
 +                      'raw' => true,
                        'label-message' => 'prefs-edits',
 -                      'default' => $lang->formatNum( $user->getEditCount() ),
 +                      'default' => $editCount,
                        'section' => 'personal/info',
                );
  
                        );
                }
  
 +              $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
 +              $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
 +
                // Actually changeable stuff
                $defaultPreferences['realname'] = array(
 -                      'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
 +                      // (not really "private", but still shouldn't be edited without permission)
 +                      'type' => $canEditPrivateInfo && $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
                        'default' => $user->getRealName(),
                        'section' => 'personal/info',
                        'label-message' => 'yourrealname',
                        'help-message' => 'prefs-help-realname',
                );
  
 -              $defaultPreferences['gender'] = array(
 -                      'type' => 'select',
 -                      'section' => 'personal/info',
 -                      'options' => array(
 -                              $context->msg( 'gender-male' )->text() => 'male',
 -                              $context->msg( 'gender-female' )->text() => 'female',
 -                              $context->msg( 'gender-unknown' )->text() => 'unknown',
 -                      ),
 -                      'label-message' => 'yourgender',
 -                      'help-message' => 'prefs-help-gender',
 -              );
 -
 -              if ( $wgAuth->allowPasswordChange() ) {
 +              if ( $canEditPrivateInfo && $wgAuth->allowPasswordChange() ) {
                        $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
                                $context->msg( 'prefs-resetpass' )->escaped(), array(),
                                array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
                                'section' => 'personal/info',
                        );
                }
 +              // Only show prefershttps if secure login is turned on
 +              if ( $wgSecureLogin && wfCanIPUseHTTPS( $context->getRequest()->getIP() ) ) {
 +                      $defaultPreferences['prefershttps'] = array(
 +                              'type' => 'toggle',
 +                              'label-message' => 'tog-prefershttps',
 +                              'help-message' => 'prefs-help-prefershttps',
 +                              'section' => 'personal/info'
 +                      );
 +              }
  
                // Language
                $languages = Language::fetchLanguageNames( null, 'mw' );
                        'label-message' => 'yourlanguage',
                );
  
 +              $defaultPreferences['gender'] = array(
 +                      'type' => 'radio',
 +                      'section' => 'personal/i18n',
 +                      'options' => array(
 +                              $context->msg( 'parentheses',
 +                                      $context->msg( 'gender-unknown' )->text()
 +                              )->text() => 'unknown',
 +                              $context->msg( 'gender-female' )->text() => 'female',
 +                              $context->msg( 'gender-male' )->text() => 'male',
 +                      ),
 +                      'label-message' => 'yourgender',
 +                      'help-message' => 'prefs-help-gender',
 +              );
 +
                // see if there are multiple language variants to choose from
                if ( !$wgDisableLangConversion ) {
 -                      $variants = $wgContLang->getVariants();
 +                      foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
 +                              if ( $langCode == $wgContLang->getCode() ) {
 +                                      $variants = $wgContLang->getVariants();
  
 -                      if ( count( $variants ) > 1 ) {
 -                              $variantArray = array();
 -                              foreach ( $variants as $v ) {
 -                                      $v = str_replace( '_', '-', strtolower( $v ) );
 -                                      $variantArray[$v] = $wgContLang->getVariantname( $v, false );
 -                              }
 +                                      if ( count( $variants ) <= 1 ) {
 +                                              continue;
 +                                      }
  
 -                              $options = array();
 -                              foreach ( $variantArray as $code => $name ) {
 -                                      $display = wfBCP47( $code ) . ' - ' . $name;
 -                                      $options[$display] = $code;
 -                              }
 +                                      $variantArray = array();
 +                                      foreach ( $variants as $v ) {
 +                                              $v = str_replace( '_', '-', strtolower( $v ) );
 +                                              $variantArray[$v] = $lang->getVariantname( $v, false );
 +                                      }
  
 -                              $defaultPreferences['variant'] = array(
 -                                      'label-message' => 'yourvariant',
 -                                      'type' => 'select',
 -                                      'options' => $options,
 -                                      'section' => 'personal/i18n',
 -                                      'help-message' => 'prefs-help-variant',
 -                              );
 +                                      $options = array();
 +                                      foreach ( $variantArray as $code => $name ) {
 +                                              $display = wfBCP47( $code ) . ' - ' . $name;
 +                                              $options[$display] = $code;
 +                                      }
  
 -                              if ( !$wgDisableTitleConversion ) {
 -                                      $defaultPreferences['noconvertlink'] =
 -                                              array(
 -                                              'type' => 'toggle',
 +                                      $defaultPreferences['variant'] = array(
 +                                              'label-message' => 'yourvariant',
 +                                              'type' => 'select',
 +                                              'options' => $options,
                                                'section' => 'personal/i18n',
 -                                              'label-message' => 'tog-noconvertlink',
 +                                              'help-message' => 'prefs-help-variant',
 +                                      );
 +                              } else {
 +                                      $defaultPreferences["variant-$langCode"] = array(
 +                                              'type' => 'api',
                                        );
                                }
                        }
                }
  
 +              // Stuff from Language::getExtraUserToggles()
 +              // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language
 +              $toggles = $wgContLang->getExtraUserToggles();
 +
 +              foreach ( $toggles as $toggle ) {
 +                      $defaultPreferences[$toggle] = array(
 +                              'type' => 'toggle',
 +                              'section' => 'personal/i18n',
 +                              'label-message' => "tog-$toggle",
 +                      );
 +              }
 +
                // show a preview of the old signature first
 -              $oldsigWikiText = $wgParser->preSaveTransform( "~~~", $context->getTitle(), $user, ParserOptions::newFromContext( $context ) );
 +              $oldsigWikiText = $wgParser->preSaveTransform(
 +                      '~~~',
 +                      $context->getTitle(),
 +                      $user,
 +                      ParserOptions::newFromContext( $context )
 +              );
                $oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true );
                $defaultPreferences['oldsig'] = array(
                        'type' => 'info',
                $defaultPreferences['fancysig'] = array(
                        'type' => 'toggle',
                        'label-message' => 'tog-fancysig',
 -                      'help-message' => 'prefs-help-signature', // show general help about signature at the bottom of the section
 +                      // show general help about signature at the bottom of the section
 +                      'help-message' => 'prefs-help-signature',
                        'section' => 'personal/signature'
                );
  
                ## Email stuff
  
                if ( $wgEnableEmail ) {
 -                      $helpMessages[] = $wgEmailConfirmToEdit
 -                                      ? 'prefs-help-email-required'
 -                                      : 'prefs-help-email';
 -
 -                      if ( $wgEnableUserEmail ) {
 -                              // additional messages when users can send email to each other
 -                              $helpMessages[] = 'prefs-help-email-others';
 -                      }
 +                      if ( $canViewPrivateInfo ) {
 +                              $helpMessages[] = $wgEmailConfirmToEdit
 +                                              ? 'prefs-help-email-required'
 +                                              : 'prefs-help-email';
 +
 +                              if ( $wgEnableUserEmail ) {
 +                                      // additional messages when users can send email to each other
 +                                      $helpMessages[] = 'prefs-help-email-others';
 +                              }
  
 -                      $link = Linker::link(
 -                              SpecialPage::getTitleFor( 'ChangeEmail' ),
 -                              $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
 -                              array(),
 -                              array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
 +                              $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
 +                              if ( $canEditPrivateInfo && $wgAuth->allowPropChange( 'emailaddress' ) ) {
 +                                      $link = Linker::link(
 +                                              SpecialPage::getTitleFor( 'ChangeEmail' ),
 +                                              $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
 +                                              array(),
 +                                              array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
 +
 +                                      $emailAddress .= $emailAddress == '' ? $link : (
 +                                              $context->msg( 'word-separator' )->plain()
 +                                              . $context->msg( 'parentheses' )->rawParams( $link )->plain()
 +                                      );
 +                              }
  
 -                      $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
 -                      if ( $wgAuth->allowPropChange( 'emailaddress' ) ) {
 -                              $emailAddress .= $emailAddress == '' ? $link : (
 -                                      $context->msg( 'word-separator' )->plain()
 -                                      . $context->msg( 'parentheses' )->rawParams( $link )->plain()
 +                              $defaultPreferences['emailaddress'] = array(
 +                                      'type' => 'info',
 +                                      'raw' => true,
 +                                      'default' => $emailAddress,
 +                                      'label-message' => 'youremail',
 +                                      'section' => 'personal/email',
 +                                      'help-messages' => $helpMessages,
 +                                      # 'cssclass' chosen below
                                );
                        }
  
 -                      $defaultPreferences['emailaddress'] = array(
 -                              'type' => 'info',
 -                              'raw' => true,
 -                              'default' => $emailAddress,
 -                              'label-message' => 'youremail',
 -                              'section' => 'personal/email',
 -                              'help-messages' => $helpMessages,
 -                              # 'cssclass' chosen below
 -                      );
 -
                        $disableEmailPrefs = false;
  
                        if ( $wgEmailAuthentication ) {
                                        $emailauthenticationclass = 'mw-email-none';
                                }
  
 -                              $defaultPreferences['emailauthentication'] = array(
 -                                      'type' => 'info',
 -                                      'raw' => true,
 -                                      'section' => 'personal/email',
 -                                      'label-message' => 'prefs-emailconfirm-label',
 -                                      'default' => $emailauthenticated,
 -                                      # Apply the same CSS class used on the input to the message:
 -                                      'cssclass' => $emailauthenticationclass,
 -                              );
 -                              $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
 +                              if ( $canViewPrivateInfo ) {
 +                                      $defaultPreferences['emailauthentication'] = array(
 +                                              'type' => 'info',
 +                                              'raw' => true,
 +                                              'section' => 'personal/email',
 +                                              'label-message' => 'prefs-emailconfirm-label',
 +                                              'default' => $emailauthenticated,
 +                                              # Apply the same CSS class used on the input to the message:
 +                                              'cssclass' => $emailauthenticationclass,
 +                                      );
 +                                      $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
 +                              }
                        }
  
                        if ( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
                                'type' => 'radio',
                                'options' => $dateOptions,
                                'label' => '&#160;',
 -                              'section' => 'datetime/dateformat',
 +                              'section' => 'rendering/dateformat',
                        );
                }
  
                        'raw' => 1,
                        'label-message' => 'servertime',
                        'default' => $nowserver,
 -                      'section' => 'datetime/timeoffset',
 +                      'section' => 'rendering/timeoffset',
                );
  
                $defaultPreferences['nowlocal'] = array(
                        'raw' => 1,
                        'label-message' => 'localtime',
                        'default' => $nowlocal,
 -                      'section' => 'datetime/timeoffset',
 +                      'section' => 'rendering/timeoffset',
                );
  
                // Grab existing pref.
                        $minDiff = $tz[1];
                        $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
                } elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
 -                      !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) ) )
 -              {
 +                      !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
 +              {
                        # Timezone offset can vary with DST
                        $userTZ = timezone_open( $tz[2] );
                        if ( $userTZ !== false ) {
                        'options' => $tzOptions,
                        'default' => $tzSetting,
                        'size' => 20,
 -                      'section' => 'datetime/timeoffset',
 +                      'section' => 'rendering/timeoffset',
                );
        }
  
         * @param $defaultPreferences Array
         */
        static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 +              ## Diffs ####################################
 +              $defaultPreferences['diffonly'] = array(
 +                      'type' => 'toggle',
 +                      'section' => 'rendering/diffs',
 +                      'label-message' => 'tog-diffonly',
 +              );
 +              $defaultPreferences['norollbackdiff'] = array(
 +                      'type' => 'toggle',
 +                      'section' => 'rendering/diffs',
 +                      'label-message' => 'tog-norollbackdiff',
 +              );
 +
                ## Page Rendering ##############################
                global $wgAllowUserCssPrefs;
                if ( $wgAllowUserCssPrefs ) {
                }
  
                $defaultPreferences['stubthreshold'] = array(
 -                      'type' => 'selectorother',
 +                      'type' => 'select',
                        'section' => 'rendering/advancedrendering',
                        'options' => $stubThresholdOptions,
                        'size' => 20,
                        'label-raw' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
                );
  
 -              if ( $wgAllowUserCssPrefs ) {
 -                      $defaultPreferences['showtoc'] = array(
 -                              'type' => 'toggle',
 -                              'section' => 'rendering/advancedrendering',
 -                              'label-message' => 'tog-showtoc',
 -                      );
 -              }
 -              $defaultPreferences['nocache'] = array(
 -                      'type' => 'toggle',
 -                      'label-message' => 'tog-nocache',
 -                      'section' => 'rendering/advancedrendering',
 -              );
                $defaultPreferences['showhiddencats'] = array(
                        'type' => 'toggle',
                        'section' => 'rendering/advancedrendering',
                        'label-message' => 'tog-showhiddencats'
                );
 -              $defaultPreferences['showjumplinks'] = array(
 -                      'type' => 'toggle',
 -                      'section' => 'rendering/advancedrendering',
 -                      'label-message' => 'tog-showjumplinks',
 -              );
 -
 -              if ( $wgAllowUserCssPrefs ) {
 -                      $defaultPreferences['justify'] = array(
 -                              'type' => 'toggle',
 -                              'section' => 'rendering/advancedrendering',
 -                              'label-message' => 'tog-justify',
 -                      );
 -              }
  
                $defaultPreferences['numberheadings'] = array(
                        'type' => 'toggle',
                global $wgAllowUserCssPrefs;
  
                ## Editing #####################################
 -              $defaultPreferences['cols'] = array(
 -                      'type' => 'int',
 -                      'label-message' => 'columns',
 -                      'section' => 'editing/textboxsize',
 -                      'min' => 4,
 -                      'max' => 1000,
 +              $defaultPreferences['editsectiononrightclick'] = array(
 +                      'type' => 'toggle',
 +                      'section' => 'editing/advancedediting',
 +                      'label-message' => 'tog-editsectiononrightclick',
                );
 -              $defaultPreferences['rows'] = array(
 -                      'type' => 'int',
 -                      'label-message' => 'rows',
 -                      'section' => 'editing/textboxsize',
 -                      'min' => 4,
 -                      'max' => 1000,
 +              $defaultPreferences['editondblclick'] = array(
 +                      'type' => 'toggle',
 +                      'section' => 'editing/advancedediting',
 +                      'label-message' => 'tog-editondblclick',
                );
  
                if ( $wgAllowUserCssPrefs ) {
                        $defaultPreferences['editfont'] = array(
                                'type' => 'select',
 -                              'section' => 'editing/advancedediting',
 +                              'section' => 'editing/editor',
                                'label-message' => 'editfont-style',
                                'options' => array(
                                        $context->msg( 'editfont-default' )->text() => 'default',
                                )
                        );
                }
 -              $defaultPreferences['previewontop'] = array(
 -                      'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-previewontop',
 +              $defaultPreferences['cols'] = array(
 +                      'type' => 'int',
 +                      'label-message' => 'columns',
 +                      'section' => 'editing/editor',
 +                      'min' => 4,
 +                      'max' => 1000,
                );
 -              $defaultPreferences['previewonfirst'] = array(
 -                      'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-previewonfirst',
 +              $defaultPreferences['rows'] = array(
 +                      'type' => 'int',
 +                      'label-message' => 'rows',
 +                      'section' => 'editing/editor',
 +                      'min' => 4,
 +                      'max' => 1000,
                );
 -
 -              if ( $wgAllowUserCssPrefs ) {
 -                      $defaultPreferences['editsection'] = array(
 +              if ( $user->isAllowed( 'minoredit' ) ) {
 +                      $defaultPreferences['minordefault'] = array(
                                'type' => 'toggle',
 -                              'section' => 'editing/advancedediting',
 -                              'label-message' => 'tog-editsection',
 +                              'section' => 'editing/editor',
 +                              'label-message' => 'tog-minordefault',
                        );
                }
 -              $defaultPreferences['editsectiononrightclick'] = array(
 +              $defaultPreferences['forceeditsummary'] = array(
                        'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-editsectiononrightclick',
 +                      'section' => 'editing/editor',
 +                      'label-message' => 'tog-forceeditsummary',
                );
 -              $defaultPreferences['editondblclick'] = array(
 +              $defaultPreferences['useeditwarning'] = array(
                        'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-editondblclick',
 +                      'section' => 'editing/editor',
 +                      'label-message' => 'tog-useeditwarning',
                );
                $defaultPreferences['showtoolbar'] = array(
                        'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 +                      'section' => 'editing/editor',
                        'label-message' => 'tog-showtoolbar',
                );
  
 -              if ( $user->isAllowed( 'minoredit' ) ) {
 -                      $defaultPreferences['minordefault'] = array(
 -                              'type' => 'toggle',
 -                              'section' => 'editing/advancedediting',
 -                              'label-message' => 'tog-minordefault',
 -                      );
 -              }
 -
 -              $defaultPreferences['forceeditsummary'] = array(
 +              $defaultPreferences['previewonfirst'] = array(
                        'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-forceeditsummary',
 +                      'section' => 'editing/preview',
 +                      'label-message' => 'tog-previewonfirst',
                );
 -
 -              $defaultPreferences['uselivepreview'] = array(
 +              $defaultPreferences['previewontop'] = array(
                        'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-uselivepreview',
 +                      'section' => 'editing/preview',
 +                      'label-message' => 'tog-previewontop',
                );
 -
 -              $defaultPreferences['useeditwarning'] = array(
 +              $defaultPreferences['uselivepreview'] = array(
                        'type' => 'toggle',
 -                      'section' => 'editing/advancedediting',
 -                      'label-message' => 'tog-useeditwarning',
 +                      'section' => 'editing/preview',
 +                      'label-message' => 'tog-uselivepreview',
                );
  
        }
                        );
                }
  
 -              if ( $wgEnableAPI ) {
 -                      # Some random gibberish as a proposed default
 -                      // @todo Fixme: this should use CryptRand but we may not want to read urandom on every view
 -                      $hash = sha1( mt_rand() . microtime( true ) );
 -
 -                      $defaultPreferences['watchlisttoken'] = array(
 -                              'type' => 'text',
 -                              'section' => 'watchlist/advancedwatchlist',
 -                              'label-message' => 'prefs-watchlist-token',
 -                              'help' => $context->msg( 'prefs-help-watchlist-token', $hash )->escaped()
 -                      );
 -              }
 -
                $watchTypes = array(
                        'edit' => 'watchdefault',
                        'move' => 'watchmoves',
  
                foreach ( $watchTypes as $action => $pref ) {
                        if ( $user->isAllowed( $action ) ) {
 +                              // Messages:
 +                              // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations
                                $defaultPreferences[$pref] = array(
                                        'type' => 'toggle',
                                        'section' => 'watchlist/advancedwatchlist',
                                );
                        }
                }
 +
 +              if ( $wgEnableAPI ) {
 +                      $defaultPreferences['watchlisttoken'] = array(
 +                              'type' => 'api',
 +                      );
 +                      $defaultPreferences['watchlisttoken-info'] = array(
 +                              'type' => 'info',
 +                              'section' => 'watchlist/tokenwatchlist',
 +                              'label-message' => 'prefs-watchlist-token',
 +                              'default' => $user->getTokenFromOption( 'watchlisttoken' ),
 +                              'help-message' => 'prefs-help-watchlist-token2',
 +                      );
 +              }
        }
  
        /**
         * @param $defaultPreferences Array
         */
        static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 -              global $wgContLang, $wgVectorUseSimpleSearch;
 -
 -              ## Search #####################################
 -              $defaultPreferences['searchlimit'] = array(
 -                      'type' => 'int',
 -                      'label-message' => 'resultsperpage',
 -                      'section' => 'searchoptions/displaysearchoptions',
 -                      'min' => 0,
 -              );
 -
 -              if ( $wgVectorUseSimpleSearch ) {
 -                      $defaultPreferences['vector-simplesearch'] = array(
 -                              'type' => 'toggle',
 -                              'label-message' => 'vector-simplesearch-preference',
 -                              'section' => 'searchoptions/displaysearchoptions',
 -                      );
 -              }
 -
 -              $defaultPreferences['disablesuggest'] = array(
 -                      'type' => 'toggle',
 -                      'label-message' => 'mwsuggest-disable',
 -                      'section' => 'searchoptions/displaysearchoptions',
 -              );
 +              global $wgContLang;
  
                $defaultPreferences['searcheverything'] = array(
                        'type' => 'toggle',
        }
  
        /**
 -       * @param $user User
 -       * @param $context IContextSource
 -       * @param $defaultPreferences Array
 +       * Dummy, kept for backwards-compatibility.
         */
        static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) {
 -              global $wgContLang;
 -
 -              ## Misc #####################################
 -              $defaultPreferences['diffonly'] = array(
 -                      'type' => 'toggle',
 -                      'section' => 'misc/diffs',
 -                      'label-message' => 'tog-diffonly',
 -              );
 -              $defaultPreferences['norollbackdiff'] = array(
 -                      'type' => 'toggle',
 -                      'section' => 'misc/diffs',
 -                      'label-message' => 'tog-norollbackdiff',
 -              );
 -
 -              // Stuff from Language::getExtraUserToggles()
 -              $toggles = $wgContLang->getExtraUserToggles();
 -
 -              foreach ( $toggles as $toggle ) {
 -                      $defaultPreferences[$toggle] = array(
 -                              'type' => 'toggle',
 -                              'section' => 'personal/i18n',
 -                              'label-message' => "tog-$toggle",
 -                      );
 -              }
        }
  
        /**
                $mptitle = Title::newMainPage();
                $previewtext = $context->msg( 'skin-preview' )->text();
  
-               # Only show members of Skin::getSkinNames() rather than
-               # $skinNames (skins is all skin names from Language.php)
-               $validSkinNames = Skin::getUsableSkins();
+               # Only show skins that aren't disabled in $wgSkipSkins
+               $validSkinNames = Skin::getAllowedSkins();
  
                # Sort by UI skin name. First though need to update validSkinNames as sometimes
                # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
                                $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
                        }
  
 -                      $display = $sn . ' ' . $context->msg( 'parentheses', $context->getLanguage()->pipeList( $linkTools ) )->text();
 +                      $display = $sn . ' ' . $context->msg(
 +                              'parentheses',
 +                              $context->getLanguage()->pipeList( $linkTools )
 +                      )->text();
                        $ret[$display] = $skinkey;
                }
  
                                $form->msg( 'badsiglength' )->numParams( $wgMaxSigChars )->text() );
                } elseif ( isset( $alldata['fancysig'] ) &&
                                $alldata['fancysig'] &&
 -                              false === $wgParser->validateSig( $signature ) ) {
 -                      return Xml::element( 'span', array( 'class' => 'error' ), $form->msg( 'badsig' )->text() );
 +                              $wgParser->validateSig( $signature ) === false
 +              ) {
 +                      return Xml::element(
 +                              'span',
 +                              array( 'class' => 'error' ),
 +                              $form->msg( 'badsig' )->text()
 +                      );
                } else {
                        return true;
                }
         * @param array $remove array of items to remove
         * @return HtmlForm
         */
 -      static function getFormObject( $user, IContextSource $context, $formClass = 'PreferencesForm', array $remove = array() ) {
 +      static function getFormObject(
 +              $user,
 +              IContextSource $context,
 +              $formClass = 'PreferencesForm',
 +              array $remove = array()
 +      ) {
                $formDescriptor = Preferences::getPreferences( $user, $context );
                if ( count( $remove ) ) {
                        $removeKeys = array_flip( $remove );
        static function getTimezoneOptions( IContextSource $context ) {
                $opt = array();
  
 -              global $wgLocalTZoffset, $wgLocaltimezone;
 -              // Check that $wgLocalTZoffset is the same as $wgLocaltimezone
 -              if ( $wgLocalTZoffset == date( 'Z' ) / 60 ) {
 -                      $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $wgLocaltimezone )->text();
 +              global $wgLocalTZoffset;
 +              $timestamp = MWTimestamp::getLocalInstance();
 +              // Check that $wgLocalTZoffset is the same as the local time zone offset
 +              if ( $wgLocalTZoffset == $timestamp->format( 'Z' ) / 60 ) {
 +                      $server_tz_msg = $context->msg(
 +                              'timezoneuseserverdefault',
 +                              $timestamp->getTimezone()->getName()
 +                      )->text();
                } else {
 -                      $tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
 +                      $tzstring = sprintf(
 +                              '%+03d:%02d',
 +                              floor( $wgLocalTZoffset / 60 ),
 +                              abs( $wgLocalTZoffset ) % 60
 +                      );
                        $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
                }
                $opt[$server_tz_msg] = "System|$wgLocalTZoffset";
         *
         * @param $formData
         * @param $form PreferencesForm
 -       * @param $entryPoint string
         * @return bool|Status|string
         */
 -      static function tryFormSubmit( $formData, $form, $entryPoint = 'internal' ) {
 +      static function tryFormSubmit( $formData, $form ) {
                global $wgHiddenPrefs, $wgAuth;
  
                $user = $form->getModifiedUser();
                $result = true;
  
 +              if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
 +                      return Status::newFatal( 'mypreferencesprotected' );
 +              }
 +
                // Filter input
                foreach ( array_keys( $formData ) as $name ) {
                        if ( isset( self::$saveFilters[$name] ) ) {
                        }
                }
  
 -              // Stuff that shouldn't be saved as a preference.
 -              $saveBlacklist = array(
 -                      'realname',
 -                      'emailaddress',
 -              );
 -
                // Fortunately, the realname field is MUCH simpler
 -              if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
 +              // (not really "private", but still shouldn't be edited without permission)
 +              if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->isAllowed( 'editmyprivateinfo' ) && array_key_exists( 'realname', $formData ) ) {
                        $realName = $formData['realname'];
                        $user->setRealName( $realName );
                }
  
 -              foreach ( $saveBlacklist as $b ) {
 -                      unset( $formData[$b] );
 -              }
 +              if ( $user->isAllowed( 'editmyoptions' ) ) {
 +                      foreach ( self::$saveBlacklist as $b ) {
 +                              unset( $formData[$b] );
 +                      }
  
 -              # If users have saved a value for a preference which has subsequently been disabled
 -              # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
 -              # is subsequently re-enabled
 -              # TODO: maintenance script to actually delete these
 -              foreach ( $wgHiddenPrefs as $pref ) {
 -                      # If the user has not set a non-default value here, the default will be returned
 -                      # and subsequently discarded
 -                      $formData[$pref] = $user->getOption( $pref, null, true );
 -              }
 +                      # If users have saved a value for a preference which has subsequently been disabled
 +                      # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
 +                      # is subsequently re-enabled
 +                      foreach ( $wgHiddenPrefs as $pref ) {
 +                              # If the user has not set a non-default value here, the default will be returned
 +                              # and subsequently discarded
 +                              $formData[$pref] = $user->getOption( $pref, null, true );
 +                      }
  
 -              // Keep old preferences from interfering due to back-compat code, etc.
 -              $user->resetOptions( 'unused', $form->getContext() );
 +                      // Keep old preferences from interfering due to back-compat code, etc.
 +                      $user->resetOptions( 'unused', $form->getContext() );
  
 -              foreach ( $formData as $key => $value ) {
 -                      $user->setOption( $key, $value );
 -              }
 +                      foreach ( $formData as $key => $value ) {
 +                              $user->setOption( $key, $value );
 +                      }
  
 -              $user->saveSettings();
 +                      wfRunHooks( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) );
 +                      $user->saveSettings();
 +              }
  
                $wgAuth->updateExternalDB( $user );
  
         * @return Status
         */
        public static function tryUISubmit( $formData, $form ) {
 -              $res = self::tryFormSubmit( $formData, $form, 'ui' );
 +              $res = self::tryFormSubmit( $formData, $form );
  
                if ( $res ) {
                        $urlOptions = array( 'success' => 1 );
        /**
         * Try to set a user's email address.
         * This does *not* try to validate the address.
 -       * Caller is responsible for checking $wgAuth.
 +       * Caller is responsible for checking $wgAuth and 'editmyprivateinfo'
 +       * right.
         *
         * @deprecated in 1.20; use User::setEmailWithConfirmation() instead.
         * @param $user User
@@@ -1563,19 -1549,13 +1562,19 @@@ class PreferencesForm extends HTMLForm 
         * @return String
         */
        function getButtons() {
 +              if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
 +                      return '';
 +              }
 +
                $html = parent::getButtons();
  
 -              $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
 +              if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
 +                      $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
  
 -              $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() );
 +                      $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() );
  
 -              $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
 +                      $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
 +              }
  
                return $html;
        }
diff --combined includes/Skin.php
@@@ -82,7 -82,6 +82,7 @@@ abstract class Skin extends ContextSour
        static function getSkinNameMessages() {
                $messages = array();
                foreach ( self::getSkinNames() as $skinKey => $skinName ) {
 +                      // Messages: skinname-cologneblue, skinname-monobook, skinname-modern, skinname-vector
                        $messages[] = "skinname-$skinKey";
                }
                return $messages;
@@@ -92,9 -91,9 +92,9 @@@
         * Fetch the list of user-selectable skins in regards to $wgSkipSkins.
         * Useful for Special:Preferences and other places where you
         * only want to show skins users _can_ use.
-        * @return array of strings
+        * @return string[]
         */
-       public static function getUsableSkins() {
+       public static function getAllowedSkins() {
                global $wgSkipSkins;
  
                $allowedSkins = self::getSkinNames();
                return $allowedSkins;
        }
  
+       /**
+        * @deprecated since 1.22, use getAllowedSkins
+        * @return string[]
+        */
+       public static function getUsableSkins() {
+               wfDeprecated( __METHOD__, '1.22' );
+               return self::getAllowedSkins();
+       }
        /**
         * Normalize a skin preference value to a form that can be loaded.
         * If a skin can't be found, it will fall back to the configured
                return $skin;
        }
  
 -      /** @return string skin name */
 +      /**
 +       * @return string skin name
 +       */
        public function getSkinName() {
                return $this->skinname;
        }
                wfProfileOut( __METHOD__ );
        }
  
 +      /**
 +       * Defines the ResourceLoader modules that should be added to the skin
 +       * It is recommended that skins wishing to override call parent::getDefaultModules()
 +       * and substitute out any modules they wish to change by using a key to look them up
 +       * @return Array of modules with helper keys for easy overriding
 +       */
 +      public function getDefaultModules() {
 +              global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
 +                      $wgAjaxWatch, $wgEnableAPI, $wgEnableWriteAPI;
 +
 +              $out = $this->getOutput();
 +              $user = $out->getUser();
 +              $modules = array(
 +                      // modules that enhance the page content in some way
 +                      'content' => array(
 +                              'mediawiki.page.ready',
 +                      ),
 +                      // modules that exist for legacy reasons
 +                      'legacy' => array(),
 +                      // modules relating to search functionality
 +                      'search' => array(),
 +                      // modules relating to functionality relating to watching an article
 +                      'watch' => array(),
 +                      // modules which relate to the current users preferences
 +                      'user' => array(),
 +              );
 +              if ( $wgIncludeLegacyJavaScript ) {
 +                      $modules['legacy'][] = 'mediawiki.legacy.wikibits';
 +              }
 +
 +              if ( $wgPreloadJavaScriptMwUtil ) {
 +                      $modules['legacy'][] = 'mediawiki.util';
 +              }
 +
 +              // Add various resources if required
 +              if ( $wgUseAjax ) {
 +                      $modules['legacy'][] = 'mediawiki.legacy.ajax';
 +
 +                      if ( $wgEnableAPI ) {
 +                              if ( $wgEnableWriteAPI && $wgAjaxWatch && $user->isLoggedIn()
 +                                      && $user->isAllowed( 'writeapi' )
 +                              ) {
 +                                      $modules['watch'][] = 'mediawiki.page.watch.ajax';
 +                              }
 +
 +                              $modules['search'][] = 'mediawiki.searchSuggest';
 +                      }
 +              }
 +
 +              if ( $user->getBoolOption( 'editsectiononrightclick' ) ) {
 +                      $modules['user'][] = 'mediawiki.action.view.rightClickEdit';
 +              }
 +
 +              // Crazy edit-on-double-click stuff
 +              if ( $out->isArticle() && $user->getOption( 'editondblclick' ) ) {
 +                      $modules['user'][] = 'mediawiki.action.view.dblClickEdit';
 +              }
 +              return $modules;
 +      }
 +
        /**
         * Preload the existence of three commonly-requested pages in a single query
         */
                return "$numeric $type $name";
        }
  
 +      /*
 +       * Return values for <html> element
 +       * @return array of associative name-to-value elements for <html> element
 +       */
 +      public function getHtmlElementAttributes() {
 +              $lang = $this->getLanguage();
 +              return array(
 +                      'lang' => $lang->getHtmlCode(),
 +                      'dir' => $lang->getDir(),
 +                      'class' => 'client-nojs',
 +              );
 +      }
 +
        /**
         * This will be called by OutputPage::headElement when it is creating the
         * "<body>" tag, skins can override it if they have a need to add in any
        }
  
        /**
 -       * @param $desc
 -       * @param $page
 -       * @return string
 +       * Returns an HTML link for use in the footer
 +       * @param string $desc i18n message key for the link text
 +       * @param string $page i18n message key for the page to link to
 +       * @return string HTML anchor
         */
        public function footerLink( $desc, $page ) {
                // if the link description has been set to "-" in the default language,
                wfProfileOut( __METHOD__ );
                return $bar;
        }
 +
        /**
         * Add content from a sidebar system message
         * Currently only used for MediaWiki:Sidebar (but may be used by Extensions)
        }
  
        /**
 -       * Should we load mediawiki.legacy.wikiprintable?  Skins that have their own
 -       * print stylesheet should override this and return false.  (This is an
 -       * ugly hack to get Monobook to play nicely with OutputPage::headElement().)
 +       * This function previously controlled whether the 'mediawiki.legacy.wikiprintable' module
 +       * should be loaded by OutputPage. That module no longer exists and the return value of this
 +       * method is ignored.
 +       *
 +       * If your skin doesn't provide its own print styles, the 'mediawiki.legacy.commonPrint' module
 +       * can be used instead (SkinTemplate-based skins do it automatically).
         *
 +       * @deprecated since 1.22
         * @return bool
         */
        public function commonPrintStylesheet() {
 -              return true;
 +              wfDeprecated( __METHOD__, '1.22' );
 +              return false;
        }
  
        /**
  
                if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
                        $uTalkTitle = $user->getTalkPage();
 -
 -                      if ( !$uTalkTitle->equals( $out->getTitle() ) ) {
 -                              $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
 -                              $nofAuthors = 0;
 -                              if ( $lastSeenRev !== null ) {
 -                                      $plural = true; // Default if we have a last seen revision: if unknown, use plural
 -                                      $latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
 -                                      if ( $latestRev !== null ) {
 -                                              // Singular if only 1 unseen revision, plural if several unseen revisions.
 -                                              $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
 -                                              $nofAuthors = $uTalkTitle->countAuthorsBetween(
 -                                                      $lastSeenRev, $latestRev, 10, 'include_new' );
 -                                      }
 -                              } else {
 -                                      // Singular if no revision -> diff link will show latest change only in any case
 -                                      $plural = false;
 +                      $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
 +                      $nofAuthors = 0;
 +                      if ( $lastSeenRev !== null ) {
 +                              $plural = true; // Default if we have a last seen revision: if unknown, use plural
 +                              $latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
 +                              if ( $latestRev !== null ) {
 +                                      // Singular if only 1 unseen revision, plural if several unseen revisions.
 +                                      $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
 +                                      $nofAuthors = $uTalkTitle->countAuthorsBetween(
 +                                              $lastSeenRev, $latestRev, 10, 'include_new' );
                                }
 -                              $plural = $plural ? 2 : 1;
 -                              // 2 signifies "more than one revision". We don't know how many, and even if we did,
 -                              // the number of revisions or authors is not necessarily the same as the number of
 -                              // "messages".
 -                              $newMessagesLink = Linker::linkKnown(
 -                                      $uTalkTitle,
 -                                      $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
 -                                      array(),
 -                                      array( 'redirect' => 'no' )
 -                              );
 +                      } else {
 +                              // Singular if no revision -> diff link will show latest change only in any case
 +                              $plural = false;
 +                      }
 +                      $plural = $plural ? 999 : 1;
 +                      // 999 signifies "more than one revision". We don't know how many, and even if we did,
 +                      // the number of revisions or authors is not necessarily the same as the number of
 +                      // "messages".
 +                      $newMessagesLink = Linker::linkKnown(
 +                              $uTalkTitle,
 +                              $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
 +                              array(),
 +                              array( 'redirect' => 'no' )
 +                      );
  
 -                              $newMessagesDiffLink = Linker::linkKnown(
 -                                      $uTalkTitle,
 -                                      $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
 -                                      array(),
 -                                      $lastSeenRev !== null
 -                                              ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
 -                                              : array( 'diff' => 'cur' )
 -                              );
 +                      $newMessagesDiffLink = Linker::linkKnown(
 +                              $uTalkTitle,
 +                              $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
 +                              array(),
 +                              $lastSeenRev !== null
 +                                      ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
 +                                      : array( 'diff' => 'cur' )
 +                      );
  
 -                              if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
 -                                      $newMessagesAlert = $this->msg(
 -                                              'youhavenewmessagesfromusers',
 -                                              $newMessagesLink,
 -                                              $newMessagesDiffLink
 -                                      )->numParams( $nofAuthors );
 -                              } else {
 -                                      // $nofAuthors === 11 signifies "11 or more" ("more than 10")
 -                                      $newMessagesAlert = $this->msg(
 -                                              $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
 -                                              $newMessagesLink,
 -                                              $newMessagesDiffLink
 -                                      );
 -                              }
 -                              $newMessagesAlert = $newMessagesAlert->text();
 -                              # Disable Squid cache
 -                              $out->setSquidMaxage( 0 );
 +                      if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
 +                              $newMessagesAlert = $this->msg(
 +                                      'youhavenewmessagesfromusers',
 +                                      $newMessagesLink,
 +                                      $newMessagesDiffLink
 +                              )->numParams( $nofAuthors, $plural );
 +                      } else {
 +                              // $nofAuthors === 11 signifies "11 or more" ("more than 10")
 +                              $newMessagesAlert = $this->msg(
 +                                      $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
 +                                      $newMessagesLink,
 +                                      $newMessagesDiffLink
 +                              )->numParams( $plural );
                        }
 +                      $newMessagesAlert = $newMessagesAlert->text();
 +                      # Disable Squid cache
 +                      $out->setSquidMaxage( 0 );
                } elseif ( count( $newtalks ) ) {
                        $sep = $this->msg( 'newtalkseparator' )->escaped();
                        $msgs = array();
         * Create a section edit link.  This supersedes editSectionLink() and
         * editSectionLinkForOther().
         *
 -       * @param $nt      Title  The title being linked to (may not be the same as
 -       *   $wgTitle, if the section is included from a template)
 +       * @param $nt Title  The title being linked to (may not be the same as
 +       *   the current page, if the section is included from a template)
         * @param string $section The designation of the section being pointed to,
         *   to be included in the link, like "&section=$section"
         * @param string $tooltip The tooltip to use for the link: will be escaped
         *   and wrapped in the 'editsectionhint' message
 -       * @param $lang    string Language code
 -       * @return         string HTML to use for edit link
 +       * @param $lang string Language code
 +       * @return string HTML to use for edit link
         */
        public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
                // HTML generated here should probably have userlangattributes
                        array( 'noclasses', 'known' )
                );
  
 -              # Run the old hook.  This takes up half of the function . . . hopefully
 -              # we can rid of it someday.
 -              $attribs = '';
 -              if ( $tooltip ) {
 -                      $attribs = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
 -                              ->inLanguage( $lang )->escaped();
 -                      $attribs = " title=\"$attribs\"";
 -              }
 -              $result = null;
 -              wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result, $lang ) );
 -              if ( !is_null( $result ) ) {
 -                      # For reverse compatibility, add the brackets *after* the hook is
 -                      # run, and even add them to hook-provided text.  (This is the main
 -                      # reason that the EditSectionLink hook is deprecated in favor of
 -                      # DoEditSectionLink: it can't change the brackets or the span.)
 -                      $result = wfMessage( 'editsection-brackets' )->rawParams( $result )
 -                              ->inLanguage( $lang )->escaped();
 -                      return "<span class=\"mw-editsection\">$result</span>";
 -              }
 -
 -              # Add the brackets and the span, and *then* run the nice new hook, with
 -              # clean and non-redundant arguments.
 -              $result = wfMessage( 'editsection-brackets' )->rawParams( $link )
 -                      ->inLanguage( $lang )->escaped();
 -              $result = "<span class=\"mw-editsection\">$result</span>";
 +              # Add the brackets and the span and run the hook.
 +              $result = '<span class="mw-editsection">'
 +                      . '<span class="mw-editsection-bracket">[</span>'
 +                      . $link
 +                      . '<span class="mw-editsection-bracket">]</span>'
 +                      . '</span>';
  
                wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
                return $result;
@@@ -78,9 -78,6 +78,9 @@@ class ApiQuerySiteinfo extends ApiQuery
                                case 'rightsinfo':
                                        $fit = $this->appendRightsInfo( $p );
                                        break;
 +                              case 'restrictions':
 +                                      $fit = $this->appendRestrictions( $p );
 +                                      break;
                                case 'languages':
                                        $fit = $this->appendLanguages( $p );
                                        break;
                                case 'protocols':
                                        $fit = $this->appendProtocols( $p );
                                        break;
 +                              case 'defaultoptions':
 +                                      $fit = $this->appendDefaultOptions( $p );
 +                                      break;
                                default:
                                        ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
                        }
                                // Abuse siprop as a query-continue parameter
                                // and set it to all unprocessed props
                                $this->setContinueEnumParameter( 'prop', implode( '|',
 -                                              array_diff( $params['prop'], $done ) ) );
 +                                      array_diff( $params['prop'], $done ) ) );
                                break;
                        }
                        $done[] = $p;
        }
  
        protected function appendGeneralInfo( $property ) {
 -              global $wgContLang,
 -                      $wgDisableLangConversion,
 -                      $wgDisableTitleConversion;
 +              global $wgContLang, $wgDisableLangConversion, $wgDisableTitleConversion;
  
                $data = array();
                $mainPage = Title::newMainPage();
                $data['mainpage'] = $mainPage->getPrefixedText();
                $data['base'] = wfExpandUrl( $mainPage->getFullURL(), PROTO_CURRENT );
                $data['sitename'] = $GLOBALS['wgSitename'];
 +
 +              // wgLogo can either be a relative or an absolute path
 +              // make sure we always return an absolute path
 +              $data['logo'] = wfExpandUrl( $GLOBALS['wgLogo'], PROTO_RELATIVE );
 +
                $data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
                $data['phpversion'] = phpversion();
                $data['phpsapi'] = PHP_SAPI;
                $data['dbtype'] = $GLOBALS['wgDBtype'];
                $data['dbversion'] = $this->getDB()->getServerVersion();
  
 +              $allowFrom = array( '' );
 +              $allowException = true;
 +              if ( !$GLOBALS['wgAllowExternalImages'] ) {
 +                      if ( $GLOBALS['wgEnableImageWhitelist'] ) {
 +                              $data['imagewhitelistenabled'] = '';
 +                      }
 +                      $allowFrom = $GLOBALS['wgAllowExternalImagesFrom'];
 +                      $allowException = !empty( $allowFrom );
 +              }
 +              if ( $allowException ) {
 +                      $data['externalimages'] = (array)$allowFrom;
 +                      $this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' );
 +              }
 +
                if ( !$wgDisableLangConversion ) {
                        $data['langconversion'] = '';
                }
                }
  
                if ( $wgContLang->linkPrefixExtension() ) {
 -                      $data['linkprefix'] = wfMessage( 'linkprefix' )->inContentLanguage()->text();
 +                      $linkPrefixCharset = $wgContLang->linkPrefixCharset();
 +                      $data['linkprefixcharset'] = $linkPrefixCharset;
 +                      // For backwards compatability
 +                      $data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
                } else {
 +                      $data['linkprefixcharset'] = '';
                        $data['linkprefix'] = '';
                }
  
                // 'case-insensitive' option is reserved for future
                $data['case'] = $GLOBALS['wgCapitalLinks'] ? 'first-letter' : 'case-sensitive';
  
 -              if ( isset( $GLOBALS['wgRightsCode'] ) ) {
 -                      $data['rightscode'] = $GLOBALS['wgRightsCode'];
 -              }
 -              $data['rights'] = $GLOBALS['wgRightsText'];
                $data['lang'] = $GLOBALS['wgLanguageCode'];
  
                $fallbacks = array();
                if ( $wgContLang->hasVariants() ) {
                        $variants = array();
                        foreach ( $wgContLang->getVariants() as $code ) {
 -                              $variants[] = array( 'code' => $code );
 +                              $variants[] = array(
 +                                      'code' => $code,
 +                                      'name' => $wgContLang->getVariantname( $code ),
 +                              );
                        }
                        $data['variants'] = $variants;
                        $this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
  
                $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
  
 +              $data['thumblimits'] = $GLOBALS['wgThumbLimits'];
 +              $this->getResult()->setIndexedTagName( $data['thumblimits'], 'limit' );
 +              $data['imagelimits'] = array();
 +              $this->getResult()->setIndexedTagName( $data['imagelimits'], 'limit' );
 +              foreach ( $GLOBALS['wgImageLimits'] as $k => $limit ) {
 +                      $data['imagelimits'][$k] = array( 'width' => $limit[0], 'height' => $limit[1] );
 +              }
 +
 +              if ( !empty( $GLOBALS['wgFavicon'] ) ) {
 +                      // wgFavicon can either be a relative or an absolute path
 +                      // make sure we always return an absolute path
 +                      $data['favicon'] = wfExpandUrl( $GLOBALS['wgFavicon'], PROTO_RELATIVE );
 +              }
 +
                wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
  
                return $this->getResult()->addValue( 'query', $property, $data );
                }
  
                $this->getResult()->setIndexedTagName( $data, 'ns' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                        $data[] = $item;
                }
  
 +              sort( $data );
 +
                $this->getResult()->setIndexedTagName( $data, 'ns' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                        }
                }
                $this->getResult()->setIndexedTagName( $data, 'specialpage' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                        $data[] = $arr;
                }
                $this->getResult()->setIndexedTagName( $data, 'magicword' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                        if ( $row['iw_local'] == '1' ) {
                                $val['local'] = '';
                        }
 -                      // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
 +                      if ( $row['iw_trans'] == '1' ) {
 +                              $val['trans'] = '';
 +                      }
                        if ( isset( $langNames[$prefix] ) ) {
                                $val['language'] = $langNames[$prefix];
                        }
                }
  
                $this->getResult()->setIndexedTagName( $data, 'iw' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                $lb = wfGetLB();
                if ( $includeAll ) {
                        if ( !$wgShowHostnames ) {
 -                              $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
 +                              $this->dieUsage(
 +                                      'Cannot view all servers info unless $wgShowHostnames is true',
 +                                      'includeAllDenied'
 +                              );
                        }
  
                        $lags = $lb->getLagTimes();
  
                $result = $this->getResult();
                $result->setIndexedTagName( $data, 'db' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                $data['activeusers'] = intval( SiteStats::activeUsers() );
                $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
                $data['jobs'] = intval( SiteStats::jobs() );
 +
 +              wfRunHooks( 'APIQuerySiteInfoStatisticsInfo', array( &$data ) );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
        protected function appendUserGroups( $property, $numberInGroup ) {
 -              global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
 +              global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups;
 +              global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
  
                $data = array();
                $result = $this->getResult();
  
                                if ( $group == 'user' ) {
                                        $arr['number'] = SiteStats::users();
 -
                                // '*' and autopromote groups have no size
                                } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
                                        $arr['number'] = SiteStats::numberInGroup( $group );
                }
  
                $result->setIndexedTagName( $data, 'group' );
 +
                return $result->addValue( 'query', $property, $data );
        }
  
                global $wgFileExtensions;
  
                $data = array();
 -              foreach ( $wgFileExtensions as $ext ) {
 +              foreach ( array_unique( $wgFileExtensions ) as $ext ) {
                        $data[] = array( 'ext' => $ext );
                }
                $this->getResult()->setIndexedTagName( $data, 'fe' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                                        $ret['url'] = $ext['url'];
                                }
                                if ( isset( $ext['version'] ) ) {
 -                                              $ret['version'] = $ext['version'];
 +                                      $ret['version'] = $ext['version'];
                                } elseif ( isset( $ext['svn-revision'] ) &&
                                        preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
 -                                              $ext['svn-revision'], $m ) )
 -                              {
 -                                              $ret['version'] = 'r' . $m[1];
 +                                              $ext['svn-revision'], $m )
 +                              ) {
 +                                      $ret['version'] = 'r' . $m[1];
 +                              }
 +                              if ( isset( $ext['path'] ) ) {
 +                                      $extensionPath = dirname( $ext['path'] );
 +                                      $gitInfo = new GitInfo( $extensionPath );
 +                                      $vcsVersion = $gitInfo->getHeadSHA1();
 +                                      if ( $vcsVersion !== false ) {
 +                                              $ret['vcs-system'] = 'git';
 +                                              $ret['vcs-version'] = $vcsVersion;
 +                                              $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
 +                                              $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $gitInfo->getHeadCommitDate() );
 +                                      } else {
 +                                              $svnInfo = SpecialVersion::getSvnInfo( $extensionPath );
 +                                              if ( $svnInfo !== false ) {
 +                                                      $ret['vcs-system'] = 'svn';
 +                                                      $ret['vcs-version'] = $svnInfo['checkout-rev'];
 +                                                      $ret['vcs-url'] = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
 +                                              }
 +                                      }
 +
 +                                      if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
 +                                              $ret['license-name'] = isset( $ext['license-name'] ) ? $ext['license-name'] : '';
 +                                              $ret['license'] = SpecialPage::getTitleFor(
 +                                                      'Version',
 +                                                      "License/{$ext['name']}"
 +                                              )->getLinkURL();
 +                                      }
 +
 +                                      if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
 +                                              $ret['credits'] = SpecialPage::getTitleFor(
 +                                                      'Version',
 +                                                      "Credits/{$ext['name']}"
 +                                              )->getLinkURL();
 +                                      }
                                }
                                $data[] = $ret;
                        }
                }
  
                $this->getResult()->setIndexedTagName( $data, 'ext' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
 +      protected function appendRestrictions( $property ) {
 +              global $wgRestrictionTypes, $wgRestrictionLevels,
 +                      $wgCascadingRestrictionLevels, $wgSemiprotectedRestrictionLevels;
 +
 +              $data = array(
 +                      'types' => $wgRestrictionTypes,
 +                      'levels' => $wgRestrictionLevels,
 +                      'cascadinglevels' => $wgCascadingRestrictionLevels,
 +                      'semiprotectedlevels' => $wgSemiprotectedRestrictionLevels,
 +              );
 +
 +              $this->getResult()->setIndexedTagName( $data['types'], 'type' );
 +              $this->getResult()->setIndexedTagName( $data['levels'], 'level' );
 +              $this->getResult()->setIndexedTagName( $data['cascadinglevels'], 'level' );
 +              $this->getResult()->setIndexedTagName( $data['semiprotectedlevels'], 'level' );
 +
 +              return $this->getResult()->addValue( 'query', $property, $data );
 +      }
 +
        public function appendLanguages( $property ) {
                $params = $this->extractRequestParams();
                $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
                        $data[] = $lang;
                }
                $this->getResult()->setIndexedTagName( $data, 'lang' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
        public function appendSkins( $property ) {
                $data = array();
-               $usable = Skin::getUsableSkins();
+               $allowed = Skin::getAllowedSkins();
                $default = Skin::normalizeKey( 'default' );
                foreach ( Skin::getSkinNames() as $name => $displayName ) {
                        $skin = array( 'code' => $name );
                        ApiResult::setContent( $skin, $displayName );
-                       if ( !isset( $usable[$name] ) ) {
+                       if ( !isset( $allowed[$name] ) ) {
                                $skin['unusable'] = '';
                        }
                        if ( $name === $default ) {
                        $data[] = $skin;
                }
                $this->getResult()->setIndexedTagName( $data, 'skin' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                $wgParser->firstCallInit();
                $tags = array_map( array( $this, 'formatParserTags' ), $wgParser->getTags() );
                $this->getResult()->setIndexedTagName( $tags, 't' );
 +
                return $this->getResult()->addValue( 'query', $property, $tags );
        }
  
                $wgParser->firstCallInit();
                $hooks = $wgParser->getFunctionHooks();
                $this->getResult()->setIndexedTagName( $hooks, 'h' );
 +
                return $this->getResult()->addValue( 'query', $property, $hooks );
        }
  
        public function appendVariables( $property ) {
                $variables = MagicWord::getVariableIDs();
                $this->getResult()->setIndexedTagName( $variables, 'v' );
 +
                return $this->getResult()->addValue( 'query', $property, $variables );
        }
  
                // Make a copy of the global so we don't try to set the _element key of it - bug 45130
                $protocols = array_values( $wgUrlProtocols );
                $this->getResult()->setIndexedTagName( $protocols, 'p' );
 +
                return $this->getResult()->addValue( 'query', $property, $protocols );
        }
  
 +      public function appendDefaultOptions( $property ) {
 +              return $this->getResult()->addValue( 'query', $property, User::getDefaultOptions() );
 +      }
 +
        private function formatParserTags( $item ) {
                return "<{$item}>";
        }
                }
  
                $this->getResult()->setIndexedTagName( $data, 'hook' );
 +
                return $this->getResult()->addValue( 'query', $property, $data );
        }
  
                                        'extensions',
                                        'fileextensions',
                                        'rightsinfo',
 +                                      'restrictions',
                                        'languages',
                                        'skins',
                                        'extensiontags',
                                        'showhooks',
                                        'variables',
                                        'protocols',
 +                                      'defaultoptions',
                                )
                        ),
                        'filteriw' => array(
  
        public function getParamDescription() {
                $p = $this->getModulePrefix();
 +
                return array(
                        'prop' => array(
                                'Which sysinfo properties to get:',
                                ' specialpagealiases    - List of special page aliases',
                                ' magicwords            - List of magic words and their aliases',
                                ' statistics            - Returns site statistics',
 -                              " interwikimap          - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))",
 +                              ' interwikimap          - Returns interwiki map ' .
 +                                      "(optionally filtered, (optionally localised by using {$p}inlanguagecode))",
                                ' dbrepllag             - Returns database server with the highest replication lag',
                                ' usergroups            - Returns user groups and the associated permissions',
                                ' extensions            - Returns extensions installed on the wiki',
                                ' fileextensions        - Returns list of file extensions allowed to be uploaded',
                                ' rightsinfo            - Returns wiki rights (license) information if available',
 -                              " languages             - Returns a list of languages MediaWiki supports (optionally localised by using {$p}inlanguagecode)",
 +                              ' restrictions          - Returns information on available restriction (protection) types',
 +                              ' languages             - Returns a list of languages MediaWiki supports' .
 +                                      "(optionally localised by using {$p}inlanguagecode)",
                                ' skins                 - Returns a list of all enabled skins',
                                ' extensiontags         - Returns a list of parser extension tags',
                                ' functionhooks         - Returns a list of parser function hooks',
                                ' showhooks             - Returns a list of all subscribed hooks (contents of $wgHooks)',
                                ' variables             - Returns a list of variable IDs',
                                ' protocols             - Returns a list of protocols that are allowed in external links.',
 +                              ' defaultoptions        - Returns the default values for user preferences.',
                        ),
                        'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
                        'showalldb' => 'List all database servers, not just the one lagging the most',
                        'numberingroup' => 'Lists the number of users in user groups',
 -                      'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)',
 +                      'inlanguagecode' => 'Language code for localised language names ' .
 +                              '(best effort, use CLDR extension)',
                );
        }
  
        public function getDescription() {
 -              return 'Return general information about the site';
 +              return 'Return general information about the site.';
        }
  
        public function getPossibleErrors() {
 -              return array_merge( parent::getPossibleErrors(), array(
 -                      array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ),
 -              ) );
 +              return array_merge( parent::getPossibleErrors(), array( array(
 +                      'code' => 'includeAllDenied',
 +                      'info' => 'Cannot view all servers info unless $wgShowHostnames is true'
 +              ), ) );
        }
  
        public function getExamples() {