diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2014-12-27 15:41:37 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2014-12-31 11:43:28 +0100 |
commit | c1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch) | |
tree | 2b38796e738dd74cb42ecd9bfd151803108386bc /includes | |
parent | b88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff) |
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes')
1020 files changed, 100653 insertions, 77151 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index c9ca1283..9bc92be9 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -48,14 +48,21 @@ class AjaxDispatcher { private $args; /** + * @var Config + */ + private $config; + + /** * Load up our object with user supplied data */ - function __construct() { + function __construct( Config $config ) { wfProfileIn( __METHOD__ ); + $this->config = $config; + $this->mode = ""; - if ( ! empty( $_GET["rs"] ) ) { + if ( !empty( $_GET["rs"] ) ) { $this->mode = "get"; } @@ -66,7 +73,7 @@ class AjaxDispatcher { switch ( $this->mode ) { case 'get': $this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : ''; - if ( ! empty( $_GET["rsargs"] ) ) { + if ( !empty( $_GET["rsargs"] ) ) { $this->args = $_GET["rsargs"]; } else { $this->args = array(); @@ -74,7 +81,7 @@ class AjaxDispatcher { break; case 'post': $this->func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : ''; - if ( ! empty( $_POST["rsargs"] ) ) { + if ( !empty( $_POST["rsargs"] ) ) { $this->args = $_POST["rsargs"]; } else { $this->args = array(); @@ -95,17 +102,17 @@ class AjaxDispatcher { * BEWARE! Data are passed as they have been supplied by the user, * they should be carefully handled in the function processing the * request. + * + * @param User $user */ - function performAction() { - global $wgAjaxExportList, $wgUser; - + function performAction( User $user ) { if ( empty( $this->mode ) ) { return; } wfProfileIn( __METHOD__ ); - if ( ! in_array( $this->func_name, $wgAjaxExportList ) ) { + if ( !in_array( $this->func_name, $this->config->get( 'AjaxExportList' ) ) ) { wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" ); wfHttpError( @@ -113,7 +120,7 @@ class AjaxDispatcher { 'Bad Request', "unknown function " . $this->func_name ); - } elseif ( !User::isEveryoneAllowed( 'read' ) && !$wgUser->isAllowed( 'read' ) ) { + } elseif ( !User::isEveryoneAllowed( 'read' ) && !$user->isAllowed( 'read' ) ) { wfHttpError( 403, 'Forbidden', diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index d5536529..8e9f490f 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -28,7 +28,6 @@ * @ingroup Ajax */ class AjaxResponse { - /** * Number of seconds to get the response cached by a proxy * @var int $mCacheDuration @@ -49,7 +48,7 @@ class AjaxResponse { /** * Date for the HTTP header Last-modified - * @var string|false $mLastModified + * @var string|bool $mLastModified */ private $mLastModified; @@ -72,11 +71,18 @@ class AjaxResponse { private $mText; /** - * @param $text string|null + * @var Config */ - function __construct( $text = null ) { + private $mConfig; + + /** + * @param string|null $text + * @param Config|null $config + */ + function __construct( $text = null, Config $config = null ) { $this->mCacheDuration = null; $this->mVary = null; + $this->mConfig = $config ?: ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); $this->mDisabled = false; $this->mText = ''; @@ -91,7 +97,7 @@ class AjaxResponse { /** * Set the number of seconds to get the response cached by a proxy - * @param $duration int + * @param int $duration */ function setCacheDuration( $duration ) { $this->mCacheDuration = $duration; @@ -99,7 +105,7 @@ class AjaxResponse { /** * Set the HTTP Vary header - * @param $vary string + * @param string $vary */ function setVary( $vary ) { $this->mVary = $vary; @@ -107,7 +113,7 @@ class AjaxResponse { /** * Set the HTTP response code - * @param $code string + * @param string $code */ function setResponseCode( $code ) { $this->mResponseCode = $code; @@ -115,7 +121,7 @@ class AjaxResponse { /** * Set the HTTP header Content-Type - * @param $type string + * @param string $type */ function setContentType( $type ) { $this->mContentType = $type; @@ -130,10 +136,10 @@ class AjaxResponse { /** * Add content to the response - * @param $text string + * @param string $text */ function addText( $text ) { - if ( ! $this->mDisabled && $text ) { + if ( !$this->mDisabled && $text ) { $this->mText .= $text; } } @@ -142,7 +148,7 @@ class AjaxResponse { * Output text */ function printText() { - if ( ! $this->mDisabled ) { + if ( !$this->mDisabled ) { print $this->mText; } } @@ -151,8 +157,6 @@ class AjaxResponse { * Construct the header and output it */ function sendHeaders() { - global $wgUseSquid, $wgUseESI; - if ( $this->mResponseCode ) { $n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode ); header( "Status: " . $this->mResponseCode, true, (int)$n ); @@ -171,12 +175,12 @@ class AjaxResponse { # and tell the client to always check with the squid. Otherwise, # tell the client to use a cached copy, without a way to purge it. - if ( $wgUseSquid ) { + if ( $this->mConfig->get( 'UseSquid' ) ) { # Expect explicit purge of the proxy cache, but require end user agents # to revalidate against the proxy on each visit. # Surrogate-Control controls our Squid, Cache-Control downstream caches - if ( $wgUseESI ) { + if ( $this->mConfig->get( 'UseESI' ) ) { header( 'Surrogate-Control: max-age=' . $this->mCacheDuration . ', content="ESI/1.0"' ); header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); } else { @@ -184,10 +188,10 @@ class AjaxResponse { } } else { - # Let the client do the caching. Cache is not purged. header ( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT" ); - header ( "Cache-Control: s-maxage={$this->mCacheDuration},public,max-age={$this->mCacheDuration}" ); + header ( "Cache-Control: s-maxage={$this->mCacheDuration}," . + "public,max-age={$this->mCacheDuration}" ); } } else { @@ -207,7 +211,7 @@ class AjaxResponse { * possible. If successful, the AjaxResponse is disabled so that * any future call to AjaxResponse::printText() have no effect. * - * @param $timestamp string + * @param string $timestamp * @return bool Returns true if the response code was set to 304 Not Modified. */ function checkLastModified( $timestamp ) { @@ -215,17 +219,12 @@ class AjaxResponse { $fname = 'AjaxResponse::checkLastModified'; if ( !$timestamp || $timestamp == '19700101000000' ) { - wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n" ); + wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n", 'log' ); return false; } if ( !$wgCachePages ) { - wfDebug( "$fname: CACHE DISABLED\n", false ); - return false; - } - - if ( $wgUser->getOption( 'nocache' ) ) { - wfDebug( "$fname: USER DISABLED CACHE\n", false ); + wfDebug( "$fname: CACHE DISABLED\n", 'log' ); return false; } @@ -239,32 +238,37 @@ class AjaxResponse { $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] ); $modsinceTime = strtotime( $modsince ); $ismodsince = wfTimestamp( TS_MW, $modsinceTime ? $modsinceTime : 1 ); - wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false ); - wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false ); + wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", 'log' ); + wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", 'log' ); - if ( ( $ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) { + if ( ( $ismodsince >= $timestamp ) + && $wgUser->validateCache( $ismodsince ) && + $ismodsince >= $wgCacheEpoch + ) { ini_set( 'zlib.output_compression', 0 ); $this->setResponseCode( "304 Not Modified" ); $this->disable(); $this->mLastModified = $lastmod; - wfDebug( "$fname: CACHED client: $ismodsince ; user: {$wgUser->getTouched()} ; page: $timestamp ; site $wgCacheEpoch\n", false ); + wfDebug( "$fname: CACHED client: $ismodsince ; user: {$wgUser->getTouched()} ; " . + "page: $timestamp ; site $wgCacheEpoch\n", 'log' ); return true; } else { - wfDebug( "$fname: READY client: $ismodsince ; user: {$wgUser->getTouched()} ; page: $timestamp ; site $wgCacheEpoch\n", false ); + wfDebug( "$fname: READY client: $ismodsince ; user: {$wgUser->getTouched()} ; " . + "page: $timestamp ; site $wgCacheEpoch\n", 'log' ); $this->mLastModified = $lastmod; } } else { - wfDebug( "$fname: client did not send If-Modified-Since header\n", false ); + wfDebug( "$fname: client did not send If-Modified-Since header\n", 'log' ); $this->mLastModified = $lastmod; } return false; } /** - * @param $mckey string - * @param $touched int + * @param string $mckey + * @param int $touched * @return bool */ function loadFromMemcached( $mckey, $touched ) { @@ -291,8 +295,8 @@ class AjaxResponse { } /** - * @param $mckey string - * @param $expiry int + * @param string $mckey + * @param int $expiry * @return bool */ function storeInMemcached( $mckey, $expiry = 86400 ) { diff --git a/includes/ArrayUtils.php b/includes/ArrayUtils.php deleted file mode 100644 index 985271f7..00000000 --- a/includes/ArrayUtils.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php - -class ArrayUtils { - /** - * Sort the given array in a pseudo-random order which depends only on the - * given key and each element value. This is typically used for load - * balancing between servers each with a local cache. - * - * Keys are preserved. The input array is modified in place. - * - * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small - * strings, md5() is only 10% slower than hash('joaat',...) etc., - * since the function call overhead dominates. So there's not much - * justification for breaking compatibility with installations - * compiled with ./configure --disable-hash. - * - * @param $array The array to sort - * @param $key The string key - * @param $separator A separator used to delimit the array elements and the - * key. This can be chosen to provide backwards compatibility with - * various consistent hash implementations that existed before this - * function was introduced. - */ - public static function consistentHashSort( &$array, $key, $separator = "\000" ) { - $hashes = array(); - foreach ( $array as $elt ) { - $hashes[$elt] = md5( $elt . $separator . $key ); - } - uasort( $array, function ( $a, $b ) use ( $hashes ) { - return strcmp( $hashes[$a], $hashes[$b] ); - } ); - } - - /** - * Given an array of non-normalised probabilities, this function will select - * an element and return the appropriate key - * - * @param $weights array - * - * @return bool|int|string - */ - public static function pickRandom( $weights ) { - if ( !is_array( $weights ) || count( $weights ) == 0 ) { - return false; - } - - $sum = array_sum( $weights ); - if ( $sum == 0 ) { - # No loads on any of them - # In previous versions, this triggered an unweighted random selection, - # but this feature has been removed as of April 2006 to allow for strict - # separation of query groups. - return false; - } - $max = mt_getrandmax(); - $rand = mt_rand( 0, $max ) / $max * $sum; - - $sum = 0; - foreach ( $weights as $i => $w ) { - $sum += $w; - # Do not return keys if they have 0 weight. - # Note that the "all 0 weight" case is handed above - if ( $w > 0 && $sum >= $rand ) { - break; - } - } - return $i; - } -} diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php index 84cf3d5e..45ad4d1b 100644 --- a/includes/AuthPlugin.php +++ b/includes/AuthPlugin.php @@ -3,7 +3,7 @@ * Authentication plugin interface * * Copyright © 2004 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +34,6 @@ * someone logs in who can be authenticated externally. */ class AuthPlugin { - /** * @var string */ @@ -46,7 +45,7 @@ class AuthPlugin { * you might need to munge it (for instance, for lowercase initial * letters). * - * @param string $username username. + * @param string $username Username. * @return bool */ public function userExists( $username ) { @@ -60,8 +59,8 @@ class AuthPlugin { * you might need to munge it (for instance, for lowercase initial * letters). * - * @param string $username username. - * @param string $password user password. + * @param string $username Username. + * @param string $password User password. * @return bool */ public function authenticate( $username, $password ) { @@ -72,7 +71,7 @@ class AuthPlugin { /** * Modify options in the login template. * - * @param $template UserLoginTemplate object. + * @param UserLoginTemplate $template * @param string $type 'signup' or 'login'. Added in 1.16. */ public function modifyUITemplate( &$template, &$type ) { @@ -83,7 +82,7 @@ class AuthPlugin { /** * Set the domain this plugin is supposed to use when authenticating. * - * @param string $domain authentication domain. + * @param string $domain Authentication domain. */ public function setDomain( $domain ) { $this->domain = $domain; @@ -105,7 +104,7 @@ class AuthPlugin { /** * Check to see if the specific domain is a valid domain. * - * @param string $domain authentication domain. + * @param string $domain Authentication domain. * @return bool */ public function validDomain( $domain ) { @@ -121,7 +120,7 @@ class AuthPlugin { * The User object is passed by reference so it can be modified; don't * forget the & on your function declaration. * - * @param $user User object + * @param User $user * @return bool */ public function updateUser( &$user ) { @@ -140,7 +139,7 @@ class AuthPlugin { * * This is just a question, and shouldn't perform any actions. * - * @return Boolean + * @return bool */ public function autoCreate() { return false; @@ -151,9 +150,9 @@ class AuthPlugin { * and use the same keys. 'Realname' 'Emailaddress' and 'Nickname' * all reference this. * - * @param $prop string + * @param string $prop * - * @return Boolean + * @return bool */ public function allowPropChange( $prop = '' ) { if ( $prop == 'realname' && is_callable( array( $this, 'allowRealNameChange' ) ) ) { @@ -193,8 +192,8 @@ class AuthPlugin { * * Return true if successful. * - * @param $user User object. - * @param string $password password. + * @param User $user + * @param string $password Password. * @return bool */ public function setPassword( $user, $password ) { @@ -205,8 +204,8 @@ class AuthPlugin { * Update user information in the external authentication database. * Return true if successful. * - * @param $user User object. - * @return Boolean + * @param User $user + * @return bool */ public function updateExternalDB( $user ) { return true; @@ -216,10 +215,10 @@ class AuthPlugin { * Update user groups in the external authentication database. * Return true if successful. * - * @param $user User object. - * @param $addgroups Groups to add. - * @param $delgroups Groups to remove. - * @return Boolean + * @param User $user + * @param array $addgroups Groups to add. + * @param array $delgroups Groups to remove. + * @return bool */ public function updateExternalDBGroups( $user, $addgroups, $delgroups = array() ) { return true; @@ -228,7 +227,7 @@ class AuthPlugin { /** * Check to see if external accounts can be created. * Return true if external accounts can be created. - * @return Boolean + * @return bool */ public function canCreateAccounts() { return false; @@ -238,11 +237,11 @@ class AuthPlugin { * Add a user to the external authentication database. * Return true if successful. * - * @param $user User: only the name should be assumed valid at this point - * @param $password String - * @param $email String - * @param $realname String - * @return Boolean + * @param User $user Only the name should be assumed valid at this point + * @param string $password + * @param string $email + * @param string $realname + * @return bool */ public function addUser( $user, $password, $email = '', $realname = '' ) { return true; @@ -254,7 +253,7 @@ class AuthPlugin { * * This is just a question, and shouldn't perform any actions. * - * @return Boolean + * @return bool */ public function strict() { return false; @@ -264,8 +263,8 @@ class AuthPlugin { * Check if a user should authenticate locally if the global authentication fails. * If either this or strict() returns true, local authentication is not used. * - * @param string $username username. - * @return Boolean + * @param string $username Username. + * @return bool */ public function strictUserAuth( $username ) { return false; @@ -279,8 +278,8 @@ class AuthPlugin { * The User object is passed by reference so it can be modified; don't * forget the & on your function declaration. * - * @param $user User object. - * @param $autocreate Boolean: True if user is being autocreated on login + * @param User $user + * @param bool $autocreate True if user is being autocreated on login */ public function initUser( &$user, $autocreate = false ) { # Override this to do something. @@ -289,7 +288,7 @@ class AuthPlugin { /** * If you want to munge the case of an account name before the final * check, now is your chance. - * @param $username string + * @param string $username * @return string */ public function getCanonicalName( $username ) { @@ -299,7 +298,7 @@ class AuthPlugin { /** * Get an instance of a User object * - * @param $user User + * @param User $user * * @return AuthPluginUser */ diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 0706fe3f..6b0daa14 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -29,49 +29,32 @@ global $wgAutoloadLocalClasses; $wgAutoloadLocalClasses = array( # Includes - 'Action' => 'includes/Action.php', 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', 'AjaxResponse' => 'includes/AjaxResponse.php', - 'AlphabeticPager' => 'includes/Pager.php', - 'ArrayUtils' => 'includes/ArrayUtils.php', - 'Article' => 'includes/Article.php', 'AtomFeed' => 'includes/Feed.php', 'AuthPlugin' => 'includes/AuthPlugin.php', 'AuthPluginUser' => 'includes/AuthPlugin.php', 'Autopromote' => 'includes/Autopromote.php', - 'BadTitleError' => 'includes/Exception.php', - 'BaseTemplate' => 'includes/SkinTemplate.php', 'Block' => 'includes/Block.php', + 'BloomCache' => 'includes/cache/bloom/BloomCache.php', + 'BloomCacheRedis' => 'includes/cache/bloom/BloomCacheRedis.php', + 'BloomFilterTitleHasLogs' => 'includes/cache/bloom/BloomFilters.php', 'CacheHelper' => 'includes/CacheHelper.php', 'Category' => 'includes/Category.php', - 'Categoryfinder' => 'includes/Categoryfinder.php', - 'CategoryPage' => 'includes/CategoryPage.php', + 'CategoryFinder' => 'includes/CategoryFinder.php', 'CategoryViewer' => 'includes/CategoryViewer.php', - 'CdbFunctions' => 'includes/Cdb_PHP.php', - 'CdbReader' => 'includes/Cdb.php', - 'CdbReader_DBA' => 'includes/Cdb.php', - 'CdbReader_PHP' => 'includes/Cdb_PHP.php', - 'CdbWriter' => 'includes/Cdb.php', - 'CdbWriter_DBA' => 'includes/Cdb.php', - 'CdbWriter_PHP' => 'includes/Cdb_PHP.php', - 'ChangesFeed' => 'includes/ChangesFeed.php', 'ChangeTags' => 'includes/ChangeTags.php', 'ChannelFeed' => 'includes/Feed.php', 'Collation' => 'includes/Collation.php', + 'CollationCkb' => 'includes/Collation.php', + 'CollationEt' => 'includes/Collation.php', 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', - 'ConfEditor' => 'includes/ConfEditor.php', - 'ConfEditorParseError' => 'includes/ConfEditor.php', - 'ConfEditorToken' => 'includes/ConfEditor.php', 'Cookie' => 'includes/Cookie.php', 'CookieJar' => 'includes/Cookie.php', 'CurlHttpRequest' => 'includes/HttpFunctions.php', - 'DeferrableUpdate' => 'includes/DeferredUpdates.php', - 'DeferredUpdates' => 'includes/DeferredUpdates.php', - 'MWCallableUpdate' => 'includes/CallableUpdate.php', 'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php', 'DerivativeRequest' => 'includes/WebRequest.php', 'DiffHistoryBlob' => 'includes/HistoryBlob.php', - 'DoubleReplacer' => 'includes/StringUtils.php', 'DummyLinker' => 'includes/Linker.php', 'Dump7ZipOutput' => 'includes/Export.php', 'DumpBZip2Output' => 'includes/Export.php', @@ -85,126 +68,83 @@ $wgAutoloadLocalClasses = array( 'DumpOutput' => 'includes/Export.php', 'DumpPipeOutput' => 'includes/Export.php', 'EditPage' => 'includes/EditPage.php', - 'EmailNotification' => 'includes/UserMailer.php', - 'ErrorPageError' => 'includes/Exception.php', - 'ExplodeIterator' => 'includes/StringUtils.php', - 'FakeTitle' => 'includes/FakeTitle.php', + 'EmptyBloomCache' => 'includes/cache/bloom/BloomCache.php', 'Fallback' => 'includes/Fallback.php', - 'FatalError' => 'includes/Exception.php', 'FauxRequest' => 'includes/WebRequest.php', 'FauxResponse' => 'includes/WebResponse.php', 'FeedItem' => 'includes/Feed.php', 'FeedUtils' => 'includes/FeedUtils.php', 'FileDeleteForm' => 'includes/FileDeleteForm.php', 'ForkController' => 'includes/ForkController.php', - 'FormlessAction' => 'includes/Action.php', - 'FormAction' => 'includes/Action.php', 'FormOptions' => 'includes/FormOptions.php', - 'FormSpecialPage' => 'includes/SpecialPage.php', 'GitInfo' => 'includes/GitInfo.php', - 'HashRing' => 'includes/HashRing.php', - 'HashtableReplacer' => 'includes/StringUtils.php', 'HistoryBlob' => 'includes/HistoryBlob.php', 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', 'HistoryBlobStub' => 'includes/HistoryBlob.php', 'Hooks' => 'includes/Hooks.php', 'Html' => 'includes/Html.php', 'HtmlFormatter' => 'includes/HtmlFormatter.php', - 'HTMLApiField' => 'includes/HTMLForm.php', - 'HTMLButtonField' => 'includes/HTMLForm.php', - 'HTMLCheckField' => 'includes/HTMLForm.php', - 'HTMLCheckMatrix' => 'includes/HTMLForm.php', - 'HTMLEditTools' => 'includes/HTMLForm.php', - 'HTMLFloatField' => 'includes/HTMLForm.php', - 'HTMLForm' => 'includes/HTMLForm.php', - 'HTMLFormField' => 'includes/HTMLForm.php', - 'HTMLFormFieldRequiredOptionsException' => 'includes/HTMLForm.php', - 'HTMLHiddenField' => 'includes/HTMLForm.php', - 'HTMLInfoField' => 'includes/HTMLForm.php', - 'HTMLIntField' => 'includes/HTMLForm.php', - 'HTMLNestedFilterable' => 'includes/HTMLForm.php', - 'HTMLMultiSelectField' => 'includes/HTMLForm.php', - 'HTMLRadioField' => 'includes/HTMLForm.php', - 'HTMLSelectAndOtherField' => 'includes/HTMLForm.php', - 'HTMLSelectField' => 'includes/HTMLForm.php', - 'HTMLSelectOrOtherField' => 'includes/HTMLForm.php', - 'HTMLSubmitField' => 'includes/HTMLForm.php', - 'HTMLTextAreaField' => 'includes/HTMLForm.php', - 'HTMLTextField' => 'includes/HTMLForm.php', + 'HTMLApiField' => 'includes/htmlform/HTMLApiField.php', + 'HTMLAutoCompleteSelectField' => 'includes/htmlform/HTMLAutoCompleteSelectField.php', + 'HTMLButtonField' => 'includes/htmlform/HTMLButtonField.php', + 'HTMLCheckField' => 'includes/htmlform/HTMLCheckField.php', + 'HTMLCheckMatrix' => 'includes/htmlform/HTMLCheckMatrix.php', + 'HTMLFormFieldCloner' => 'includes/htmlform/HTMLFormFieldCloner.php', + 'HTMLEditTools' => 'includes/htmlform/HTMLEditTools.php', + 'HTMLFloatField' => 'includes/htmlform/HTMLFloatField.php', + 'HTMLForm' => 'includes/htmlform/HTMLForm.php', + 'HTMLFormField' => 'includes/htmlform/HTMLFormField.php', + 'HTMLFormFieldRequiredOptionsException' => + 'includes/htmlform/HTMLFormFieldRequiredOptionsException.php', + 'HTMLHiddenField' => 'includes/htmlform/HTMLHiddenField.php', + 'HTMLInfoField' => 'includes/htmlform/HTMLInfoField.php', + 'HTMLIntField' => 'includes/htmlform/HTMLIntField.php', + 'HTMLNestedFilterable' => 'includes/htmlform/HTMLNestedFilterable.php', + 'HTMLMultiSelectField' => 'includes/htmlform/HTMLMultiSelectField.php', + 'HTMLRadioField' => 'includes/htmlform/HTMLRadioField.php', + 'HTMLSelectAndOtherField' => 'includes/htmlform/HTMLSelectAndOtherField.php', + 'HTMLSelectField' => 'includes/htmlform/HTMLSelectField.php', + 'HTMLSelectLimitField' => 'includes/htmlform/HTMLSelectLimitField.php', + 'HTMLSelectOrOtherField' => 'includes/htmlform/HTMLSelectOrOtherField.php', + 'HTMLSubmitField' => 'includes/htmlform/HTMLSubmitField.php', + 'HTMLTextAreaField' => 'includes/htmlform/HTMLTextAreaField.php', + 'HTMLTextField' => 'includes/htmlform/HTMLTextField.php', 'Http' => 'includes/HttpFunctions.php', - 'HttpError' => 'includes/Exception.php', - 'ICacheHelper' => 'includes/CacheHelper.php', 'IcuCollation' => 'includes/Collation.php', 'IdentityCollation' => 'includes/Collation.php', - 'ImageHistoryList' => 'includes/ImagePage.php', - 'ImageHistoryPseudoPager' => 'includes/ImagePage.php', - 'ImagePage' => 'includes/ImagePage.php', - 'ImageQueryPage' => 'includes/ImageQueryPage.php', 'ImportStreamSource' => 'includes/Import.php', 'ImportStringSource' => 'includes/Import.php', - 'IncludableSpecialPage' => 'includes/SpecialPage.php', - 'IndexPager' => 'includes/Pager.php', 'Interwiki' => 'includes/interwiki/Interwiki.php', - 'IP' => 'includes/IP.php', - 'LCStore' => 'includes/cache/LocalisationCache.php', - 'LCStore_Accel' => 'includes/cache/LocalisationCache.php', - 'LCStore_CDB' => 'includes/cache/LocalisationCache.php', - 'LCStore_DB' => 'includes/cache/LocalisationCache.php', - 'LCStore_Null' => 'includes/cache/LocalisationCache.php', 'License' => 'includes/Licenses.php', 'Licenses' => 'includes/Licenses.php', 'Linker' => 'includes/Linker.php', 'LinkFilter' => 'includes/LinkFilter.php', - 'LinksUpdate' => 'includes/LinksUpdate.php', - 'LinksDeletionUpdate' => 'includes/LinksUpdate.php', - 'LocalisationCache' => 'includes/cache/LocalisationCache.php', - 'LocalisationCache_BulkLoad' => 'includes/cache/LocalisationCache.php', 'MagicWord' => 'includes/MagicWord.php', 'MagicWordArray' => 'includes/MagicWord.php', - 'MailAddress' => 'includes/UserMailer.php', - 'MappedIterator' => 'includes/MappedIterator.php', - 'MediaWiki' => 'includes/Wiki.php', - 'MediaWiki_I18N' => 'includes/SkinTemplate.php', + 'MediaWiki' => 'includes/MediaWiki.php', + 'MediaWikiVersionFetcher' => 'includes/MediaWikiVersionFetcher.php', 'Message' => 'includes/Message.php', 'MessageBlobStore' => 'includes/MessageBlobStore.php', 'MimeMagic' => 'includes/MimeMagic.php', - 'MWCryptRand' => 'includes/MWCryptRand.php', - 'MWException' => 'includes/Exception.php', - 'MWExceptionHandler' => 'includes/Exception.php', - 'MWFunction' => 'includes/MWFunction.php', + 'MovePage' => 'includes/MovePage.php', 'MWHookException' => 'includes/Hooks.php', 'MWHttpRequest' => 'includes/HttpFunctions.php', - 'MWInit' => 'includes/Init.php', - 'MWNamespace' => 'includes/Namespace.php', + 'MWNamespace' => 'includes/MWNamespace.php', 'OutputPage' => 'includes/OutputPage.php', - 'Page' => 'includes/WikiPage.php', - 'PageQueryPage' => 'includes/PageQueryPage.php', - 'Pager' => 'includes/Pager.php', - 'PasswordError' => 'includes/User.php', 'PathRouter' => 'includes/PathRouter.php', 'PathRouterPatternReplacer' => 'includes/PathRouter.php', - 'PermissionsError' => 'includes/Exception.php', 'PhpHttpRequest' => 'includes/HttpFunctions.php', - 'PoolCounter' => 'includes/PoolCounter.php', - 'PoolCounter_Stub' => 'includes/PoolCounter.php', - 'PoolCounterWork' => 'includes/PoolCounter.php', - 'PoolCounterWorkViaCallback' => 'includes/PoolCounter.php', - 'PoolWorkArticleView' => 'includes/WikiPage.php', + 'PoolCounter' => 'includes/poolcounter/PoolCounter.php', + 'PoolCounter_Stub' => 'includes/poolcounter/PoolCounter.php', + 'PoolCounterRedis' => 'includes/poolcounter/PoolCounterRedis.php', + 'PoolCounterWork' => 'includes/poolcounter/PoolCounterWork.php', + 'PoolCounterWorkViaCallback' => 'includes/poolcounter/PoolCounterWorkViaCallback.php', + 'PoolWorkArticleView' => 'includes/poolcounter/PoolWorkArticleView.php', 'Preferences' => 'includes/Preferences.php', 'PreferencesForm' => 'includes/Preferences.php', 'PrefixSearch' => 'includes/PrefixSearch.php', 'ProtectionForm' => 'includes/ProtectionForm.php', - 'QueryPage' => 'includes/QueryPage.php', - 'QuickTemplate' => 'includes/SkinTemplate.php', 'RawMessage' => 'includes/Message.php', - 'RdfMetaData' => 'includes/Metadata.php', - 'ReadOnlyError' => 'includes/Exception.php', - 'RedirectSpecialArticle' => 'includes/SpecialPage.php', - 'RedirectSpecialPage' => 'includes/SpecialPage.php', - 'RegexlikeReplacer' => 'includes/StringUtils.php', - 'ReplacementArray' => 'includes/StringUtils.php', - 'Replacer' => 'includes/StringUtils.php', - 'ReverseChronologicalPager' => 'includes/Pager.php', 'RevisionItem' => 'includes/RevisionList.php', 'RevisionItemBase' => 'includes/RevisionList.php', 'RevisionListBase' => 'includes/RevisionList.php', @@ -212,125 +152,72 @@ $wgAutoloadLocalClasses = array( 'RevisionList' => 'includes/RevisionList.php', 'RSSFeed' => 'includes/Feed.php', 'Sanitizer' => 'includes/Sanitizer.php', - 'DataUpdate' => 'includes/DataUpdate.php', - 'SqlDataUpdate' => 'includes/SqlDataUpdate.php', - 'ScopedCallback' => 'includes/ScopedCallback.php', - 'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php', 'SiteConfiguration' => 'includes/SiteConfiguration.php', 'SiteStats' => 'includes/SiteStats.php', 'SiteStatsInit' => 'includes/SiteStats.php', - 'SiteStatsUpdate' => 'includes/SiteStats.php', - 'Skin' => 'includes/Skin.php', - 'SkinTemplate' => 'includes/SkinTemplate.php', - 'SpecialCreateAccount' => 'includes/SpecialPage.php', - 'SpecialListAdmins' => 'includes/SpecialPage.php', - 'SpecialListBots' => 'includes/SpecialPage.php', - 'SpecialMycontributions' => 'includes/SpecialPage.php', - 'SpecialMypage' => 'includes/SpecialPage.php', - 'SpecialMytalk' => 'includes/SpecialPage.php', - 'SpecialMyuploads' => 'includes/SpecialPage.php', - 'SpecialAllMyUploads' => 'includes/SpecialPage.php', - 'SpecialPage' => 'includes/SpecialPage.php', - 'SpecialPageFactory' => 'includes/SpecialPageFactory.php', - 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php', 'SquidPurgeClient' => 'includes/SquidPurgeClient.php', 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php', 'StatCounter' => 'includes/StatCounter.php', 'Status' => 'includes/Status.php', 'StreamFile' => 'includes/StreamFile.php', - 'StringUtils' => 'includes/StringUtils.php', - 'StubContLang' => 'includes/StubObject.php', + 'StringPrefixSearch' => 'includes/PrefixSearch.php', 'StubObject' => 'includes/StubObject.php', 'StubUserLang' => 'includes/StubObject.php', - 'TablePager' => 'includes/Pager.php', - 'MWTimestamp' => 'includes/Timestamp.php', - 'TimestampException' => 'includes/Timestamp.php', + 'MWTimestamp' => 'includes/MWTimestamp.php', + 'TimestampException' => 'includes/TimestampException.php', 'Title' => 'includes/Title.php', 'TitleArray' => 'includes/TitleArray.php', - 'TitleArrayFromResult' => 'includes/TitleArray.php', - 'ThrottledError' => 'includes/Exception.php', - 'UIDGenerator' => 'includes/UIDGenerator.php', - 'UnlistedSpecialPage' => 'includes/SpecialPage.php', + 'TitleArrayFromResult' => 'includes/TitleArrayFromResult.php', + 'TitlePrefixSearch' => 'includes/PrefixSearch.php', 'UploadSourceAdapter' => 'includes/Import.php', 'UppercaseCollation' => 'includes/Collation.php', 'User' => 'includes/User.php', 'UserArray' => 'includes/UserArray.php', - 'UserArrayFromResult' => 'includes/UserArray.php', - 'UserBlockedError' => 'includes/Exception.php', - 'UserNotLoggedIn' => 'includes/Exception.php', - 'UserCache' => 'includes/cache/UserCache.php', - 'UserMailer' => 'includes/UserMailer.php', + 'UserArrayFromResult' => 'includes/UserArrayFromResult.php', 'UserRightsProxy' => 'includes/UserRightsProxy.php', - 'ViewCountUpdate' => 'includes/ViewCountUpdate.php', - 'WantedQueryPage' => 'includes/QueryPage.php', 'WatchedItem' => 'includes/WatchedItem.php', 'WebRequest' => 'includes/WebRequest.php', 'WebRequestUpload' => 'includes/WebRequest.php', 'WebResponse' => 'includes/WebResponse.php', - 'WikiCategoryPage' => 'includes/WikiCategoryPage.php', - 'WikiError' => 'includes/WikiError.php', - 'WikiErrorMsg' => 'includes/WikiError.php', 'WikiExporter' => 'includes/Export.php', - 'WikiFilePage' => 'includes/WikiFilePage.php', 'WikiImporter' => 'includes/Import.php', - 'WikiPage' => 'includes/WikiPage.php', 'WikiRevision' => 'includes/Import.php', 'WikiMap' => 'includes/WikiMap.php', 'WikiReference' => 'includes/WikiMap.php', - 'WikiXmlError' => 'includes/WikiError.php', 'Xml' => 'includes/Xml.php', 'XmlDumpWriter' => 'includes/Export.php', 'XmlJsCode' => 'includes/Xml.php', - 'XMLReader2' => 'includes/Import.php', 'XmlSelect' => 'includes/Xml.php', - 'XmlTypeCheck' => 'includes/XmlTypeCheck.php', - 'ZhClient' => 'includes/ZhClient.php', - 'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php', - 'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php', - - # content handler - 'AbstractContent' => 'includes/content/AbstractContent.php', - 'ContentHandler' => 'includes/content/ContentHandler.php', - 'Content' => 'includes/content/Content.php', - 'CssContentHandler' => 'includes/content/CssContentHandler.php', - 'CssContent' => 'includes/content/CssContent.php', - 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php', - 'JavaScriptContent' => 'includes/content/JavaScriptContent.php', - 'MessageContent' => 'includes/content/MessageContent.php', - 'MWContentSerializationException' => 'includes/content/ContentHandler.php', - 'TextContentHandler' => 'includes/content/TextContentHandler.php', - 'TextContent' => 'includes/content/TextContent.php', - 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', - 'WikitextContent' => 'includes/content/WikitextContent.php', # includes/actions + 'Action' => 'includes/actions/Action.php', 'CachedAction' => 'includes/actions/CachedAction.php', 'CreditsAction' => 'includes/actions/CreditsAction.php', 'DeleteAction' => 'includes/actions/DeleteAction.php', 'EditAction' => 'includes/actions/EditAction.php', + 'FormlessAction' => 'includes/actions/FormlessAction.php', + 'FormAction' => 'includes/actions/FormAction.php', 'HistoryAction' => 'includes/actions/HistoryAction.php', - 'HistoryPage' => 'includes/actions/HistoryAction.php', 'HistoryPager' => 'includes/actions/HistoryAction.php', 'InfoAction' => 'includes/actions/InfoAction.php', 'MarkpatrolledAction' => 'includes/actions/MarkpatrolledAction.php', 'ProtectAction' => 'includes/actions/ProtectAction.php', 'PurgeAction' => 'includes/actions/PurgeAction.php', 'RawAction' => 'includes/actions/RawAction.php', - 'RawPage' => 'includes/actions/RawAction.php', 'RenderAction' => 'includes/actions/RenderAction.php', 'RevertAction' => 'includes/actions/RevertAction.php', - 'RevertFileAction' => 'includes/actions/RevertAction.php', 'RevisiondeleteAction' => 'includes/actions/RevisiondeleteAction.php', 'RollbackAction' => 'includes/actions/RollbackAction.php', - 'SubmitAction' => 'includes/actions/EditAction.php', - 'UnprotectAction' => 'includes/actions/ProtectAction.php', - 'UnwatchAction' => 'includes/actions/WatchAction.php', + 'SubmitAction' => 'includes/actions/SubmitAction.php', + 'UnprotectAction' => 'includes/actions/UnprotectAction.php', + 'UnwatchAction' => 'includes/actions/UnwatchAction.php', 'ViewAction' => 'includes/actions/ViewAction.php', 'WatchAction' => 'includes/actions/WatchAction.php', # includes/api 'ApiBase' => 'includes/api/ApiBase.php', 'ApiBlock' => 'includes/api/ApiBlock.php', + 'ApiClearHasMsg' => 'includes/api/ApiClearHasMsg.php', 'ApiComparePages' => 'includes/api/ApiComparePages.php', 'ApiCreateAccount' => 'includes/api/ApiCreateAccount.php', 'ApiDelete' => 'includes/api/ApiDelete.php', @@ -339,12 +226,13 @@ $wgAutoloadLocalClasses = array( 'ApiEmailUser' => 'includes/api/ApiEmailUser.php', 'ApiExpandTemplates' => 'includes/api/ApiExpandTemplates.php', 'ApiFeedContributions' => 'includes/api/ApiFeedContributions.php', + 'ApiFeedRecentChanges' => 'includes/api/ApiFeedRecentChanges.php', 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php', 'ApiFileRevert' => 'includes/api/ApiFileRevert.php', 'ApiFormatBase' => 'includes/api/ApiFormatBase.php', 'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php', 'ApiFormatDump' => 'includes/api/ApiFormatDump.php', - 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', + 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatFeedWrapper.php', 'ApiFormatJson' => 'includes/api/ApiFormatJson.php', 'ApiFormatNone' => 'includes/api/ApiFormatNone.php', 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php', @@ -379,12 +267,14 @@ $wgAutoloadLocalClasses = array( 'ApiQueryAllPages' => 'includes/api/ApiQueryAllPages.php', 'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php', 'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php', + 'ApiQueryBacklinksprop' => 'includes/api/ApiQueryBacklinksprop.php', 'ApiQueryBase' => 'includes/api/ApiQueryBase.php', 'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php', 'ApiQueryCategories' => 'includes/api/ApiQueryCategories.php', 'ApiQueryCategoryInfo' => 'includes/api/ApiQueryCategoryInfo.php', 'ApiQueryCategoryMembers' => 'includes/api/ApiQueryCategoryMembers.php', 'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php', + 'ApiQueryContributors' => 'includes/api/ApiQueryContributors.php', 'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php', 'ApiQueryDisabled' => 'includes/api/ApiQueryDisabled.php', 'ApiQueryDuplicateFiles' => 'includes/api/ApiQueryDuplicateFiles.php', @@ -405,6 +295,7 @@ $wgAutoloadLocalClasses = array( 'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php', 'ApiQueryPagesWithProp' => 'includes/api/ApiQueryPagesWithProp.php', 'ApiQueryPagePropNames' => 'includes/api/ApiQueryPagePropNames.php', + 'ApiQueryPrefixSearch' => 'includes/api/ApiQueryPrefixSearch.php', 'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php', 'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php', 'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php', @@ -415,11 +306,13 @@ $wgAutoloadLocalClasses = array( 'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php', 'ApiQueryStashImageInfo' => 'includes/api/ApiQueryStashImageInfo.php', 'ApiQueryTags' => 'includes/api/ApiQueryTags.php', + 'ApiQueryTokens' => 'includes/api/ApiQueryTokens.php', 'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php', 'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php', 'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php', 'ApiQueryWatchlistRaw' => 'includes/api/ApiQueryWatchlistRaw.php', 'ApiResult' => 'includes/api/ApiResult.php', + 'ApiRevisionDelete' => 'includes/api/ApiRevisionDelete.php', 'ApiRollback' => 'includes/api/ApiRollback.php', 'ApiRsd' => 'includes/api/ApiRsd.php', 'ApiSetNotificationTimestamp' => 'includes/api/ApiSetNotificationTimestamp.php', @@ -434,35 +327,73 @@ $wgAutoloadLocalClasses = array( # includes/cache 'BacklinkCache' => 'includes/cache/BacklinkCache.php', 'CacheDependency' => 'includes/cache/CacheDependency.php', + 'CacheHelper' => 'includes/cache/CacheHelper.php', 'ConstantDependency' => 'includes/cache/CacheDependency.php', 'DependencyWrapper' => 'includes/cache/CacheDependency.php', 'FileCacheBase' => 'includes/cache/FileCacheBase.php', 'FileDependency' => 'includes/cache/CacheDependency.php', 'GenderCache' => 'includes/cache/GenderCache.php', 'GlobalDependency' => 'includes/cache/CacheDependency.php', - 'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php', 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php', + 'ICacheHelper' => 'includes/cache/CacheHelper.php', + 'LCStore' => 'includes/cache/LocalisationCache.php', + 'LCStoreCDB' => 'includes/cache/LocalisationCache.php', + 'LCStoreDB' => 'includes/cache/LocalisationCache.php', + 'LCStoreNull' => 'includes/cache/LocalisationCache.php', 'LinkBatch' => 'includes/cache/LinkBatch.php', 'LinkCache' => 'includes/cache/LinkCache.php', + 'LocalisationCache' => 'includes/cache/LocalisationCache.php', + 'LocalisationCacheBulkLoad' => 'includes/cache/LocalisationCache.php', + 'MapCacheLRU' => 'includes/cache/MapCacheLRU.php', 'MessageCache' => 'includes/cache/MessageCache.php', 'ObjectFileCache' => 'includes/cache/ObjectFileCache.php', - 'ProcessCacheLRU' => 'includes/cache/ProcessCacheLRU.php', 'ResourceFileCache' => 'includes/cache/ResourceFileCache.php', - 'SquidUpdate' => 'includes/cache/SquidUpdate.php', - 'TitleDependency' => 'includes/cache/CacheDependency.php', - 'TitleListDependency' => 'includes/cache/CacheDependency.php', + 'UserCache' => 'includes/cache/UserCache.php', # includes/changes + 'ChangesFeed' => 'includes/changes/ChangesFeed.php', 'ChangesList' => 'includes/changes/ChangesList.php', 'EnhancedChangesList' => 'includes/changes/EnhancedChangesList.php', 'OldChangesList' => 'includes/changes/OldChangesList.php', 'RCCacheEntry' => 'includes/changes/RCCacheEntry.php', + 'RCCacheEntryFactory' => 'includes/changes/RCCacheEntryFactory.php', 'RecentChange' => 'includes/changes/RecentChange.php', # includes/clientpool 'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php', 'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php', + # includes/composer + 'ComposerPackageModifier' => 'includes/composer/ComposerPackageModifier.php', + 'ComposerVersionNormalizer' => 'includes/composer/ComposerVersionNormalizer.php', + + # includes/config + 'Config' => 'includes/config/Config.php', + 'ConfigException' => 'includes/config/ConfigException.php', + 'ConfigFactory' => 'includes/config/ConfigFactory.php', + 'GlobalVarConfig' => 'includes/config/GlobalVarConfig.php', + 'HashConfig' => 'includes/config/HashConfig.php', + 'MultiConfig' => 'includes/config/MultiConfig.php', + 'MutableConfig' => 'includes/config/MutableConfig.php', + + # includes/content + 'AbstractContent' => 'includes/content/AbstractContent.php', + 'CodeContentHandler' => 'includes/content/CodeContentHandler.php', + 'Content' => 'includes/content/Content.php', + 'ContentHandler' => 'includes/content/ContentHandler.php', + 'CssContent' => 'includes/content/CssContent.php', + 'CssContentHandler' => 'includes/content/CssContentHandler.php', + 'JavaScriptContent' => 'includes/content/JavaScriptContent.php', + 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php', + 'JsonContent' => 'includes/content/JsonContent.php', + 'JsonContentHandler' => 'includes/content/JsonContentHandler.php', + 'MessageContent' => 'includes/content/MessageContent.php', + 'MWContentSerializationException' => 'includes/content/ContentHandler.php', + 'TextContent' => 'includes/content/TextContent.php', + 'TextContentHandler' => 'includes/content/TextContentHandler.php', + 'WikitextContent' => 'includes/content/WikitextContent.php', + 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', + # includes/context 'ContextSource' => 'includes/context/ContextSource.php', 'DerivativeContext' => 'includes/context/DerivativeContext.php', @@ -491,6 +422,7 @@ $wgAutoloadLocalClasses = array( 'DBConnectionError' => 'includes/db/DatabaseError.php', 'DBConnRef' => 'includes/db/LoadBalancer.php', 'DBError' => 'includes/db/DatabaseError.php', + 'DBExpectedError' => 'includes/db/DatabaseError.php', 'DBObject' => 'includes/db/DatabaseUtility.php', 'IDatabase' => 'includes/db/Database.php', 'IORMRow' => 'includes/db/IORMRow.php', @@ -501,18 +433,19 @@ $wgAutoloadLocalClasses = array( 'FakeResultWrapper' => 'includes/db/DatabaseUtility.php', 'Field' => 'includes/db/DatabaseUtility.php', 'LBFactory' => 'includes/db/LBFactory.php', - 'LBFactory_Fake' => 'includes/db/LBFactory.php', - 'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php', - 'LBFactory_Simple' => 'includes/db/LBFactory.php', - 'LBFactory_Single' => 'includes/db/LBFactory_Single.php', + 'LBFactoryFake' => 'includes/db/LBFactory.php', + 'LBFactoryMulti' => 'includes/db/LBFactoryMulti.php', + 'LBFactorySimple' => 'includes/db/LBFactory.php', + 'LBFactorySingle' => 'includes/db/LBFactorySingle.php', 'LikeMatch' => 'includes/db/DatabaseUtility.php', 'LoadBalancer' => 'includes/db/LoadBalancer.php', - 'LoadBalancer_Single' => 'includes/db/LBFactory_Single.php', + 'LoadBalancerSingle' => 'includes/db/LBFactorySingle.php', 'LoadMonitor' => 'includes/db/LoadMonitor.php', - 'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php', - 'LoadMonitor_Null' => 'includes/db/LoadMonitor.php', + 'LoadMonitorMySQL' => 'includes/db/LoadMonitor.php', + 'LoadMonitorNull' => 'includes/db/LoadMonitor.php', 'MssqlField' => 'includes/db/DatabaseMssql.php', - 'MssqlResult' => 'includes/db/DatabaseMssql.php', + 'MssqlBlob' => 'includes/db/DatabaseMssql.php', + 'MssqlResultWrapper' => 'includes/db/DatabaseMssql.php', 'MySQLField' => 'includes/db/DatabaseMysqlBase.php', 'MySQLMasterPos' => 'includes/db/DatabaseMysqlBase.php', 'ORAField' => 'includes/db/DatabaseOracle.php', @@ -528,27 +461,54 @@ $wgAutoloadLocalClasses = array( 'SQLiteField' => 'includes/db/DatabaseSqlite.php', # includes/debug - 'MWDebug' => 'includes/debug/Debug.php', + 'MWDebug' => 'includes/debug/MWDebug.php', + + # includes/deferred + 'DataUpdate' => 'includes/deferred/DataUpdate.php', + 'DeferrableUpdate' => 'includes/deferred/DeferredUpdates.php', + 'DeferredUpdates' => 'includes/deferred/DeferredUpdates.php', + 'HTMLCacheUpdate' => 'includes/deferred/HTMLCacheUpdate.php', + 'LinksDeletionUpdate' => 'includes/deferred/LinksUpdate.php', + 'LinksUpdate' => 'includes/deferred/LinksUpdate.php', + 'MWCallableUpdate' => 'includes/deferred/CallableUpdate.php', + 'SearchUpdate' => 'includes/deferred/SearchUpdate.php', + 'SiteStatsUpdate' => 'includes/deferred/SiteStatsUpdate.php', + 'SqlDataUpdate' => 'includes/deferred/SqlDataUpdate.php', + 'SquidUpdate' => 'includes/deferred/SquidUpdate.php', + 'ViewCountUpdate' => 'includes/deferred/ViewCountUpdate.php', # includes/diff - '_DiffEngine' => 'includes/diff/DairikiDiff.php', - '_DiffOp' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Add' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Change' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Copy' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Delete' => 'includes/diff/DairikiDiff.php', - '_HWLDF_WordAccumulator' => 'includes/diff/DairikiDiff.php', - 'ArrayDiffFormatter' => 'includes/diff/DairikiDiff.php', + 'DiffEngine' => 'includes/diff/DairikiDiff.php', + 'DiffOp' => 'includes/diff/DairikiDiff.php', + 'DiffOpAdd' => 'includes/diff/DairikiDiff.php', + 'DiffOpChange' => 'includes/diff/DairikiDiff.php', + 'DiffOpCopy' => 'includes/diff/DairikiDiff.php', + 'DiffOpDelete' => 'includes/diff/DairikiDiff.php', + 'HWLDFWordAccumulator' => 'includes/diff/DairikiDiff.php', + 'ArrayDiffFormatter' => 'includes/diff/ArrayDiffFormatter.php', 'Diff' => 'includes/diff/DairikiDiff.php', 'DifferenceEngine' => 'includes/diff/DifferenceEngine.php', - 'DiffFormatter' => 'includes/diff/DairikiDiff.php', + 'DiffFormatter' => 'includes/diff/DiffFormatter.php', 'MappedDiff' => 'includes/diff/DairikiDiff.php', 'RangeDifference' => 'includes/diff/WikiDiff3.php', - 'TableDiffFormatter' => 'includes/diff/DairikiDiff.php', - 'UnifiedDiffFormatter' => 'includes/diff/DairikiDiff.php', + 'TableDiffFormatter' => 'includes/diff/TableDiffFormatter.php', + 'UnifiedDiffFormatter' => 'includes/diff/UnifiedDiffFormatter.php', 'WikiDiff3' => 'includes/diff/WikiDiff3.php', 'WordLevelDiff' => 'includes/diff/DairikiDiff.php', + # includes/exception + 'UserBlockedError' => 'includes/exception/UserBlockedError.php', + 'UserNotLoggedIn' => 'includes/exception/UserNotLoggedIn.php', + 'ThrottledError' => 'includes/exception/ThrottledError.php', + 'ReadOnlyError' => 'includes/exception/ReadOnlyError.php', + 'PermissionsError' => 'includes/exception/PermissionsError.php', + 'MWException' => 'includes/exception/MWException.php', + 'MWExceptionHandler' => 'includes/exception/MWExceptionHandler.php', + 'HttpError' => 'includes/exception/HttpError.php', + 'BadTitleError' => 'includes/exception/BadTitleError.php', + 'ErrorPageError' => 'includes/exception/ErrorPageError.php', + 'FatalError' => 'includes/exception/FatalError.php', + # includes/externalstore 'ExternalStore' => 'includes/externalstore/ExternalStore.php', 'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php', @@ -560,6 +520,7 @@ $wgAutoloadLocalClasses = array( 'FileBackendGroup' => 'includes/filebackend/FileBackendGroup.php', 'FileBackend' => 'includes/filebackend/FileBackend.php', 'FileBackendError' => 'includes/filebackend/FileBackend.php', + 'FileBackendException' => 'includes/filebackend/FileBackend.php', 'FileBackendStore' => 'includes/filebackend/FileBackendStore.php', 'FileBackendStoreShardListIterator' => 'includes/filebackend/FileBackendStore.php', 'FileBackendStoreShardDirIterator' => 'includes/filebackend/FileBackendStore.php', @@ -572,6 +533,7 @@ $wgAutoloadLocalClasses = array( 'FSFileBackendDirList' => 'includes/filebackend/FSFileBackend.php', 'FSFileBackendFileList' => 'includes/filebackend/FSFileBackend.php', 'FSFileOpHandle' => 'includes/filebackend/FSFileBackend.php', + 'MemoryFileBackend' => 'includes/filebackend/MemoryFileBackend.php', 'SwiftFileBackend' => 'includes/filebackend/SwiftFileBackend.php', 'SwiftFileBackendList' => 'includes/filebackend/SwiftFileBackend.php', 'SwiftFileBackendDirList' => 'includes/filebackend/SwiftFileBackend.php', @@ -586,7 +548,6 @@ $wgAutoloadLocalClasses = array( 'ScopedLock' => 'includes/filebackend/lockmanager/ScopedLock.php', 'FSLockManager' => 'includes/filebackend/lockmanager/FSLockManager.php', 'DBLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', - 'LSLockManager' => 'includes/filebackend/lockmanager/LSLockManager.php', 'MemcLockManager' => 'includes/filebackend/lockmanager/MemcLockManager.php', 'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php', 'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', @@ -634,73 +595,83 @@ $wgAutoloadLocalClasses = array( 'InstallDocFormatter' => 'includes/installer/InstallDocFormatter.php', 'Installer' => 'includes/installer/Installer.php', 'LocalSettingsGenerator' => 'includes/installer/LocalSettingsGenerator.php', + 'MssqlInstaller' => 'includes/installer/MssqlInstaller.php', + 'MssqlUpdater' => 'includes/installer/MssqlUpdater.php', 'MysqlInstaller' => 'includes/installer/MysqlInstaller.php', 'MysqlUpdater' => 'includes/installer/MysqlUpdater.php', 'OracleInstaller' => 'includes/installer/OracleInstaller.php', 'OracleUpdater' => 'includes/installer/OracleUpdater.php', - 'PhpRefCallBugTester' => 'includes/installer/PhpBugTests.php', 'PhpXmlBugTester' => 'includes/installer/PhpBugTests.php', 'PostgresInstaller' => 'includes/installer/PostgresInstaller.php', 'PostgresUpdater' => 'includes/installer/PostgresUpdater.php', 'SqliteInstaller' => 'includes/installer/SqliteInstaller.php', 'SqliteUpdater' => 'includes/installer/SqliteUpdater.php', 'WebInstaller' => 'includes/installer/WebInstaller.php', - 'WebInstaller_Complete' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Copying' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_DBConnect' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_DBSettings' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Document' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_ExistingWiki' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Install' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Language' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Name' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Options' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Readme' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_ReleaseNotes' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Restart' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Upgrade' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_UpgradeDoc' => 'includes/installer/WebInstallerPage.php', - 'WebInstaller_Welcome' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerComplete' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerCopying' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerDBConnect' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerDBSettings' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerDocument' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerExistingWiki' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerInstall' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerLanguage' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerName' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerOptions' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerReadme' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerReleaseNotes' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerRestart' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerUpgrade' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerUpgradeDoc' => 'includes/installer/WebInstallerPage.php', + 'WebInstallerWelcome' => 'includes/installer/WebInstallerPage.php', 'WebInstallerOutput' => 'includes/installer/WebInstallerOutput.php', 'WebInstallerPage' => 'includes/installer/WebInstallerPage.php', # includes/job - 'Job' => 'includes/job/Job.php', - 'JobQueue' => 'includes/job/JobQueue.php', - 'JobQueueAggregator' => 'includes/job/aggregator/JobQueueAggregator.php', - 'JobQueueAggregatorMemc' => 'includes/job/aggregator/JobQueueAggregatorMemc.php', - 'JobQueueAggregatorRedis' => 'includes/job/aggregator/JobQueueAggregatorRedis.php', - 'JobQueueDB' => 'includes/job/JobQueueDB.php', - 'JobQueueConnectionError' => 'includes/job/JobQueue.php', - 'JobQueueError' => 'includes/job/JobQueue.php', - 'JobQueueGroup' => 'includes/job/JobQueueGroup.php', - 'JobQueueFederated' => 'includes/job/JobQueueFederated.php', - 'JobQueueRedis' => 'includes/job/JobQueueRedis.php', - - # includes/job/jobs - 'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php', - 'DuplicateJob' => 'includes/job/jobs/DuplicateJob.php', - 'EmaillingJob' => 'includes/job/jobs/EmaillingJob.php', - 'EnotifNotifyJob' => 'includes/job/jobs/EnotifNotifyJob.php', - 'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php', - 'NullJob' => 'includes/job/jobs/NullJob.php', - 'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php', - 'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php', - 'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php', - 'AssembleUploadChunksJob' => 'includes/job/jobs/AssembleUploadChunksJob.php', - 'PublishStashedFileJob' => 'includes/job/jobs/PublishStashedFileJob.php', + 'IJobSpecification' => 'includes/jobqueue/JobSpecification.php', + 'Job' => 'includes/jobqueue/Job.php', + 'JobQueue' => 'includes/jobqueue/JobQueue.php', + 'JobQueueAggregator' => 'includes/jobqueue/aggregator/JobQueueAggregator.php', + 'JobQueueAggregatorMemc' => 'includes/jobqueue/aggregator/JobQueueAggregatorMemc.php', + 'JobQueueAggregatorRedis' => 'includes/jobqueue/aggregator/JobQueueAggregatorRedis.php', + 'JobQueueDB' => 'includes/jobqueue/JobQueueDB.php', + 'JobQueueConnectionError' => 'includes/jobqueue/JobQueue.php', + 'JobQueueError' => 'includes/jobqueue/JobQueue.php', + 'JobQueueGroup' => 'includes/jobqueue/JobQueueGroup.php', + 'JobQueueFederated' => 'includes/jobqueue/JobQueueFederated.php', + 'JobQueueRedis' => 'includes/jobqueue/JobQueueRedis.php', + 'JobRunner' => 'includes/jobqueue/JobRunner.php', + 'JobSpecification' => 'includes/jobqueue/JobSpecification.php', + + # includes/jobqueue/jobs + 'DoubleRedirectJob' => 'includes/jobqueue/jobs/DoubleRedirectJob.php', + 'DuplicateJob' => 'includes/jobqueue/jobs/DuplicateJob.php', + 'EmaillingJob' => 'includes/jobqueue/jobs/EmaillingJob.php', + 'EnotifNotifyJob' => 'includes/jobqueue/jobs/EnotifNotifyJob.php', + 'HTMLCacheUpdateJob' => 'includes/jobqueue/jobs/HTMLCacheUpdateJob.php', + 'NullJob' => 'includes/jobqueue/jobs/NullJob.php', + 'RefreshLinksJob' => 'includes/jobqueue/jobs/RefreshLinksJob.php', + 'RefreshLinksJob2' => 'includes/jobqueue/jobs/RefreshLinksJob2.php', + 'UploadFromUrlJob' => 'includes/jobqueue/jobs/UploadFromUrlJob.php', + 'AssembleUploadChunksJob' => 'includes/jobqueue/jobs/AssembleUploadChunksJob.php', + 'PublishStashedFileJob' => 'includes/jobqueue/jobs/PublishStashedFileJob.php', + + # includes/jobqueue/utils + 'BacklinkJobUtils' => 'includes/jobqueue/utils/BacklinkJobUtils.php', # includes/json 'FormatJson' => 'includes/json/FormatJson.php', # includes/libs 'CSSJanus' => 'includes/libs/CSSJanus.php', - 'CSSJanus_Tokenizer' => 'includes/libs/CSSJanus.php', + 'CSSJanusTokenizer' => 'includes/libs/CSSJanus.php', 'CSSMin' => 'includes/libs/CSSMin.php', 'GenericArrayObject' => 'includes/libs/GenericArrayObject.php', + 'HashRing' => 'includes/libs/HashRing.php', 'HttpStatus' => 'includes/libs/HttpStatus.php', 'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php', 'IEUrlExtension' => 'includes/libs/IEUrlExtension.php', + 'MappedIterator' => 'includes/libs/MappedIterator.php', + 'IPSet' => 'includes/libs/IPSet.php', 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php', 'JSCompilerContext' => 'includes/libs/jsminplus.php', 'JSMinPlus' => 'includes/libs/jsminplus.php', @@ -708,6 +679,16 @@ $wgAutoloadLocalClasses = array( 'JSParser' => 'includes/libs/jsminplus.php', 'JSToken' => 'includes/libs/jsminplus.php', 'JSTokenizer' => 'includes/libs/jsminplus.php', + 'MultiHttpClient' => 'includes/libs/MultiHttpClient.php', + 'MWMessagePack' => 'includes/libs/MWMessagePack.php', + 'ProcessCacheLRU' => 'includes/libs/ProcessCacheLRU.php', + 'RunningStat' => 'includes/libs/RunningStat.php', + 'ScopedCallback' => 'includes/libs/ScopedCallback.php', + 'ScopedPHPTimeout' => 'includes/libs/ScopedPHPTimeout.php', + 'SwiftVirtualRESTService' => 'includes/libs/virtualrest/SwiftVirtualRESTService.php', + 'VirtualRESTService' => 'includes/libs/virtualrest/VirtualRESTService.php', + 'VirtualRESTServiceClient' => 'includes/libs/virtualrest/VirtualRESTServiceClient.php', + 'XmlTypeCheck' => 'includes/libs/XmlTypeCheck.php', # includes/libs/lessphp 'lessc' => 'includes/libs/lessc.inc.php', @@ -729,6 +710,7 @@ $wgAutoloadLocalClasses = array( 'ManualLogEntry' => 'includes/logging/LogEntry.php', 'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php', 'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php', + 'PageLangLogFormatter' => 'includes/logging/PageLangLogFormatter.php', 'PatrolLog' => 'includes/logging/PatrolLog.php', 'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php', 'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php', @@ -744,6 +726,11 @@ $wgAutoloadLocalClasses = array( 'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', 'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', + # includes/mail + 'EmailNotification' => 'includes/mail/EmailNotification.php', + 'MailAddress' => 'includes/mail/MailAddress.php', + 'UserMailer' => 'includes/mail/UserMailer.php', + # includes/media 'BitmapHandler' => 'includes/media/Bitmap.php', 'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php', @@ -753,7 +740,6 @@ $wgAutoloadLocalClasses = array( 'DjVuImage' => 'includes/media/DjVuImage.php', 'Exif' => 'includes/media/Exif.php', 'ExifBitmapHandler' => 'includes/media/ExifBitmap.php', - 'FormatExif' => 'includes/media/FormatMetadata.php', 'FormatMetadata' => 'includes/media/FormatMetadata.php', 'GIFHandler' => 'includes/media/GIF.php', 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php', @@ -771,6 +757,7 @@ $wgAutoloadLocalClasses = array( 'SVGReader' => 'includes/media/SVGMetadataExtractor.php', 'ThumbnailImage' => 'includes/media/MediaTransformOutput.php', 'TiffHandler' => 'includes/media/Tiff.php', + 'TransformationalImageHandler' => 'includes/media/TransformationalImageHandler.php', 'TransformParameterError' => 'includes/media/MediaTransformOutput.php', 'XCFHandler' => 'includes/media/XCF.php', 'XMPInfo' => 'includes/media/XMPInfo.php', @@ -783,10 +770,7 @@ $wgAutoloadLocalClasses = array( # includes/objectcache 'APCBagOStuff' => 'includes/objectcache/APCBagOStuff.php', 'BagOStuff' => 'includes/objectcache/BagOStuff.php', - 'DBABagOStuff' => 'includes/objectcache/DBABagOStuff.php', - 'EhcacheBagOStuff' => 'includes/objectcache/EhcacheBagOStuff.php', 'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php', - 'FakeMemCachedClient' => 'includes/objectcache/EmptyBagOStuff.php', 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php', 'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php', 'MemCachedClientforWiki' => 'includes/objectcache/MemcachedClient.php', @@ -802,14 +786,32 @@ $wgAutoloadLocalClasses = array( 'WinCacheBagOStuff' => 'includes/objectcache/WinCacheBagOStuff.php', 'XCacheBagOStuff' => 'includes/objectcache/XCacheBagOStuff.php', + # includes/page + 'Article' => 'includes/page/Article.php', + 'CategoryPage' => 'includes/page/CategoryPage.php', + 'ImageHistoryList' => 'includes/page/ImagePage.php', + 'ImageHistoryPseudoPager' => 'includes/page/ImagePage.php', + 'ImagePage' => 'includes/page/ImagePage.php', + 'Page' => 'includes/page/WikiPage.php', + 'WikiCategoryPage' => 'includes/page/WikiCategoryPage.php', + 'WikiFilePage' => 'includes/page/WikiFilePage.php', + 'WikiPage' => 'includes/page/WikiPage.php', + + # includes/pager + 'AlphabeticPager' => 'includes/pager/AlphabeticPager.php', + 'IndexPager' => 'includes/pager/IndexPager.php', + 'Pager' => 'includes/pager/Pager.php', + 'ReverseChronologicalPager' => 'includes/pager/ReverseChronologicalPager.php', + 'TablePager' => 'includes/pager/TablePager.php', + # includes/parser 'CacheTime' => 'includes/parser/CacheTime.php', 'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php', 'CoreTagHooks' => 'includes/parser/CoreTagHooks.php', 'DateFormatter' => 'includes/parser/DateFormatter.php', 'LinkHolderArray' => 'includes/parser/LinkHolderArray.php', - 'MWTidy' => 'includes/parser/Tidy.php', - 'MWTidyWrapper' => 'includes/parser/Tidy.php', + 'MWTidy' => 'includes/parser/MWTidy.php', + 'MWTidyWrapper' => 'includes/parser/MWTidy.php', 'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', 'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', 'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php', @@ -834,20 +836,36 @@ $wgAutoloadLocalClasses = array( 'ParserCache' => 'includes/parser/ParserCache.php', 'ParserOptions' => 'includes/parser/ParserOptions.php', 'ParserOutput' => 'includes/parser/ParserOutput.php', - 'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php', + 'ParserDiffTest' => 'includes/parser/ParserDiffTest.php', 'Preprocessor' => 'includes/parser/Preprocessor.php', 'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php', 'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php', 'StripState' => 'includes/parser/StripState.php', + # includes/password + 'BcryptPassword' => 'includes/password/BcryptPassword.php', + 'InvalidPassword' => 'includes/password/InvalidPassword.php', + 'LayeredParameterizedPassword' => 'includes/password/LayeredParameterizedPassword.php', + 'MWSaltedPassword' => 'includes/password/MWSaltedPassword.php', + 'MWOldPassword' => 'includes/password/MWOldPassword.php', + 'ParameterizedPassword' => 'includes/password/ParameterizedPassword.php', + 'Password' => 'includes/password/Password.php', + 'PasswordError' => 'includes/password/PasswordError.php', + 'PasswordFactory' => 'includes/password/PasswordFactory.php', + 'Pbkdf2Password' => 'includes/password/Pbkdf2Password.php', + 'EncryptedPassword' => 'includes/password/EncryptedPassword.php', + # includes/profiler 'Profiler' => 'includes/profiler/Profiler.php', - 'ProfilerSimple' => 'includes/profiler/ProfilerSimple.php', + 'ProfilerMwprof' => 'includes/profiler/ProfilerMwprof.php', + 'ProfilerSimpleDB' => 'includes/profiler/ProfilerSimpleDB.php', 'ProfilerSimpleText' => 'includes/profiler/ProfilerSimpleText.php', 'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php', 'ProfilerSimpleUDP' => 'includes/profiler/ProfilerSimpleUDP.php', + 'ProfilerStandard' => 'includes/profiler/ProfilerStandard.php', 'ProfilerStub' => 'includes/profiler/ProfilerStub.php', 'ProfileSection' => 'includes/profiler/Profiler.php', + 'TransactionProfiler' => 'includes/profiler/Profiler.php', # includes/rcfeed 'RCFeedEngine' => 'includes/rcfeed/RCFeedEngine.php', @@ -856,62 +874,65 @@ $wgAutoloadLocalClasses = array( 'RCFeedFormatter' => 'includes/rcfeed/RCFeedFormatter.php', 'IRCColourfulRCFeedFormatter' => 'includes/rcfeed/IRCColourfulRCFeedFormatter.php', 'JSONRCFeedFormatter' => 'includes/rcfeed/JSONRCFeedFormatter.php', + 'XMLRCFeedFormatter' => 'includes/rcfeed/XMLRCFeedFormatter.php', + 'MachineReadableRCFeedFormatter' => 'includes/rcfeed/MachineReadableRCFeedFormatter.php', # includes/resourceloader + 'DerivativeResourceLoaderContext' => + 'includes/resourceloader/DerivativeResourceLoaderContext.php', 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php', 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php', + 'ResourceLoaderEditToolbarModule' => 'includes/resourceloader/ResourceLoaderEditToolbarModule.php', 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php', 'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php', - 'ResourceLoaderLESSFunctions' => 'includes/resourceloader/ResourceLoaderLESSFunctions.php', + 'ResourceLoaderFilePath' => 'includes/resourceloader/ResourceLoaderFilePath.php', 'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php', 'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php', 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php', 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php', - 'ResourceLoaderUserCSSPrefsModule' => 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php', + 'ResourceLoaderUserCSSPrefsModule' => + 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php', 'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php', 'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php', 'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php', 'ResourceLoaderUserTokensModule' => 'includes/resourceloader/ResourceLoaderUserTokensModule.php', - 'ResourceLoaderLanguageDataModule' => 'includes/resourceloader/ResourceLoaderLanguageDataModule.php', + 'ResourceLoaderLanguageDataModule' => + 'includes/resourceloader/ResourceLoaderLanguageDataModule.php', + 'ResourceLoaderLanguageNamesModule' => + 'includes/resourceloader/ResourceLoaderLanguageNamesModule.php', 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php', # includes/revisiondelete - 'RevDel_ArchivedFileItem' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_ArchivedFileList' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_ArchivedRevisionItem' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_ArchiveItem' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_ArchiveList' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_FileItem' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_FileList' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_Item' => 'includes/revisiondelete/RevisionDeleteAbstracts.php', - 'RevDel_List' => 'includes/revisiondelete/RevisionDeleteAbstracts.php', - 'RevDel_LogItem' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_LogList' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_RevisionItem' => 'includes/revisiondelete/RevisionDelete.php', - 'RevDel_RevisionList' => 'includes/revisiondelete/RevisionDelete.php', + 'RevDelArchivedFileItem' => 'includes/revisiondelete/RevDelArchivedFileItem.php', + 'RevDelArchivedFileList' => 'includes/revisiondelete/RevDelArchivedFileList.php', + 'RevDelArchivedRevisionItem' => 'includes/revisiondelete/RevDelArchivedRevisionItem.php', + 'RevDelArchiveItem' => 'includes/revisiondelete/RevDelArchiveItem.php', + 'RevDelArchiveList' => 'includes/revisiondelete/RevDelArchiveList.php', + 'RevDelFileItem' => 'includes/revisiondelete/RevDelFileItem.php', + 'RevDelFileList' => 'includes/revisiondelete/RevDelFileList.php', + 'RevDelItem' => 'includes/revisiondelete/RevDelItem.php', + 'RevDelList' => 'includes/revisiondelete/RevDelList.php', + 'RevDelLogItem' => 'includes/revisiondelete/RevDelLogItem.php', + 'RevDelLogList' => 'includes/revisiondelete/RevDelLogList.php', + 'RevDelRevisionItem' => 'includes/revisiondelete/RevDelRevisionItem.php', + 'RevDelRevisionList' => 'includes/revisiondelete/RevDelRevisionList.php', 'RevisionDeleter' => 'includes/revisiondelete/RevisionDeleter.php', 'RevisionDeleteUser' => 'includes/revisiondelete/RevisionDeleteUser.php', # includes/search - 'MssqlSearchResultSet' => 'includes/search/SearchMssql.php', - 'MySQLSearchResultSet' => 'includes/search/SearchMySQL.php', - 'PostgresSearchResult' => 'includes/search/SearchPostgres.php', - 'PostgresSearchResultSet' => 'includes/search/SearchPostgres.php', + 'SearchDatabase' => 'includes/search/SearchDatabase.php', 'SearchEngine' => 'includes/search/SearchEngine.php', 'SearchEngineDummy' => 'includes/search/SearchEngine.php', - 'SearchHighlighter' => 'includes/search/SearchEngine.php', + 'SearchHighlighter' => 'includes/search/SearchHighlighter.php', 'SearchMssql' => 'includes/search/SearchMssql.php', 'SearchMySQL' => 'includes/search/SearchMySQL.php', - 'SearchNearMatchResultSet' => 'includes/search/SearchEngine.php', + 'SearchNearMatchResultSet' => 'includes/search/SearchResultSet.php', 'SearchOracle' => 'includes/search/SearchOracle.php', 'SearchPostgres' => 'includes/search/SearchPostgres.php', - 'SearchResult' => 'includes/search/SearchEngine.php', - 'SearchResultSet' => 'includes/search/SearchEngine.php', - 'SearchResultTooMany' => 'includes/search/SearchEngine.php', + 'SearchResult' => 'includes/search/SearchResult.php', + 'SearchResultSet' => 'includes/search/SearchResultSet.php', 'SearchSqlite' => 'includes/search/SearchSqlite.php', - 'SearchUpdate' => 'includes/search/SearchUpdate.php', - 'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php', - 'SqlSearchResultSet' => 'includes/search/SearchEngine.php', + 'SqlSearchResultSet' => 'includes/search/SearchResultSet.php', # includes/site 'MediaWikiSite' => 'includes/site/MediaWikiSite.php', @@ -923,9 +944,35 @@ $wgAutoloadLocalClasses = array( 'Sites' => 'includes/site/SiteSQLStore.php', 'SiteStore' => 'includes/site/SiteStore.php', + # includes/skins + 'BaseTemplate' => 'includes/skins/SkinTemplate.php', + 'MediaWikiI18N' => 'includes/skins/SkinTemplate.php', + 'QuickTemplate' => 'includes/skins/SkinTemplate.php', + 'Skin' => 'includes/skins/Skin.php', + 'SkinException' => 'includes/skins/SkinException.php', + 'SkinFactory' => 'includes/skins/SkinFactory.php', + 'SkinFallback' => 'includes/skins/SkinFallback.php', + 'SkinFallbackTemplate' => 'includes/skins/SkinFallbackTemplate.php', + 'SkinTemplate' => 'includes/skins/SkinTemplate.php', + + # includes/specialpage + 'ChangesListSpecialPage' => 'includes/specialpage/ChangesListSpecialPage.php', + 'FormSpecialPage' => 'includes/specialpage/FormSpecialPage.php', + 'ImageQueryPage' => 'includes/specialpage/ImageQueryPage.php', + 'IncludableSpecialPage' => 'includes/specialpage/IncludableSpecialPage.php', + 'PageQueryPage' => 'includes/specialpage/PageQueryPage.php', + 'QueryPage' => 'includes/specialpage/QueryPage.php', + 'RedirectSpecialArticle' => 'includes/specialpage/RedirectSpecialPage.php', + 'RedirectSpecialPage' => 'includes/specialpage/RedirectSpecialPage.php', + 'SpecialPage' => 'includes/specialpage/SpecialPage.php', + 'SpecialPageFactory' => 'includes/specialpage/SpecialPageFactory.php', + 'SpecialRedirectToSpecial' => 'includes/specialpage/RedirectSpecialPage.php', + 'UnlistedSpecialPage' => 'includes/specialpage/UnlistedSpecialPage.php', + 'WantedQueryPage' => 'includes/specialpage/WantedQueryPage.php', + # includes/specials 'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php', - 'AllmessagesTablePager' => 'includes/specials/SpecialAllmessages.php', + 'AllMessagesTablePager' => 'includes/specials/SpecialAllMessages.php', 'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php', 'BlockListPager' => 'includes/specials/SpecialBlockList.php', 'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php', @@ -941,15 +988,15 @@ $wgAutoloadLocalClasses = array( 'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php', 'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php', 'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php', - 'HTMLBlockedUsersItemSelect' => 'includes/specials/SpecialBlockList.php', 'ImageListPager' => 'includes/specials/SpecialListfiles.php', 'ImportReporter' => 'includes/specials/SpecialImport.php', - 'IPBlockForm' => 'includes/specials/SpecialBlock.php', 'LinkSearchPage' => 'includes/specials/SpecialLinkSearch.php', 'ListredirectsPage' => 'includes/specials/SpecialListredirects.php', + 'ListDuplicatedFilesPage' => 'includes/specials/SpecialListDuplicatedFiles.php', 'LoginForm' => 'includes/specials/SpecialUserlogin.php', 'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php', 'LongPagesPage' => 'includes/specials/SpecialLongpages.php', + 'MediaStatisticsPage' => 'includes/specials/SpecialMediaStatistics.php', 'MergeHistoryPager' => 'includes/specials/SpecialMergeHistory.php', 'MIMEsearchPage' => 'includes/specials/SpecialMIMEsearch.php', 'MostcategoriesPage' => 'includes/specials/SpecialMostcategories.php', @@ -969,8 +1016,9 @@ $wgAutoloadLocalClasses = array( 'RandomPage' => 'includes/specials/SpecialRandompage.php', 'ShortPagesPage' => 'includes/specials/SpecialShortpages.php', 'SpecialActiveUsers' => 'includes/specials/SpecialActiveusers.php', - 'SpecialAllmessages' => 'includes/specials/SpecialAllmessages.php', - 'SpecialAllpages' => 'includes/specials/SpecialAllpages.php', + 'SpecialAllMessages' => 'includes/specials/SpecialAllMessages.php', + 'SpecialAllMyUploads' => 'includes/specials/SpecialMyRedirectPages.php', + 'SpecialAllPages' => 'includes/specials/SpecialAllPages.php', 'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php', 'SpecialBlock' => 'includes/specials/SpecialBlock.php', 'SpecialBlockList' => 'includes/specials/SpecialBlockList.php', @@ -981,23 +1029,34 @@ $wgAutoloadLocalClasses = array( 'SpecialChangePassword' => 'includes/specials/SpecialChangePassword.php', 'SpecialComparePages' => 'includes/specials/SpecialComparePages.php', 'SpecialContributions' => 'includes/specials/SpecialContributions.php', + 'SpecialCreateAccount' => 'includes/specials/SpecialCreateAccount.php', + 'SpecialDiff' => 'includes/specials/SpecialDiff.php', 'SpecialEditWatchlist' => 'includes/specials/SpecialEditWatchlist.php', 'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php', + 'SpecialExpandTemplates' => 'includes/specials/SpecialExpandTemplates.php', 'SpecialExport' => 'includes/specials/SpecialExport.php', 'SpecialFilepath' => 'includes/specials/SpecialFilepath.php', 'SpecialImport' => 'includes/specials/SpecialImport.php', 'SpecialJavaScriptTest' => 'includes/specials/SpecialJavaScriptTest.php', + 'SpecialListAdmins' => 'includes/specials/SpecialListusers.php', + 'SpecialListBots' => 'includes/specials/SpecialListusers.php', 'SpecialListFiles' => 'includes/specials/SpecialListfiles.php', 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', 'SpecialListUsers' => 'includes/specials/SpecialListusers.php', 'SpecialLockdb' => 'includes/specials/SpecialLockdb.php', 'SpecialLog' => 'includes/specials/SpecialLog.php', 'SpecialMergeHistory' => 'includes/specials/SpecialMergeHistory.php', + 'SpecialMycontributions' => 'includes/specials/SpecialMyRedirectPages.php', + 'SpecialMyLanguage' => 'includes/specials/SpecialMyLanguage.php', + 'SpecialMypage' => 'includes/specials/SpecialMyRedirectPages.php', + 'SpecialMytalk' => 'includes/specials/SpecialMyRedirectPages.php', + 'SpecialMyuploads' => 'includes/specials/SpecialMyRedirectPages.php', 'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php', 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php', + 'SpecialPageLanguage' => 'includes/specials/SpecialPageLanguage.php', 'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php', 'SpecialPagesWithProp' => 'includes/specials/SpecialPagesWithProp.php', - 'SpecialPermanentLink' => 'includes/SpecialPage.php', + 'SpecialPermanentLink' => 'includes/specials/SpecialPermanentLink.php', 'SpecialPreferences' => 'includes/specials/SpecialPreferences.php', 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php', 'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php', @@ -1005,14 +1064,16 @@ $wgAutoloadLocalClasses = array( 'SpecialRandomInCategory' => 'includes/specials/SpecialRandomInCategory.php', 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php', 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php', - 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php', + 'SpecialRecentChangesLinked' => 'includes/specials/SpecialRecentchangeslinked.php', 'SpecialRedirect' => 'includes/specials/SpecialRedirect.php', 'SpecialResetTokens' => 'includes/specials/SpecialResetTokens.php', 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php', + 'SpecialRunJobs' => 'includes/specials/SpecialRunJobs.php', 'SpecialSearch' => 'includes/specials/SpecialSearch.php', 'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php', 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php', 'SpecialTags' => 'includes/specials/SpecialTags.php', + 'SpecialTrackingCategories' => 'includes/specials/SpecialTrackingCategories.php', 'SpecialUnblock' => 'includes/specials/SpecialUnblock.php', 'SpecialUndelete' => 'includes/specials/SpecialUndelete.php', 'SpecialUnlockdb' => 'includes/specials/SpecialUnlockdb.php', @@ -1042,13 +1103,21 @@ $wgAutoloadLocalClasses = array( 'WantedFilesPage' => 'includes/specials/SpecialWantedfiles.php', 'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php', 'WantedTemplatesPage' => 'includes/specials/SpecialWantedtemplates.php', - 'WatchlistEditor' => 'includes/specials/SpecialEditWatchlist.php', 'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php', # includes/templates 'UserloginTemplate' => 'includes/templates/Userlogin.php', 'UsercreateTemplate' => 'includes/templates/Usercreate.php', + # includes/title + 'PageLinkRenderer' => 'includes/title/PageLinkRenderer.php', + 'TitleFormatter' => 'includes/title/TitleFormatter.php', + 'TitleParser' => 'includes/title/TitleParser.php', + 'TitleValue' => 'includes/title/TitleValue.php', + 'MalformedTitleException' => 'includes/title/MalformedTitleException.php', + 'MediaWikiPageLinkRenderer' => 'includes/title/MediaWikiPageLinkRenderer.php', + 'MediaWikiTitleCodec' => 'includes/title/MediaWikiTitleCodec.php', + # includes/upload 'UploadBase' => 'includes/upload/UploadBase.php', 'UploadFromFile' => 'includes/upload/UploadFromFile.php', @@ -1067,18 +1136,43 @@ $wgAutoloadLocalClasses = array( 'UploadStashWrongOwnerException' => 'includes/upload/UploadStash.php', 'UploadStashNoSuchKeyException' => 'includes/upload/UploadStash.php', + # includes/utils + 'ArrayUtils' => 'includes/utils/ArrayUtils.php', + 'CdbException' => 'includes/utils/Cdb.php', + 'CdbFunctions' => 'includes/utils/CdbPHP.php', + 'CdbReader' => 'includes/utils/Cdb.php', + 'CdbReaderDBA' => 'includes/utils/CdbDBA.php', + 'CdbReaderPHP' => 'includes/utils/CdbPHP.php', + 'CdbWriter' => 'includes/utils/Cdb.php', + 'CdbWriterDBA' => 'includes/utils/CdbDBA.php', + 'CdbWriterPHP' => 'includes/utils/CdbPHP.php', + 'DoubleReplacer' => 'includes/utils/StringUtils.php', + 'ExplodeIterator' => 'includes/utils/StringUtils.php', + 'HashtableReplacer' => 'includes/utils/StringUtils.php', + 'IP' => 'includes/utils/IP.php', + 'MWCryptRand' => 'includes/utils/MWCryptRand.php', + 'MWCryptHKDF' => 'includes/utils/MWCryptHKDF.php', + 'MWFunction' => 'includes/utils/MWFunction.php', + 'RegexlikeReplacer' => 'includes/utils/StringUtils.php', + 'ReplacementArray' => 'includes/utils/StringUtils.php', + 'Replacer' => 'includes/utils/StringUtils.php', + 'StringUtils' => 'includes/utils/StringUtils.php', + 'UIDGenerator' => 'includes/utils/UIDGenerator.php', + 'ZipDirectoryReader' => 'includes/utils/ZipDirectoryReader.php', + 'ZipDirectoryReaderError' => 'includes/utils/ZipDirectoryReader.php', + # languages - 'ConverterRule' => 'languages/LanguageConverter.php', - 'FakeConverter' => 'languages/Language.php', + 'ConverterRule' => 'languages/ConverterRule.php', + 'FakeConverter' => 'languages/FakeConverter.php', 'Language' => 'languages/Language.php', 'LanguageConverter' => 'languages/LanguageConverter.php', - 'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleEvaluator.php', - 'CLDRPluralRuleConverter_Expression' => 'languages/utils/CLDRPluralRuleEvaluator.php', - 'CLDRPluralRuleConverter_Fragment' => 'languages/utils/CLDRPluralRuleEvaluator.php', - 'CLDRPluralRuleConverter_Operator' => 'languages/utils/CLDRPluralRuleEvaluator.php', + 'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleConverter.php', + 'CLDRPluralRuleConverterExpression' => 'languages/utils/CLDRPluralRuleConverterExpression.php', + 'CLDRPluralRuleConverterFragment' => 'languages/utils/CLDRPluralRuleConverterFragment.php', + 'CLDRPluralRuleConverterOperator' => 'languages/utils/CLDRPluralRuleConverterOperator.php', 'CLDRPluralRuleEvaluator' => 'languages/utils/CLDRPluralRuleEvaluator.php', - 'CLDRPluralRuleEvaluator_Range' => 'languages/utils/CLDRPluralRuleEvaluator.php', - 'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleEvaluator.php', + 'CLDRPluralRuleEvaluatorRange' => 'languages/utils/CLDRPluralRuleEvaluatorRange.php', + 'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleError.php', # maintenance 'BackupDumper' => 'maintenance/backup.inc', @@ -1092,6 +1186,7 @@ $wgAutoloadLocalClasses = array( 'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php', 'LoggedUpdateMaintenance' => 'maintenance/Maintenance.php', 'Maintenance' => 'maintenance/Maintenance.php', + 'PopulateBacklinkNamespace' => 'maintenance/populateBacklinkNamespace.php', 'PopulateCategory' => 'maintenance/populateCategory.php', 'PopulateImageSha1' => 'maintenance/populateImageSha1.php', 'PopulateFilearchiveSha1' => 'maintenance/populateFilearchiveSha1.php', @@ -1108,13 +1203,12 @@ $wgAutoloadLocalClasses = array( 'UserDupes' => 'maintenance/userDupes.inc', # maintenance/language - 'csvStatsOutput' => 'maintenance/language/StatOutputs.php', - 'extensionLanguages' => 'maintenance/language/languages.inc', - 'languages' => 'maintenance/language/languages.inc', - 'MessageWriter' => 'maintenance/language/writeMessagesArray.inc', - 'statsOutput' => 'maintenance/language/StatOutputs.php', - 'textStatsOutput' => 'maintenance/language/StatOutputs.php', - 'wikiStatsOutput' => 'maintenance/language/StatOutputs.php', + 'CsvStatsOutput' => 'maintenance/language/StatOutputs.php', + 'ExtensionLanguages' => 'maintenance/language/languages.inc', + 'Languages' => 'maintenance/language/languages.inc', + 'StatsOutput' => 'maintenance/language/StatOutputs.php', + 'TextStatsOutput' => 'maintenance/language/StatOutputs.php', + 'WikiStatsOutput' => 'maintenance/language/StatOutputs.php', # maintenance/term 'AnsiTermColorer' => 'maintenance/term/MWTerm.php', @@ -1123,29 +1217,19 @@ $wgAutoloadLocalClasses = array( # mw-config 'InstallerOverrides' => 'mw-config/overrides.php', 'MyLocalSettingsGenerator' => 'mw-config/overrides.php', - - # skins - 'CologneBlueTemplate' => 'skins/CologneBlue.php', - 'ModernTemplate' => 'skins/Modern.php', - 'MonoBookTemplate' => 'skins/MonoBook.php', - 'SkinCologneBlue' => 'skins/CologneBlue.php', - 'SkinModern' => 'skins/Modern.php', - 'SkinMonoBook' => 'skins/MonoBook.php', - 'SkinVector' => 'skins/Vector.php', - 'VectorTemplate' => 'skins/Vector.php', ); class AutoLoader { + static protected $autoloadLocalClassesLower = null; + /** * autoload - take a class name and attempt to load it * - * @param string $className name of class we're looking for. - * @return bool Returning false is important on failure as - * it allows Zend to try and look in other registered autoloaders - * as well. + * @param string $className Name of class we're looking for. */ static function autoload( $className ) { - global $wgAutoloadClasses, $wgAutoloadLocalClasses; + global $wgAutoloadClasses, $wgAutoloadLocalClasses, + $wgAutoloadAttemptLowercase; // Workaround for PHP bug <https://bugs.php.net/bug.php?id=49143> (5.3.2. is broken, it's // fixed in 5.3.6). Strip leading backslashes from class names. When namespaces are used, @@ -1156,41 +1240,46 @@ class AutoLoader { // do not strip the leading backlash in this case, causing autoloading to fail. $className = ltrim( $className, '\\' ); + $filename = false; + if ( isset( $wgAutoloadLocalClasses[$className] ) ) { $filename = $wgAutoloadLocalClasses[$className]; } elseif ( isset( $wgAutoloadClasses[$className] ) ) { $filename = $wgAutoloadClasses[$className]; - } else { - # Try a different capitalisation - # The case can sometimes be wrong when unserializing PHP 4 objects - $filename = false; + } elseif ( $wgAutoloadAttemptLowercase ) { + /* + * Try a different capitalisation. + * + * PHP 4 objects are always serialized with the classname coerced to lowercase, + * and we are plagued with several legacy uses created by MediaWiki < 1.5, see + * https://wikitech.wikimedia.org/wiki/Text_storage_data + */ $lowerClass = strtolower( $className ); - foreach ( $wgAutoloadLocalClasses as $class2 => $file2 ) { - if ( strtolower( $class2 ) == $lowerClass ) { - $filename = $file2; - } + if ( self::$autoloadLocalClassesLower === null ) { + self::$autoloadLocalClassesLower = array_change_key_case( $wgAutoloadLocalClasses, CASE_LOWER ); } - if ( !$filename ) { - if ( function_exists( 'wfDebug' ) ) { - wfDebug( "Class {$className} not found; skipped loading\n" ); + if ( isset( self::$autoloadLocalClassesLower[$lowerClass] ) ) { + if ( function_exists( 'wfDebugLog' ) ) { + wfDebugLog( 'autoloader', "Class {$className} was loaded using incorrect case" ); } - - # Give up - return false; + $filename = self::$autoloadLocalClassesLower[$lowerClass]; } } - # Make an absolute path, this improves performance by avoiding some stat calls + if ( !$filename ) { + // Class not found; let the next autoloader try to find it + return; + } + + // Make an absolute path, this improves performance by avoiding some stat calls if ( substr( $filename, 0, 1 ) != '/' && substr( $filename, 1, 1 ) != ':' ) { global $IP; $filename = "$IP/$filename"; } require $filename; - - return true; } /** @@ -1198,12 +1287,20 @@ class AutoLoader { * Sanitizer that have define()s outside of their class definition. Of course * this wouldn't be necessary if everything in MediaWiki was class-based. Sigh. * - * @param $class string - * @return Boolean Return the results of class_exists() so we know if we were successful + * @param string $class + * @return bool Return the results of class_exists() so we know if we were successful */ static function loadClass( $class ) { return class_exists( $class ); } + + /** + * Method to clear the protected class property $autoloadLocalClassesLower. + * Used in tests. + */ + static function resetAutoloadLocalClassesLower() { + self::$autoloadLocalClassesLower = null; + } } spl_autoload_register( array( 'AutoLoader', 'autoload' ) ); diff --git a/includes/Autopromote.php b/includes/Autopromote.php index 170d7abf..81f3b7aa 100644 --- a/includes/Autopromote.php +++ b/includes/Autopromote.php @@ -29,7 +29,7 @@ class Autopromote { /** * Get the groups for the given user based on $wgAutopromote. * - * @param $user User The user to get the groups for + * @param User $user The user to get the groups for * @return array Array of groups to promote to. */ public static function getAutopromoteGroups( User $user ) { @@ -53,8 +53,8 @@ class Autopromote { * * Does not return groups the user already belongs to or has once belonged. * - * @param $user User The user to get the groups for - * @param string $event key in $wgAutopromoteOnce (each one has groups/criteria) + * @param User $user The user to get the groups for + * @param string $event Key in $wgAutopromoteOnce (each one has groups/criteria) * * @return array Groups the user should be promoted to. * @@ -99,8 +99,8 @@ class Autopromote { * This function evaluates the former type recursively, and passes off to * self::checkCondition for evaluation of the latter type. * - * @param $cond Mixed: a condition, possibly containing other conditions - * @param $user User The user to check the conditions against + * @param mixed $cond A condition, possibly containing other conditions + * @param User $user The user to check the conditions against * @return bool Whether the condition is true */ private static function recCheckCondition( $cond, User $user ) { @@ -156,7 +156,7 @@ class Autopromote { * APCOND_AGE. Other types will throw an exception if no extension evaluates them. * * @param array $cond A condition, which must not contain other conditions - * @param $user User The user to check the condition against + * @param User $user The user to check the condition against * @throws MWException * @return bool Whether the condition is true for the user */ @@ -197,7 +197,8 @@ class Autopromote { return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) ); default: $result = null; - wfRunHooks( 'AutopromoteCondition', array( $cond[0], array_slice( $cond, 1 ), $user, &$result ) ); + wfRunHooks( 'AutopromoteCondition', array( $cond[0], + array_slice( $cond, 1 ), $user, &$result ) ); if ( $result === null ) { throw new MWException( "Unrecognized condition {$cond[0]} for autopromotion!" ); } diff --git a/includes/Block.php b/includes/Block.php index 34b89e73..6a29a056 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -20,33 +20,54 @@ * @file */ class Block { - /* public*/ var $mReason, $mTimestamp, $mAuto, $mExpiry, $mHideName; + /** @var string */ + public $mReason; - protected - $mId, - $mFromMaster, + /** @var bool|string */ + public $mTimestamp; - $mBlockEmail, - $mDisableUsertalk, - $mCreateAccount, - $mParentBlockId; + /** @var int */ + public $mAuto; - /// @var User|String + /** @var bool|string */ + public $mExpiry; + + public $mHideName; + + /** @var int */ + public $mParentBlockId; + + /** @var int */ + protected $mId; + + /** @var bool */ + protected $mFromMaster; + + /** @var bool */ + protected $mBlockEmail; + + /** @var bool */ + protected $mDisableUsertalk; + + /** @var bool */ + protected $mCreateAccount; + + /** @var User|string */ protected $target; - // @var Integer Hack for foreign blocking (CentralAuth) + /** @var int Hack for foreign blocking (CentralAuth) */ protected $forcedTargetID; - /// @var Block::TYPE_ constant. Can only be USER, IP or RANGE internally + /** @var int Block::TYPE_ constant. Can only be USER, IP or RANGE internally */ protected $type; - /// @var User + /** @var User */ protected $blocker; - /// @var Bool + /** @var bool */ protected $isHardblock = true; - /// @var Bool + /** @var bool */ protected $isAutoblocking = true; # TYPE constants @@ -57,14 +78,27 @@ class Block { const TYPE_ID = 5; /** - * Constructor - * @todo FIXME: Don't know what the best format to have for this constructor is, but fourteen - * optional parameters certainly isn't it. + * @todo FIXME: Don't know what the best format to have for this constructor + * is, but fourteen optional parameters certainly isn't it. + * @param string $address + * @param int $user + * @param int $by + * @param string $reason + * @param mixed $timestamp + * @param int $auto + * @param string $expiry + * @param int $anonOnly + * @param int $createAccount + * @param int $enableAutoblock + * @param int $hideName + * @param int $blockEmail + * @param int $allowUsertalk + * @param string $byText */ function __construct( $address = '', $user = 0, $by = 0, $reason = '', $timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0, - $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' ) - { + $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' + ) { if ( $timestamp === 0 ) { $timestamp = wfTimestampNow(); } @@ -102,25 +136,10 @@ class Block { } /** - * Load a block from the database, using either the IP address or - * user ID. Tries the user ID first, and if that doesn't work, tries - * the address. - * - * @param string $address IP address of user/anon - * @param $user Integer: user id of user - * @return Block Object - * @deprecated since 1.18 - */ - public static function newFromDB( $address, $user = 0 ) { - wfDeprecated( __METHOD__, '1.18' ); - return self::newFromTarget( User::whoIs( $user ), $address ); - } - - /** * Load a blocked user from their block id. * - * @param $id Integer: Block id to search for - * @return Block object or null + * @param int $id Block id to search for + * @return Block|null */ public static function newFromID( $id ) { $dbr = wfGetDB( DB_SLAVE ); @@ -166,7 +185,7 @@ class Block { * Check if two blocks are effectively equal. Doesn't check irrelevant things like * the blocking user or the block timestamp, only things which affect the blocked user * - * @param $block Block + * @param Block $block * * @return bool */ @@ -187,52 +206,14 @@ class Block { } /** - * Clear all member variables in the current object. Does not clear - * the block from the DB. - * @deprecated since 1.18 - */ - public function clear() { - wfDeprecated( __METHOD__, '1.18' ); - # Noop - } - - /** - * Get a block from the DB, with either the given address or the given username - * - * @param string $address The IP address of the user, or blank to skip IP blocks - * @param int $user The user ID, or zero for anonymous users - * @return Boolean: the user is blocked from editing - * @deprecated since 1.18 - */ - public function load( $address = '', $user = 0 ) { - wfDeprecated( __METHOD__, '1.18' ); - if ( $user ) { - $username = User::whoIs( $user ); - $block = self::newFromTarget( $username, $address ); - } else { - $block = self::newFromTarget( null, $address ); - } - - if ( $block instanceof Block ) { - # This is mildly evil, but hey, it's B/C :D - foreach ( $block as $variable => $value ) { - $this->$variable = $value; - } - return true; - } else { - return false; - } - } - - /** * Load a block from the database which affects the already-set $this->target: * 1) A block directly on the given user or IP * 2) A rangeblock encompassing the given IP (smallest first) * 3) An autoblock on the given IP - * @param $vagueTarget User|String also search for blocks affecting this target. Doesn't + * @param User|string $vagueTarget Also search for blocks affecting this target. Doesn't * make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups. * @throws MWException - * @return Bool whether a relevant block was found + * @return bool Whether a relevant block was found */ protected function newLoad( $vagueTarget = null ) { $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE ); @@ -333,7 +314,7 @@ class Block { * Get a set of SQL conditions which will select rangeblocks encompassing a given range * @param string $start Hexadecimal IP representation * @param string $end Hexadecimal IP representation, or null to use $start = $end - * @return String + * @return string */ public static function getRangeCond( $start, $end = null ) { if ( $end === null ) { @@ -365,8 +346,8 @@ class Block { /** * Get the component of an IP address which is certain to be the same between an IP * address and a rangeblock containing that IP address. - * @param $hex String Hexadecimal IP representation - * @return String + * @param string $hex Hexadecimal IP representation + * @return string */ protected static function getIpFragment( $hex ) { global $wgBlockCIDRLimit; @@ -380,7 +361,7 @@ class Block { /** * Given a database row from the ipblocks table, initialize * member variables - * @param $row ResultWrapper: a row from the ipblocks table + * @param stdClass $row A row from the ipblocks table */ protected function initFromRow( $row ) { $this->setTarget( $row->ipb_address ); @@ -415,7 +396,7 @@ class Block { /** * Create a new Block object from a database row - * @param $row ResultWrapper row from the ipblocks table + * @param stdClass $row Row from the ipblocks table * @return Block */ public static function newFromRow( $row ) { @@ -428,7 +409,7 @@ class Block { * Delete the row from the IP blocks table. * * @throws MWException - * @return Boolean + * @return bool */ public function delete() { if ( wfReadOnly() ) { @@ -450,8 +431,8 @@ class Block { * Insert a block into the block table. Will fail if there is a conflicting * block (same name and options) already in the database. * - * @param $dbw DatabaseBase if you have one available - * @return mixed: false on failure, assoc array on success: + * @param DatabaseBase $dbw If you have one available + * @return bool|array False on failure, assoc array on success: * ('id' => block ID, 'autoIds' => array of autoblock IDs) */ public function insert( $dbw = null ) { @@ -488,13 +469,15 @@ class Block { * Update a block in the DB with new parameters. * The ID field needs to be loaded first. * - * @return Int number of affected rows, which should probably be 1 or something has - * gone slightly awry + * @return bool|array False on failure, array on success: + * ('id' => block ID, 'autoIds' => array of autoblock IDs) */ public function update() { wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" ); $dbw = wfGetDB( DB_MASTER ); + $dbw->startAtomic( __METHOD__ ); + $dbw->update( 'ipblocks', $this->getDatabaseArray( $dbw ), @@ -502,13 +485,39 @@ class Block { __METHOD__ ); - return $dbw->affectedRows(); + $affected = $dbw->affectedRows(); + + if ( $this->isAutoblocking() ) { + // update corresponding autoblock(s) (bug 48813) + $dbw->update( + 'ipblocks', + $this->getAutoblockUpdateArray(), + array( 'ipb_parent_block_id' => $this->getId() ), + __METHOD__ + ); + } else { + // autoblock no longer required, delete corresponding autoblock(s) + $dbw->delete( + 'ipblocks', + array( 'ipb_parent_block_id' => $this->getId() ), + __METHOD__ + ); + } + + $dbw->endAtomic( __METHOD__ ); + + if ( $affected ) { + $auto_ipd_ids = $this->doRetroactiveAutoblock(); + return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ); + } + + return false; } /** * Get an array suitable for passing to $dbw->insert() or $dbw->update() - * @param $db DatabaseBase - * @return Array + * @param DatabaseBase $db + * @return array */ protected function getDatabaseArray( $db = null ) { if ( !$db ) { @@ -546,10 +555,24 @@ class Block { } /** + * @return array + */ + protected function getAutoblockUpdateArray() { + return array( + 'ipb_by' => $this->getBy(), + 'ipb_by_text' => $this->getByName(), + 'ipb_reason' => $this->mReason, + 'ipb_create_account' => $this->prevents( 'createaccount' ), + 'ipb_deleted' => (int)$this->mHideName, // typecast required for SQLite + 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ), + ); + } + + /** * Retroactively autoblocks the last IP used by the user (if it is a user) * blocked by this Block. * - * @return Array: block IDs of retroactive autoblocks made + * @return array Block IDs of retroactive autoblocks made */ protected function doRetroactiveAutoblock() { $blockIds = array(); @@ -573,7 +596,6 @@ class Block { * * @param Block $block * @param array &$blockIds - * @return Array: block IDs of retroactive autoblocks made */ protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) { global $wgPutIPinRC; @@ -614,7 +636,7 @@ class Block { * TODO: this probably belongs somewhere else, but not sure where... * * @param string $ip The IP to check - * @return Boolean + * @return bool */ public static function isWhitelistedFromAutoblocks( $ip ) { global $wgMemc; @@ -656,8 +678,8 @@ class Block { /** * Autoblocks the given IP, referring to this Block. * - * @param string $autoblockIP the IP to autoblock. - * @return mixed: block ID if an autoblock was inserted, false if not. + * @param string $autoblockIP The IP to autoblock. + * @return int|bool Block ID if an autoblock was inserted, false if not. */ public function doAutoblock( $autoblockIP ) { # If autoblocks are disabled, go away. @@ -698,7 +720,8 @@ class Block { wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" ); $autoblock->setTarget( $autoblockIP ); $autoblock->setBlocker( $this->getBlocker() ); - $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )->inContentLanguage()->plain(); + $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason ) + ->inContentLanguage()->plain(); $timestamp = wfTimestampNow(); $autoblock->mTimestamp = $timestamp; $autoblock->mAuto = 1; @@ -726,7 +749,7 @@ class Block { /** * Check if a block has expired. Delete it if it is. - * @return Boolean + * @return bool */ public function deleteIfExpired() { wfProfileIn( __METHOD__ ); @@ -746,7 +769,7 @@ class Block { /** * Has the block expired? - * @return Boolean + * @return bool */ public function isExpired() { $timestamp = wfTimestampNow(); @@ -761,7 +784,7 @@ class Block { /** * Is the block address valid (i.e. not a null string?) - * @return Boolean + * @return bool */ public function isValid() { return $this->getTarget() != null; @@ -792,7 +815,7 @@ class Block { /** * Get the IP address at the start of the range in Hex form * @throws MWException - * @return String IP in Hex form + * @return string IP in Hex form */ public function getRangeStart() { switch ( $this->type ) { @@ -811,7 +834,7 @@ class Block { /** * Get the IP address at the end of the range in Hex form * @throws MWException - * @return String IP in Hex form + * @return string IP in Hex form */ public function getRangeEnd() { switch ( $this->type ) { @@ -830,7 +853,7 @@ class Block { /** * Get the user id of the blocking sysop * - * @return Integer (0 for foreign users) + * @return int (0 for foreign users) */ public function getBy() { $blocker = $this->getBlocker(); @@ -842,7 +865,7 @@ class Block { /** * Get the username of the blocking sysop * - * @return String + * @return string */ public function getByName() { $blocker = $this->getBlocker(); @@ -860,21 +883,10 @@ class Block { } /** - * Get/set the SELECT ... FOR UPDATE flag - * @deprecated since 1.18 - * - * @param $x Bool - */ - public function forUpdate( $x = null ) { - wfDeprecated( __METHOD__, '1.18' ); - # noop - } - - /** * Get/set a flag determining whether the master is used for reads * - * @param $x Bool - * @return Bool + * @param bool $x + * @return bool */ public function fromMaster( $x = null ) { return wfSetVar( $this->mFromMaster, $x ); @@ -882,8 +894,8 @@ class Block { /** * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range - * @param $x Bool - * @return Bool + * @param bool $x + * @return bool */ public function isHardblock( $x = null ) { wfSetVar( $this->isHardblock, $x ); @@ -906,9 +918,9 @@ class Block { /** * Get/set whether the Block prevents a given action - * @param $action String - * @param $x Bool - * @return Bool + * @param string $action + * @param bool $x + * @return bool */ public function prevents( $action, $x = null ) { switch ( $action ) { @@ -932,7 +944,7 @@ class Block { /** * Get the block name, but with autoblocked IPs hidden as per standard privacy policy - * @return String, text is escaped + * @return string Text is escaped */ public function getRedactedName() { if ( $this->mAuto ) { @@ -947,37 +959,10 @@ class Block { } /** - * Encode expiry for DB - * - * @param string $expiry timestamp for expiry, or - * @param $db DatabaseBase object - * @return String - * @deprecated since 1.18; use $dbw->encodeExpiry() instead - */ - public static function encodeExpiry( $expiry, $db ) { - wfDeprecated( __METHOD__, '1.18' ); - return $db->encodeExpiry( $expiry ); - } - - /** - * Decode expiry which has come from the DB - * - * @param string $expiry Database expiry format - * @param int $timestampType Requested timestamp format - * @return String - * @deprecated since 1.18; use $wgLang->formatExpiry() instead - */ - public static function decodeExpiry( $expiry, $timestampType = TS_MW ) { - wfDeprecated( __METHOD__, '1.18' ); - global $wgContLang; - return $wgContLang->formatExpiry( $expiry, $timestampType ); - } - - /** * Get a timestamp of the expiry for autoblocks * - * @param $timestamp String|Int - * @return String + * @param string|int $timestamp + * @return string */ public static function getAutoblockExpiry( $timestamp ) { global $wgAutoblockExpiry; @@ -986,18 +971,6 @@ class Block { } /** - * Gets rid of unneeded numbers in quad-dotted/octet IP strings - * For example, 127.111.113.151/24 -> 127.111.113.0/24 - * @param string $range IP address to normalize - * @return string - * @deprecated since 1.18, call IP::sanitizeRange() directly - */ - public static function normaliseRange( $range ) { - wfDeprecated( __METHOD__, '1.18' ); - return IP::sanitizeRange( $range ); - } - - /** * Purge expired blocks from the ipblocks table */ public static function purgeExpired() { @@ -1007,38 +980,15 @@ class Block { $method = __METHOD__; $dbw = wfGetDB( DB_MASTER ); - $dbw->onTransactionIdle( function() use ( $dbw, $method ) { + $dbw->onTransactionIdle( function () use ( $dbw, $method ) { $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method ); } ); } /** - * Get a value to insert into expiry field of the database when infinite expiry - * is desired - * @deprecated since 1.18, call $dbr->getInfinity() directly - * @return String - */ - public static function infinity() { - wfDeprecated( __METHOD__, '1.18' ); - return wfGetDB( DB_SLAVE )->getInfinity(); - } - - /** - * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute - * ("24 May 2034"), into an absolute timestamp we can put into the database. - * @param string $expiry whatever was typed into the form - * @return String: timestamp or "infinity" string for th DB implementation - * @deprecated since 1.18 moved to SpecialBlock::parseExpiryInput() - */ - public static function parseExpiryInput( $expiry ) { - wfDeprecated( __METHOD__, '1.18' ); - return SpecialBlock::parseExpiryInput( $expiry ); - } - - /** * Given a target and the target's type, get an existing Block object if possible. - * @param $specificTarget String|User|Int a block target, which may be one of several types: + * @param string|User|int $specificTarget A block target, which may be one of several types: * * A user to block, in which case $target will be a User * * An IP to block, in which case $target will be a User generated by using * User::newFromName( $ip, false ) to turn off name validation @@ -1048,10 +998,10 @@ class Block { * Calling this with a user, IP address or range will not select autoblocks, and will * only select a block where the targets match exactly (so looking for blocks on * 1.2.3.4 will not select 1.2.0.0/16 or even 1.2.3.4/32) - * @param $vagueTarget String|User|Int as above, but we will search for *any* block which + * @param string|User|int $vagueTarget As above, but we will search for *any* block which * affects that target (so for an IP address, get ranges containing that IP; and also * get any relevant autoblocks). Leave empty or blank to skip IP-based lookups. - * @param bool $fromMaster whether to use the DB_MASTER database + * @param bool $fromMaster Whether to use the DB_MASTER database * @return Block|null (null if no relevant block could be found). The target and type * of the returned Block will refer to the actual block which was found, which might * not be the same as the target you gave if you used $vagueTarget! @@ -1068,7 +1018,10 @@ class Block { # passed by some callers (bug 29116) return null; - } elseif ( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) { + } elseif ( in_array( + $type, + array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) + ) { $block = new Block(); $block->fromMaster( $fromMaster ); @@ -1083,15 +1036,14 @@ class Block { return null; } - /** * Get all blocks that match any IP from an array of IP addresses * - * @param Array $ipChain list of IPs (strings), usually retrieved from the + * @param array $ipChain List of IPs (strings), usually retrieved from the * X-Forwarded-For header of the request - * @param Bool $isAnon Exclude anonymous-only blocks if false - * @param Bool $fromMaster Whether to query the master or slave database - * @return Array of Blocks + * @param bool $isAnon Exclude anonymous-only blocks if false + * @param bool $fromMaster Whether to query the master or slave database + * @return array Array of Blocks * @since 1.22 */ public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) { @@ -1111,7 +1063,7 @@ class Block { continue; } # Don't check trusted IPs (includes local squids which will be in every request) - if ( wfIsTrustedProxy( $ipaddr ) ) { + if ( IP::isTrustedProxy( $ipaddr ) ) { continue; } # Check both the original IP (to check against single blocks), as well as build @@ -1165,13 +1117,13 @@ class Block { * - Other softblocks are chosen over autoblocks * - If there are multiple exact or range blocks at the same level, the one chosen * is random - - * @param Array $ipChain list of IPs (strings). This is used to determine how "close" + * + * @param array $blocks Array of blocks + * @param array $ipChain List of IPs (strings). This is used to determine how "close" * a block is to the server, and if a block matches exactly, or is in a range. * The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2, * local-squid, ...) - * @param Array $block Array of blocks - * @return Block|null the "best" block from the list + * @return Block|null The "best" block from the list */ public static function chooseBlock( array $blocks, array $ipChain ) { if ( !count( $blocks ) ) { @@ -1184,7 +1136,7 @@ class Block { // Sort hard blocks before soft ones and secondarily sort blocks // that disable account creation before those that don't. - usort( $blocks, function( Block $a, Block $b ) { + usort( $blocks, function ( Block $a, Block $b ) { $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' ); $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' ); return strcmp( $bWeight, $aWeight ); // highest weight first @@ -1275,7 +1227,7 @@ class Block { * as a string; for User objects this will return User::__toString() * which in turn gives User::getName(). * - * @param $target String|Int|User|null + * @param string|int|User|null $target * @return array( User|String|null, Block::TYPE_ constant|null ) */ public static function parseTarget( $target ) { @@ -1332,7 +1284,7 @@ class Block { /** * Get the type of target for this particular block - * @return Block::TYPE_ constant, will never be TYPE_ID + * @return int Block::TYPE_ constant, will never be TYPE_ID */ public function getType() { return $this->mAuto @@ -1355,7 +1307,7 @@ class Block { * Get the target for this particular Block. Note that for autoblocks, * this returns the unredacted name; frontend functions need to call $block->getRedactedName() * in this situation. - * @return User|String + * @return User|string */ public function getTarget() { return $this->target; @@ -1364,7 +1316,7 @@ class Block { /** * @since 1.19 * - * @return Mixed|string + * @return mixed|string */ public function getExpiry() { return $this->mExpiry; @@ -1372,7 +1324,7 @@ class Block { /** * Set the target for this block, and update $this->type accordingly - * @param $target Mixed + * @param mixed $target */ public function setTarget( $target ) { list( $this->target, $this->type ) = self::parseTarget( $target ); @@ -1388,7 +1340,7 @@ class Block { /** * Set the user who implemented (or will implement) this block - * @param $user User|string Local User object or username string for foreign users + * @param User|string $user Local User object or username string for foreign users */ public function setBlocker( $user ) { $this->blocker = $user; @@ -1429,7 +1381,7 @@ class Block { $this->getId(), $lang->formatExpiry( $this->mExpiry ), (string)$intended, - $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ), + $lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ), ); } } diff --git a/includes/Category.php b/includes/Category.php index 126b8fee..322b0530 100644 --- a/includes/Category.php +++ b/includes/Category.php @@ -26,7 +26,7 @@ * like to refresh link counts, the objects will be appropriately reinitialized. * Member variables are lazy-initialized. * - * TODO: Move some stuff from CategoryPage.php to here, and use that. + * @todo Move some stuff from CategoryPage.php to here, and use that. */ class Category { /** Name of the category, normalized to DB-key form */ @@ -75,7 +75,8 @@ class Category { if ( !$row ) { # Okay, there were no contents. Nothing to initialize. if ( $this->mTitle ) { - # If there is a title object but no record in the category table, treat this as an empty category + # If there is a title object but no record in the category table, + # treat this as an empty category. $this->mID = false; $this->mName = $this->mTitle->getDBkey(); $this->mPages = 0; @@ -128,8 +129,8 @@ class Category { /** * Factory function. * - * @param $title Title for the category page - * @return Category|bool on a totally invalid name + * @param Title $title Title for the category page + * @return Category|bool On a totally invalid name */ public static function newFromTitle( $title ) { $cat = new self(); @@ -143,7 +144,7 @@ class Category { /** * Factory function. * - * @param $id Integer: a category id + * @param int $id A category id * @return Category */ public static function newFromID( $id ) { @@ -155,11 +156,13 @@ class Category { /** * Factory function, for constructing a Category object from a result set * - * @param $row result set row, must contain the cat_xxx fields. If the fields are null, - * the resulting Category object will represent an empty category if a title object - * was given. If the fields are null and no title was given, this method fails and returns false. - * @param Title $title optional title object for the category represented by the given row. - * May be provided if it is already known, to avoid having to re-create a title object later. + * @param object $row Result set row, must contain the cat_xxx fields. If the + * fields are null, the resulting Category object will represent an empty + * category if a title object was given. If the fields are null and no + * title was given, this method fails and returns false. + * @param Title $title Optional title object for the category represented by + * the given row. May be provided if it is already known, to avoid having + * to re-create a title object later. * @return Category */ public static function newFromRow( $row, $title = null ) { @@ -177,7 +180,8 @@ class Category { # but we can't know that here... return false; } else { - $cat->mName = $title->getDBkey(); # if we have a title object, fetch the category name from there + # if we have a title object, fetch the category name from there + $cat->mName = $title->getDBkey(); } $cat->mID = false; @@ -195,27 +199,37 @@ class Category { return $cat; } - /** @return mixed DB key name, or false on failure */ + /** + * @return mixed DB key name, or false on failure + */ public function getName() { return $this->getX( 'mName' ); } - /** @return mixed Category ID, or false on failure */ + /** + * @return mixed Category ID, or false on failure + */ public function getID() { return $this->getX( 'mID' ); } - /** @return mixed Total number of member pages, or false on failure */ + /** + * @return mixed Total number of member pages, or false on failure + */ public function getPageCount() { return $this->getX( 'mPages' ); } - /** @return mixed Number of subcategories, or false on failure */ + /** + * @return mixed Number of subcategories, or false on failure + */ public function getSubcatCount() { return $this->getX( 'mSubcats' ); } - /** @return mixed Number of member files, or false on failure */ + /** + * @return mixed Number of member files, or false on failure + */ public function getFileCount() { return $this->getX( 'mFiles' ); } @@ -239,9 +253,9 @@ class Category { /** * Fetch a TitleArray of up to $limit category members, beginning after the * category sort key $offset. - * @param $limit integer - * @param $offset string - * @return TitleArray object for category members. + * @param int $limit + * @param string $offset + * @return TitleArray TitleArray object for category members. */ public function getMembers( $limit = false, $offset = '' ) { wfProfileIn( __METHOD__ ); @@ -277,6 +291,7 @@ class Category { /** * Generic accessor + * @param string $key * @return bool */ private function getX( $key ) { @@ -306,7 +321,7 @@ class Category { wfProfileIn( __METHOD__ ); $dbw = wfGetDB( DB_MASTER ); - $dbw->begin( __METHOD__ ); + $dbw->startAtomic( __METHOD__ ); # Insert the row if it doesn't exist yet (e.g., this is being run via # update.php from a pre-1.16 schema). TODO: This will cause lots and @@ -346,7 +361,7 @@ class Category { array( 'cat_title' => $this->mName ), __METHOD__ ); - $dbw->commit( __METHOD__ ); + $dbw->endAtomic( __METHOD__ ); wfProfileOut( __METHOD__ ); diff --git a/includes/Categoryfinder.php b/includes/CategoryFinder.php index 6ef224b6..cf537e15 100644 --- a/includes/Categoryfinder.php +++ b/includes/CategoryFinder.php @@ -21,7 +21,7 @@ */ /** - * The "Categoryfinder" class takes a list of articles, creates an internal + * The "CategoryFinder" class takes a list of articles, creates an internal * representation of all their parent categories (as well as parents of * parents etc.). From this representation, it determines which of these * articles are in one or all of a given subset of categories. @@ -31,7 +31,7 @@ * # Determines whether the article with the page_id 12345 is in both * # "Category 1" and "Category 2" or their subcategories, respectively * - * $cf = new Categoryfinder; + * $cf = new CategoryFinder; * $cf->seed( * array( 12345 ), * array( 'Category 1', 'Category 2' ), @@ -42,36 +42,41 @@ * </code> * */ -class Categoryfinder { - var $articles = array(); # The original article IDs passed to the seed function - var $deadend = array(); # Array of DBKEY category names for categories that don't have a page - var $parents = array(); # Array of [ID => array()] - var $next = array(); # Array of article/category IDs - var $targets = array(); # Array of DBKEY category names - var $name2id = array(); - var $mode; # "AND" or "OR" +class CategoryFinder { + /** @var int[] The original article IDs passed to the seed function */ + protected $articles = array(); - /** - * @var DatabaseBase - */ - var $dbr; # Read-DB slave + /** @var array Array of DBKEY category names for categories that don't have a page */ + protected $deadend = array(); - /** - * Constructor (currently empty). - */ - function __construct() { - } + /** @var array Array of [ID => array()] */ + protected $parents = array(); + + /** @var array Array of article/category IDs */ + protected $next = array(); + + /** @var array Array of DBKEY category names */ + protected $targets = array(); + + /** @var array */ + protected $name2id = array(); + + /** @var string "AND" or "OR" */ + protected $mode; + + /** @var DatabaseBase Read-DB slave */ + protected $dbr; /** * Initializes the instance. Do this prior to calling run(). - * @param $article_ids Array of article IDs - * @param $categories FIXME + * @param array $articleIds Array of article IDs + * @param array $categories FIXME * @param string $mode FIXME, default 'AND'. * @todo FIXME: $categories/$mode */ - function seed( $article_ids, $categories, $mode = 'AND' ) { - $this->articles = $article_ids; - $this->next = $article_ids; + public function seed( $articleIds, $categories, $mode = 'AND' ) { + $this->articles = $articleIds; + $this->next = $articleIds; $this->mode = $mode; # Set the list of target categories; convert them to DBKEY form first @@ -88,12 +93,12 @@ class Categoryfinder { /** * Iterates through the parent tree starting with the seed values, * then checks the articles if they match the conditions - * @return array of page_ids (those given to seed() that match the conditions) + * @return array Array of page_ids (those given to seed() that match the conditions) */ - function run() { + public function run() { $this->dbr = wfGetDB( DB_SLAVE ); while ( count( $this->next ) > 0 ) { - $this->scan_next_layer(); + $this->scanNextLayer(); } # Now check if this applies to the individual articles @@ -110,13 +115,21 @@ class Categoryfinder { } /** + * Get the parents. Only really useful if run() has been called already + * @return array + */ + public function getParents() { + return $this->parents; + } + + /** * This functions recurses through the parent representation, trying to match the conditions * @param int $id The article/category to check * @param array $conds The array of categories to match - * @param array $path used to check for recursion loops + * @param array $path Used to check for recursion loops * @return bool Does this match the conditions? */ - function check( $id, &$conds, $path = array() ) { + private function check( $id, &$conds, $path = array() ) { // Check for loops and stop! if ( in_array( $id, $path ) ) { return false; @@ -171,8 +184,8 @@ class Categoryfinder { /** * Scans a "parent layer" of the articles/categories in $this->next */ - function scan_next_layer() { - wfProfileIn( __METHOD__ ); + private function scanNextLayer() { + $profiler = new ProfileSection( __METHOD__ ); # Find all parents of the article currently in $this->next $layer = array(); @@ -227,7 +240,5 @@ class Categoryfinder { foreach ( $layer as $v ) { $this->deadend[$v] = $v; } - - wfProfileOut( __METHOD__ ); } } diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index 55d9c1e5..7581ae40 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -21,68 +21,77 @@ */ class CategoryViewer extends ContextSource { - var $limit, $from, $until, - $articles, $articles_start_char, - $children, $children_start_char, - $showGallery, $imgsNoGalley, - $imgsNoGallery_start_char, - $imgsNoGallery; + /** @var int */ + public $limit; - /** - * @var Array - */ - var $nextPage; + /** @var array */ + public $from; - /** - * @var Array - */ - var $flip; + /** @var array */ + public $until; - /** - * @var Title - */ - var $title; + /** @var string[] */ + public $articles; - /** - * @var Collation - */ - var $collation; + /** @var array */ + public $articles_start_char; - /** - * @var ImageGallery - */ - var $gallery; + /** @var array */ + public $children; - /** - * Category object for this page - * @var Category - */ + /** @var array */ + public $children_start_char; + + /** @var bool */ + public $showGallery; + + /** @var array */ + public $imgsNoGallery_start_char; + + /** @var array */ + public $imgsNoGallery; + + /** @var array */ + public $nextPage; + + /** @var array */ + protected $prevPage; + + /** @var array */ + public $flip; + + /** @var Title */ + public $title; + + /** @var Collation */ + public $collation; + + /** @var ImageGallery */ + public $gallery; + + /** @var Category Category object for this page. */ private $cat; - /** - * The original query array, to be used in generating paging links. - * @var array - */ + /** @var array The original query array, to be used in generating paging links. */ private $query; /** - * Constructor - * * @since 1.19 $context is a second, required parameter - * @param $title Title - * @param $context IContextSource + * @param Title $title + * @param IContextSource $context * @param array $from An array with keys page, subcat, * and file for offset of results of each section (since 1.17) * @param array $until An array with 3 keys for until of each section (since 1.17) - * @param $query Array + * @param array $query */ - function __construct( $title, IContextSource $context, $from = array(), $until = array(), $query = array() ) { - global $wgCategoryPagingLimit; + function __construct( $title, IContextSource $context, $from = array(), + $until = array(), $query = array() + ) { $this->title = $title; $this->setContext( $context ); $this->from = $from; $this->until = $until; - $this->limit = $wgCategoryPagingLimit; + $this->limit = $context->getConfig()->get( 'CategoryPagingLimit' ); $this->cat = Category::newFromTitle( $title ); $this->query = $query; $this->collation = Collation::singleton(); @@ -95,10 +104,10 @@ class CategoryViewer extends ContextSource { * @return string HTML output */ public function getHTML() { - global $wgCategoryMagicGallery; wfProfileIn( __METHOD__ ); - $this->showGallery = $wgCategoryMagicGallery && !$this->getOutput()->mNoGallery; + $this->showGallery = $this->getConfig()->get( 'CategoryMagicGallery' ) + && !$this->getOutput()->mNoGallery; $this->clearCategoryState(); $this->doCategoryQuery(); @@ -144,14 +153,13 @@ class CategoryViewer extends ContextSource { // Note that null for mode is taken to mean use default. $mode = $this->getRequest()->getVal( 'gallerymode', null ); try { - $this->gallery = ImageGalleryBase::factory( $mode ); + $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() ); } catch ( MWException $e ) { // User specified something invalid, fallback to default. - $this->gallery = ImageGalleryBase::factory(); + $this->gallery = ImageGalleryBase::factory( false, $this->getContext() ); } $this->gallery->setHideBadImages(); - $this->gallery->setContext( $this->getContext() ); } else { $this->imgsNoGallery = array(); $this->imgsNoGallery_start_char = array(); @@ -160,9 +168,9 @@ class CategoryViewer extends ContextSource { /** * Add a subcategory to the internal lists, using a Category object - * @param $cat Category - * @param $sortkey - * @param $pageLength + * @param Category $cat + * @param string $sortkey + * @param int $pageLength */ function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) { // Subcategory; strip the 'Category' namespace from the link text. @@ -182,15 +190,6 @@ class CategoryViewer extends ContextSource { } /** - * Add a subcategory to the internal lists, using a title object - * @deprecated since 1.17 kept for compatibility, use addSubcategoryObject instead - */ - function addSubcategory( Title $title, $sortkey, $pageLength ) { - wfDeprecated( __METHOD__, '1.17' ); - $this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength ); - } - - /** * Get the character to be used for sorting subcategories. * If there's a link from Category:A to Category:B, the sortkey of the resulting * entry in the categorylinks table is Category:A, not A, which it SHOULD be. @@ -217,10 +216,10 @@ class CategoryViewer extends ContextSource { /** * Add a page in the image namespace - * @param $title Title - * @param $sortkey - * @param $pageLength - * @param $isRedirect bool + * @param Title $title + * @param string $sortkey + * @param int $pageLength + * @param bool $isRedirect */ function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) { global $wgContLang; @@ -247,10 +246,10 @@ class CategoryViewer extends ContextSource { /** * Add a miscellaneous page - * @param $title - * @param $sortkey - * @param $pageLength - * @param $isRedirect bool + * @param Title $title + * @param string $sortkey + * @param int $pageLength + * @param bool $isRedirect */ function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) { global $wgContLang; @@ -290,6 +289,12 @@ class CategoryViewer extends ContextSource { 'subcat' => null, 'file' => null, ); + $this->prevPage = array( + 'page' => null, + 'subcat' => null, + 'file' => null, + ); + $this->flip = array( 'page' => false, 'subcat' => false, 'file' => false ); foreach ( array( 'page', 'subcat', 'file' ) as $type ) { @@ -346,6 +351,9 @@ class CategoryViewer extends ContextSource { $this->nextPage[$type] = $humanSortkey; break; } + if ( $count == $this->limit ) { + $this->prevPage[$type] = $humanSortkey; + } if ( $title->getNamespace() == NS_CATEGORY ) { $cat = Category::newFromRow( $row, $title ); @@ -432,7 +440,12 @@ class CategoryViewer extends ContextSource { $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' ); $r .= "<div id=\"mw-category-media\">\n"; - $r .= '<h2>' . $this->msg( 'category-media-header', wfEscapeWikiText( $this->title->getText() ) )->text() . "</h2>\n"; + $r .= '<h2>' . + $this->msg( + 'category-media-header', + wfEscapeWikiText( $this->title->getText() ) + )->text() . + "</h2>\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'file' ); if ( $this->showGallery ) { @@ -451,12 +464,24 @@ class CategoryViewer extends ContextSource { * of the output. * * @param string $type 'page', 'subcat', or 'file' - * @return String: HTML output, possibly empty if there are no other pages + * @return string HTML output, possibly empty if there are no other pages */ private function getSectionPagingLinks( $type ) { if ( isset( $this->until[$type] ) && $this->until[$type] !== null ) { - return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type ); - } elseif ( $this->nextPage[$type] !== null || ( isset( $this->from[$type] ) && $this->from[$type] !== null ) ) { + // The new value for the until parameter should be pointing to the first + // result displayed on the page which is the second last result retrieved + // from the database.The next link should have a from parameter pointing + // to the until parameter of the current page. + if ( $this->nextPage[$type] !== null ) { + return $this->pagingLinks( $this->prevPage[$type], $this->until[$type], $type ); + } else { + // If the nextPage variable is null, it means that we have reached the first page + // and therefore the previous link should be disabled. + return $this->pagingLinks( null, $this->until[$type], $type ); + } + } elseif ( $this->nextPage[$type] !== null + || ( isset( $this->from[$type] ) && $this->from[$type] !== null ) + ) { return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type ); } else { return ''; @@ -474,10 +499,10 @@ class CategoryViewer extends ContextSource { * Format a list of articles chunked by letter, either as a * bullet list or a columnar format, depending on the length. * - * @param $articles Array - * @param $articles_start_char Array - * @param $cutoff Int - * @return String + * @param array $articles + * @param array $articles_start_char + * @param int $cutoff + * @return string * @private */ function formatList( $articles, $articles_start_char, $cutoff = 6 ) { @@ -507,9 +532,9 @@ class CategoryViewer extends ContextSource { * More distant TODO: Scrap this and use CSS columns, whenever IE finally * supports those. * - * @param $articles Array - * @param $articles_start_char Array - * @return String + * @param array $articles + * @param string[] $articles_start_char + * @return string * @private */ static function columnList( $articles, $articles_start_char ) { @@ -563,15 +588,16 @@ class CategoryViewer extends ContextSource { /** * Format a list of articles chunked by letter in a bullet list. - * @param $articles Array - * @param $articles_start_char Array - * @return String + * @param array $articles + * @param string[] $articles_start_char + * @return string * @private */ static function shortList( $articles, $articles_start_char ) { $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n"; $r .= '<ul><li>' . $articles[0] . '</li>'; - for ( $index = 1; $index < count( $articles ); $index++ ) { + $articleCount = count( $articles ); + for ( $index = 1; $index < $articleCount; $index++ ) { if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) { $r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>"; } @@ -589,7 +615,7 @@ class CategoryViewer extends ContextSource { * @param string $last The 'from' parameter for the generated URL * @param string $type A prefix for parameters, 'page' or 'subcat' or * 'file' - * @return String HTML + * @return string HTML */ private function pagingLinks( $first, $last, $type = '' ) { $prevLink = $this->msg( 'prevn' )->numParams( $this->limit )->escaped(); @@ -627,8 +653,8 @@ class CategoryViewer extends ContextSource { * Takes a title, and adds the fragment identifier that * corresponds to the correct segment of the category. * - * @param Title $title: The title (usually $this->title) - * @param string $section: Which section + * @param Title $title The title (usually $this->title) + * @param string $section Which section * @throws MWException * @return Title */ @@ -660,7 +686,7 @@ class CategoryViewer extends ContextSource { * @param int $rescnt The number of items returned by our database query. * @param int $dbcnt The number of items according to the category table. * @param string $type 'subcat', 'article', or 'file' - * @return string: A message giving the number of items, to output to HTML. + * @return string A message giving the number of items, to output to HTML. */ private function getCountMessage( $rescnt, $dbcnt, $type ) { // There are three cases: @@ -701,7 +727,10 @@ class CategoryViewer extends ContextSource { // to refresh the incorrect category table entry -- which should be // quick due to the small number of entries. $totalcnt = $rescnt; - $this->cat->refreshCounts(); + $category = $this->cat; + wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $category ) { + $category->refreshCounts(); + } ); } else { // Case 3: hopeless. Don't give a total count at all. // Messages: category-subcat-count-limited, category-article-count-limited, diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php index 3fc27f9a..94b7b7a9 100644 --- a/includes/ChangeTags.php +++ b/includes/ChangeTags.php @@ -21,18 +21,15 @@ */ class ChangeTags { - /** * Creates HTML for the given tags * * @param string $tags Comma-separated list of tags * @param string $page A label for the type of action which is being displayed, - * for example: 'history', 'contributions' or 'newpages' - * - * @return Array with two items: (html, classes) - * - html: String: HTML for displaying the tags (empty string when param $tags is empty) - * - classes: Array of strings: CSS classes used in the generated html, one class for each tag - * + * for example: 'history', 'contributions' or 'newpages' + * @return array Array with two items: (html, classes) + * - html: String: HTML for displaying the tags (empty string when param $tags is empty) + * - classes: Array of strings: CSS classes used in the generated html, one class for each tag */ public static function formatSummaryRow( $tags, $page ) { global $wgLang; @@ -66,10 +63,10 @@ class ChangeTags { /** * Get a short description for a tag * - * @param string $tag tag + * @param string $tag Tag * - * @return String: Short description of the tag from "mediawiki:tag-$tag" if this message exists, - * html-escaped version of $tag otherwise + * @return string Short description of the tag from "mediawiki:tag-$tag" if this message exists, + * html-escaped version of $tag otherwise */ public static function tagDescription( $tag ) { $msg = wfMessage( "tag-$tag" ); @@ -80,17 +77,19 @@ class ChangeTags { * Add tags to a change given its rc_id, rev_id and/or log_id * * @param string|array $tags Tags to add to the change - * @param $rc_id int: rc_id of the change to add the tags to - * @param $rev_id int: rev_id of the change to add the tags to - * @param $log_id int: log_id of the change to add the tags to - * @param string $params params to put in the ct_params field of table 'change_tag' + * @param int|null $rc_id The rc_id of the change to add the tags to + * @param int|null $rev_id The rev_id of the change to add the tags to + * @param int|null $log_id The log_id of the change to add the tags to + * @param string $params Params to put in the ct_params field of table 'change_tag' * * @throws MWException - * @return bool: false if no changes are made, otherwise true + * @return bool False if no changes are made, otherwise true * - * @exception MWException when $rc_id, $rev_id and $log_id are all null + * @exception MWException When $rc_id, $rev_id and $log_id are all null */ - public static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) { + public static function addTags( $tags, $rc_id = null, $rev_id = null, + $log_id = null, $params = null + ) { if ( !is_array( $tags ) ) { $tags = array( $tags ); } @@ -102,26 +101,52 @@ class ChangeTags { 'specified when adding a tag to a change!' ); } - $dbr = wfGetDB( DB_SLAVE ); + $dbw = wfGetDB( DB_MASTER ); // Might as well look for rcids and so on. if ( !$rc_id ) { - $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. + // Info might be out of date, somewhat fractionally, on slave. if ( $log_id ) { - $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ ); + $rc_id = $dbw->selectField( + 'recentchanges', + 'rc_id', + array( 'rc_logid' => $log_id ), + __METHOD__ + ); } elseif ( $rev_id ) { - $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ ); + $rc_id = $dbw->selectField( + 'recentchanges', + 'rc_id', + array( 'rc_this_oldid' => $rev_id ), + __METHOD__ + ); } } elseif ( !$log_id && !$rev_id ) { - $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. - $log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ ); - $rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ ); + // Info might be out of date, somewhat fractionally, on slave. + $log_id = $dbw->selectField( + 'recentchanges', + 'rc_logid', + array( 'rc_id' => $rc_id ), + __METHOD__ + ); + $rev_id = $dbw->selectField( + 'recentchanges', + 'rc_this_oldid', + array( 'rc_id' => $rc_id ), + __METHOD__ + ); } - $tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, 'ts_log_id' => $log_id ) ); + $tsConds = array_filter( array( + 'ts_rc_id' => $rc_id, + 'ts_rev_id' => $rev_id, + 'ts_log_id' => $log_id ) + ); - ## Update the summary row. - $prevTags = $dbr->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ ); + // Update the summary row. + // $prevTags can be out of date on slaves, especially when addTags is called consecutively, + // causing loss of tags added recently in tag_summary table. + $prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ ); $prevTags = $prevTags ? $prevTags : ''; $prevTags = array_filter( explode( ',', $prevTags ) ); $newTags = array_unique( array_merge( $prevTags, $tags ) ); @@ -133,7 +158,6 @@ class ChangeTags { return false; } - $dbw = wfGetDB( DB_MASTER ); $dbw->replace( 'tag_summary', array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ), @@ -167,9 +191,9 @@ class ChangeTags { * * @param string|array $tables Table names, see DatabaseBase::select * @param string|array $fields Fields used in query, see DatabaseBase::select - * @param string|array $conds conditions used in query, see DatabaseBase::select - * @param $join_conds Array: join conditions, see DatabaseBase::select - * @param array $options options, see Database::select + * @param string|array $conds Conditions used in query, see DatabaseBase::select + * @param array $join_conds Join conditions, see DatabaseBase::select + * @param array $options Options, see Database::select * @param bool|string $filter_tag Tag to select on * * @throws MWException When unable to determine appropriate JOIN condition for tagging @@ -184,29 +208,27 @@ class ChangeTags { // Figure out which conditions can be done. if ( in_array( 'recentchanges', $tables ) ) { - $join_cond = 'rc_id'; + $join_cond = 'ct_rc_id=rc_id'; } elseif ( in_array( 'logging', $tables ) ) { - $join_cond = 'log_id'; + $join_cond = 'ct_log_id=log_id'; } elseif ( in_array( 'revision', $tables ) ) { - $join_cond = 'rev_id'; + $join_cond = 'ct_rev_id=rev_id'; + } elseif ( in_array( 'archive', $tables ) ) { + $join_cond = 'ct_rev_id=ar_rev_id'; } else { throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' ); } - // JOIN on tag_summary - $tables[] = 'tag_summary'; - $join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" ); - $fields[] = 'ts_tags'; + $fields['ts_tags'] = wfGetDB( DB_SLAVE )->buildGroupConcatField( + ',', 'change_tag', 'ct_tag', $join_cond + ); if ( $wgUseTagFilter && $filter_tag ) { // Somebody wants to filter on a tag. // Add an INNER JOIN on change_tag - // FORCE INDEX -- change_tags will almost ALWAYS be the correct query plan. - $options['USE INDEX'] = array( 'change_tag' => 'change_tag_tag_id' ); - unset( $options['FORCE INDEX'] ); $tables[] = 'change_tag'; - $join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" ); + $join_conds['change_tag'] = array( 'INNER JOIN', $join_cond ); $conds['ct_tag'] = $filter_tag; } } @@ -214,34 +236,55 @@ class ChangeTags { /** * Build a text box to select a change tag * - * @param string $selected tag to select by default - * @param $fullForm Boolean: + * @param string $selected Tag to select by default + * @param bool $fullForm * - if false, then it returns an array of (label, form). * - if true, it returns an entire form around the selector. - * @param $title Title object to send the form to. + * @param Title $title Title object to send the form to. * Used when, and only when $fullForm is true. - * @return String or array: + * @return string|array * - if $fullForm is false: Array with * - if $fullForm is true: String, html fragment */ - public static function buildTagFilterSelector( $selected = '', $fullForm = false, Title $title = null ) { + public static function buildTagFilterSelector( $selected = '', + $fullForm = false, Title $title = null + ) { global $wgUseTagFilter; if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) ) { return $fullForm ? '' : array(); } - $data = array( Html::rawElement( 'label', array( 'for' => 'tagfilter' ), wfMessage( 'tag-filter' )->parse() ), - Xml::input( 'tagfilter', 20, $selected, array( 'class' => 'mw-tagfilter-input' ) ) ); + $data = array( + Html::rawElement( + 'label', + array( 'for' => 'tagfilter' ), + wfMessage( 'tag-filter' )->parse() + ), + Xml::input( + 'tagfilter', + 20, + $selected, + array( 'class' => 'mw-tagfilter-input', 'id' => 'tagfilter' ) + ) + ); if ( !$fullForm ) { return $data; } $html = implode( ' ', $data ); - $html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMessage( 'tag-filter-submit' )->text() ) ); + $html .= "\n" . + Xml::element( + 'input', + array( 'type' => 'submit', 'value' => wfMessage( 'tag-filter-submit' )->text() ) + ); $html .= "\n" . Html::hidden( 'title', $title->getPrefixedText() ); - $html = Xml::tags( 'form', array( 'action' => $title->getLocalURL(), 'class' => 'mw-tagfilter-form', 'method' => 'get' ), $html ); + $html = Xml::tags( + 'form', + array( 'action' => $title->getLocalURL(), 'class' => 'mw-tagfilter-form', 'method' => 'get' ), + $html + ); return $html; } @@ -253,7 +296,7 @@ class ChangeTags { * * Tries memcached first. * - * @return Array of strings: tags + * @return string[] Array of strings: tags */ public static function listDefinedTags() { // Caching... diff --git a/includes/Collation.php b/includes/Collation.php index b0252c70..1c2c2db3 100644 --- a/includes/Collation.php +++ b/includes/Collation.php @@ -21,7 +21,7 @@ */ abstract class Collation { - static $instance; + private static $instance; /** * @return Collation @@ -36,7 +36,7 @@ abstract class Collation { /** * @throws MWException - * @param $collationName string + * @param string $collationName * @return Collation */ static function factory( $collationName ) { @@ -47,6 +47,10 @@ abstract class Collation { return new IdentityCollation; case 'uca-default': return new IcuCollation( 'root' ); + case 'xx-uca-ckb': + return new CollationCkb; + case 'xx-uca-et': + return new CollationEt; default: $match = array(); if ( preg_match( '/^uca-([a-z@=-]+)$/', $collationName, $match ) ) { @@ -106,7 +110,8 @@ abstract class Collation { } class UppercaseCollation extends Collation { - var $lang; + private $lang; + function __construct() { // Get a language object so that we can use the generic UTF-8 uppercase // function there @@ -149,10 +154,22 @@ class IdentityCollation extends Collation { } class IcuCollation extends Collation { - const FIRST_LETTER_VERSION = 1; + const FIRST_LETTER_VERSION = 2; + + /** @var Collator */ + private $primaryCollator; + + /** @var Collator */ + private $mainCollator; + + /** @var string */ + private $locale; - var $primaryCollator, $mainCollator, $locale; - var $firstLetterData; + /** @var Language */ + protected $digitTransformLanguage; + + /** @var array */ + private $firstLetterData; /** * Unified CJK blocks. @@ -163,7 +180,7 @@ class IcuCollation extends Collation { * is pretty useless for sorting Chinese text anyway. Japanese and Korean * blocks are not included here, because they are smaller and more useful. */ - static $cjkBlocks = array( + private static $cjkBlocks = array( array( 0x2E80, 0x2EFF ), // CJK Radicals Supplement array( 0x2F00, 0x2FDF ), // Kangxi Radicals array( 0x2FF0, 0x2FFF ), // Ideographic Description Characters @@ -202,14 +219,19 @@ class IcuCollation extends Collation { * Empty arrays are intended; this signifies that the data for the language is * available and that there are, in fact, no additional letters to consider. */ - static $tailoringFirstLetters = array( + private static $tailoringFirstLetters = array( // Verified by native speakers 'be' => array( "Ё" ), 'be-tarask' => array( "Ё" ), + 'cy' => array( "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ), 'en' => array(), + 'fa' => array( "آ", "ء", "ه" ), 'fi' => array( "Å", "Ä", "Ö" ), + 'fr' => array(), 'hu' => array( "Cs", "Dz", "Dzs", "Gy", "Ly", "Ny", "Ö", "Sz", "Ty", "Ü", "Zs" ), + 'is' => array( "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ), 'it' => array(), + 'lv' => array( "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ), 'pl' => array( "Ą", "Ć", "Ę", "Ł", "Ń", "Ó", "Ś", "Ź", "Ż" ), 'pt' => array(), 'ru' => array(), @@ -227,18 +249,15 @@ class IcuCollation extends Collation { 'ca' => array(), 'co' => array(), 'cs' => array( "Č", "Ch", "Ř", "Š", "Ž" ), - 'cy' => array( "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ), 'da' => array( "Æ", "Ø", "Å" ), 'de' => array(), 'dsb' => array( "Č", "Ć", "Dź", "Ě", "Ch", "Ł", "Ń", "Ŕ", "Š", "Ś", "Ž", "Ź" ), 'el' => array(), 'eo' => array( "Ĉ", "Ĝ", "Ĥ", "Ĵ", "Ŝ", "Ŭ" ), 'es' => array( "Ñ" ), - 'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ), + 'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü", "W" ), // added W for CollationEt (xx-uca-et) 'eu' => array( "Ñ" ), - 'fa' => array( "آ", "ء", "ه" ), 'fo' => array( "Á", "Ð", "Í", "Ó", "Ú", "Ý", "Æ", "Ø", "Å" ), - 'fr' => array(), 'fur' => array( "À", "Á", "Â", "È", "Ì", "Ò", "Ù" ), 'fy' => array(), 'ga' => array(), @@ -246,7 +265,6 @@ class IcuCollation extends Collation { 'gl' => array( "Ch", "Ll", "Ñ" ), 'hr' => array( "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ), 'hsb' => array( "Č", "Dź", "Ě", "Ch", "Ł", "Ń", "Ř", "Š", "Ć", "Ž" ), - 'is' => array( "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ), 'kk' => array( "Ү", "І" ), 'kl' => array( "Æ", "Ø", "Å" ), 'ku' => array( "Ç", "Ê", "Î", "Ş", "Û" ), @@ -254,7 +272,6 @@ class IcuCollation extends Collation { 'la' => array(), 'lb' => array(), 'lt' => array( "Č", "Š", "Ž" ), - 'lv' => array( "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ), 'mk' => array(), 'mo' => array( "Ă", "Â", "Î", "Ş", "Ţ" ), 'mt' => array( "Ċ", "Ġ", "Għ", "Ħ", "Ż" ), @@ -284,7 +301,12 @@ class IcuCollation extends Collation { throw new MWException( 'An ICU collation was requested, ' . 'but the intl extension is not available.' ); } + $this->locale = $locale; + // Drop everything after the '@' in locale's name + $localeParts = explode( '@', $locale ); + $this->digitTransformLanguage = Language::factory( $locale === 'root' ? 'en' : $localeParts[0] ); + $this->mainCollator = Collator::create( $locale ); if ( !$this->mainCollator ) { throw new MWException( "Invalid ICU locale specified for collation: $locale" ); @@ -319,16 +341,14 @@ class IcuCollation extends Collation { // Check for CJK $firstChar = mb_substr( $string, 0, 1, 'UTF-8' ); - if ( ord( $firstChar ) > 0x7f - && self::isCjk( utf8ToCodepoint( $firstChar ) ) ) - { + if ( ord( $firstChar ) > 0x7f && self::isCjk( utf8ToCodepoint( $firstChar ) ) ) { return $firstChar; } $sortKey = $this->getPrimarySortKey( $string ); // Do a binary search to find the correct letter to sort under - $min = $this->findLowerBound( + $min = ArrayUtils::findLowerBound( array( $this, 'getSortKeyByLetterIndex' ), $this->getFirstLetterCount(), 'strcmp', @@ -347,7 +367,12 @@ class IcuCollation extends Collation { } $cache = wfGetCache( CACHE_ANYTHING ); - $cacheKey = wfMemcKey( 'first-letters', $this->locale ); + $cacheKey = wfMemcKey( + 'first-letters', + $this->locale, + $this->digitTransformLanguage->getCode(), + self::getICUVersion() + ); $cacheEntry = $cache->get( $cacheKey ); if ( $cacheEntry && isset( $cacheEntry['version'] ) @@ -367,6 +392,12 @@ class IcuCollation extends Collation { if ( isset( self::$tailoringFirstLetters['-' . $this->locale] ) ) { $letters = array_diff( $letters, self::$tailoringFirstLetters['-' . $this->locale] ); } + // Apply digit transforms + $digits = array( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ); + $letters = array_diff( $letters, $digits ); + foreach ( $digits as $digit ) { + $letters[] = $this->digitTransformLanguage->formatNum( $digit, true ); + } } else { $letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" ); if ( $letters === false ) { @@ -459,7 +490,7 @@ class IcuCollation extends Collation { $prev = $trimmedKey; } foreach ( $duplicatePrefixes as $badKey ) { - wfDebug( "Removing '{$letterMap[$badKey]}' from first letters." ); + wfDebug( "Removing '{$letterMap[$badKey]}' from first letters.\n" ); unset( $letterMap[$badKey] ); // This code assumes that unsetting does not change sort order. } @@ -499,53 +530,6 @@ class IcuCollation extends Collation { return count( $this->firstLetterData['chars'] ); } - /** - * Do a binary search, and return the index of the largest item that sorts - * less than or equal to the target value. - * - * @param array $valueCallback A function to call to get the value with - * a given array index. - * @param int $valueCount The number of items accessible via $valueCallback, - * indexed from 0 to $valueCount - 1 - * @param array $comparisonCallback A callback to compare two values, returning - * -1, 0 or 1 in the style of strcmp(). - * @param string $target The target value to find. - * - * @return int|bool The item index of the lower bound, or false if the target value - * sorts before all items. - */ - function findLowerBound( $valueCallback, $valueCount, $comparisonCallback, $target ) { - if ( $valueCount === 0 ) { - return false; - } - - $min = 0; - $max = $valueCount; - do { - $mid = $min + ( ( $max - $min ) >> 1 ); - $item = call_user_func( $valueCallback, $mid ); - $comparison = call_user_func( $comparisonCallback, $target, $item ); - if ( $comparison > 0 ) { - $min = $mid; - } elseif ( $comparison == 0 ) { - $min = $mid; - break; - } else { - $max = $mid; - } - } while ( $min < $max - 1 ); - - if ( $min == 0 ) { - $item = call_user_func( $valueCallback, $min ); - $comparison = call_user_func( $comparisonCallback, $target, $item ); - if ( $comparison < 0 ) { - // Before the first item - return false; - } - } - return $min; - } - static function isCjk( $codepoint ) { foreach ( self::$cjkBlocks as $block ) { if ( $codepoint >= $block[0] && $codepoint <= $block[1] ) { @@ -565,7 +549,7 @@ class IcuCollation extends Collation { * This function will return false on older PHPs. * * @since 1.21 - * @return string|false + * @return string|bool */ static function getICUVersion() { return defined( 'INTL_ICU_VERSION' ) ? INTL_ICU_VERSION : false; @@ -576,7 +560,7 @@ class IcuCollation extends Collation { * currently in use, or false when it can't be determined. * * @since 1.21 - * @return string|false + * @return string|bool */ static function getUnicodeVersionForICU() { $icuVersion = IcuCollation::getICUVersion(); @@ -606,3 +590,56 @@ class IcuCollation extends Collation { } } } + +/** + * Workaround for the lack of support of Sorani Kurdish / Central Kurdish language ('ckb') in ICU. + * + * Uses the same collation rules as Persian / Farsi ('fa'), but different characters for digits. + */ +class CollationCkb extends IcuCollation { + function __construct() { + // This will set $locale and collators, which affect the actual sorting order + parent::__construct( 'fa' ); + // Override the 'fa' language set by parent constructor, which affects #getFirstLetterData() + $this->digitTransformLanguage = Language::factory( 'ckb' ); + } +} + +/** + * Workaround for incorrect collation of Estonian language ('et') in ICU (bug 54168). + * + * 'W' and 'V' should not be considered the same letter for the purposes of collation in modern + * Estonian. We work around this by replacing 'W' and 'w' with 'ᴡ' U+1D21 'LATIN LETTER SMALL + * CAPITAL W' for sortkey generation, which is collated like 'W' and is not tailored to have the + * same primary weight as 'V' in Estonian. + */ +class CollationEt extends IcuCollation { + function __construct() { + parent::__construct( 'et' ); + } + + private static function mangle( $string ) { + return str_replace( + array( 'w', 'W' ), + 'ᴡ', // U+1D21 'LATIN LETTER SMALL CAPITAL W' + $string + ); + } + + private static function unmangle( $string ) { + // Casing data is lost… + return str_replace( + 'ᴡ', // U+1D21 'LATIN LETTER SMALL CAPITAL W' + 'W', + $string + ); + } + + function getSortKey( $string ) { + return parent::getSortKey( self::mangle( $string ) ); + } + + function getFirstLetter( $string ) { + return self::unmangle( parent::getFirstLetter( self::mangle( $string ) ) ); + } +} diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php deleted file mode 100644 index 67cb87db..00000000 --- a/includes/ConfEditor.php +++ /dev/null @@ -1,1109 +0,0 @@ -<?php -/** - * Configuration file editor. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - */ - -/** - * This is a state machine style parser with two internal stacks: - * * A next state stack, which determines the state the machine will progress to next - * * A path stack, which keeps track of the logical location in the file. - * - * Reference grammar: - * - * file = T_OPEN_TAG *statement - * statement = T_VARIABLE "=" expression ";" - * expression = array / scalar / T_VARIABLE - * array = T_ARRAY "(" [ element *( "," element ) [ "," ] ] ")" - * element = assoc-element / expression - * assoc-element = scalar T_DOUBLE_ARROW expression - * scalar = T_LNUMBER / T_DNUMBER / T_STRING / T_CONSTANT_ENCAPSED_STRING - */ -class ConfEditor { - /** The text to parse */ - var $text; - - /** The token array from token_get_all() */ - var $tokens; - - /** The current position in the token array */ - var $pos; - - /** The current 1-based line number */ - var $lineNum; - - /** The current 1-based column number */ - var $colNum; - - /** The current 0-based byte number */ - var $byteNum; - - /** The current ConfEditorToken object */ - var $currentToken; - - /** The previous ConfEditorToken object */ - var $prevToken; - - /** - * The state machine stack. This is an array of strings where the topmost - * element will be popped off and become the next parser state. - */ - var $stateStack; - - /** - * The path stack is a stack of associative arrays with the following elements: - * name The name of top level of the path - * level The level (number of elements) of the path - * startByte The byte offset of the start of the path - * startToken The token offset of the start - * endByte The byte offset of thee - * endToken The token offset of the end, plus one - * valueStartToken The start token offset of the value part - * valueStartByte The start byte offset of the value part - * valueEndToken The end token offset of the value part, plus one - * valueEndByte The end byte offset of the value part, plus one - * nextArrayIndex The next numeric array index at this level - * hasComma True if the array element ends with a comma - * arrowByte The byte offset of the "=>", or false if there isn't one - */ - var $pathStack; - - /** - * The elements of the top of the pathStack for every path encountered, indexed - * by slash-separated path. - */ - var $pathInfo; - - /** - * Next serial number for whitespace placeholder paths (\@extra-N) - */ - var $serial; - - /** - * Editor state. This consists of the internal copy/insert operations which - * are applied to the source string to obtain the destination string. - */ - var $edits; - - /** - * Simple entry point for command-line testing - * - * @param $text string - * - * @return string - */ - static function test( $text ) { - try { - $ce = new self( $text ); - $ce->parse(); - } catch ( ConfEditorParseError $e ) { - return $e->getMessage() . "\n" . $e->highlight( $text ); - } - return "OK"; - } - - /** - * Construct a new parser - */ - public function __construct( $text ) { - $this->text = $text; - } - - /** - * Edit the text. Returns the edited text. - * @param array $ops of operations. - * - * Operations are given as an associative array, with members: - * type: One of delete, set, append or insert (required) - * path: The path to operate on (required) - * key: The array key to insert/append, with PHP quotes - * value: The value, with PHP quotes - * - * delete - * Deletes an array element or statement with the specified path. - * e.g. - * array('type' => 'delete', 'path' => '$foo/bar/baz' ) - * is equivalent to the runtime PHP code: - * unset( $foo['bar']['baz'] ); - * - * set - * Sets the value of an array element. If the element doesn't exist, it - * is appended to the array. If it does exist, the value is set, with - * comments and indenting preserved. - * - * append - * Appends a new element to the end of the array. Adds a trailing comma. - * e.g. - * array( 'type' => 'append', 'path', '$foo/bar', - * 'key' => 'baz', 'value' => "'x'" ) - * is like the PHP code: - * $foo['bar']['baz'] = 'x'; - * - * insert - * Insert a new element at the start of the array. - * - * @throws MWException - * @return string - */ - public function edit( $ops ) { - $this->parse(); - - $this->edits = array( - array( 'copy', 0, strlen( $this->text ) ) - ); - foreach ( $ops as $op ) { - $type = $op['type']; - $path = $op['path']; - $value = isset( $op['value'] ) ? $op['value'] : null; - $key = isset( $op['key'] ) ? $op['key'] : null; - - switch ( $type ) { - case 'delete': - list( $start, $end ) = $this->findDeletionRegion( $path ); - $this->replaceSourceRegion( $start, $end, false ); - break; - case 'set': - if ( isset( $this->pathInfo[$path] ) ) { - list( $start, $end ) = $this->findValueRegion( $path ); - $encValue = $value; // var_export( $value, true ); - $this->replaceSourceRegion( $start, $end, $encValue ); - break; - } - // No existing path, fall through to append - $slashPos = strrpos( $path, '/' ); - $key = var_export( substr( $path, $slashPos + 1 ), true ); - $path = substr( $path, 0, $slashPos ); - // Fall through - case 'append': - // Find the last array element - $lastEltPath = $this->findLastArrayElement( $path ); - if ( $lastEltPath === false ) { - throw new MWException( "Can't find any element of array \"$path\"" ); - } - $lastEltInfo = $this->pathInfo[$lastEltPath]; - - // Has it got a comma already? - if ( strpos( $lastEltPath, '@extra' ) === false && !$lastEltInfo['hasComma'] ) { - // No comma, insert one after the value region - list( , $end ) = $this->findValueRegion( $lastEltPath ); - $this->replaceSourceRegion( $end - 1, $end - 1, ',' ); - } - - // Make the text to insert - list( $start, $end ) = $this->findDeletionRegion( $lastEltPath ); - - if ( $key === null ) { - list( $indent, ) = $this->getIndent( $start ); - $textToInsert = "$indent$value,"; - } else { - list( $indent, $arrowIndent ) = - $this->getIndent( $start, $key, $lastEltInfo['arrowByte'] ); - $textToInsert = "$indent$key$arrowIndent=> $value,"; - } - $textToInsert .= ( $indent === false ? ' ' : "\n" ); - - // Insert the item - $this->replaceSourceRegion( $end, $end, $textToInsert ); - break; - case 'insert': - // Find first array element - $firstEltPath = $this->findFirstArrayElement( $path ); - if ( $firstEltPath === false ) { - throw new MWException( "Can't find array element of \"$path\"" ); - } - list( $start, ) = $this->findDeletionRegion( $firstEltPath ); - $info = $this->pathInfo[$firstEltPath]; - - // Make the text to insert - if ( $key === null ) { - list( $indent, ) = $this->getIndent( $start ); - $textToInsert = "$indent$value,"; - } else { - list( $indent, $arrowIndent ) = - $this->getIndent( $start, $key, $info['arrowByte'] ); - $textToInsert = "$indent$key$arrowIndent=> $value,"; - } - $textToInsert .= ( $indent === false ? ' ' : "\n" ); - - // Insert the item - $this->replaceSourceRegion( $start, $start, $textToInsert ); - break; - default: - throw new MWException( "Unrecognised operation: \"$type\"" ); - } - } - - // Do the edits - $out = ''; - foreach ( $this->edits as $edit ) { - if ( $edit[0] == 'copy' ) { - $out .= substr( $this->text, $edit[1], $edit[2] - $edit[1] ); - } else { // if ( $edit[0] == 'insert' ) - $out .= $edit[1]; - } - } - - // Do a second parse as a sanity check - $this->text = $out; - try { - $this->parse(); - } catch ( ConfEditorParseError $e ) { - throw new MWException( - "Sorry, ConfEditor broke the file during editing and it won't parse anymore: " . - $e->getMessage() ); - } - return $out; - } - - /** - * Get the variables defined in the text - * @return array( varname => value ) - */ - function getVars() { - $vars = array(); - $this->parse(); - foreach ( $this->pathInfo as $path => $data ) { - if ( $path[0] != '$' ) { - continue; - } - $trimmedPath = substr( $path, 1 ); - $name = $data['name']; - if ( $name[0] == '@' ) { - continue; - } - if ( $name[0] == '$' ) { - $name = substr( $name, 1 ); - } - $parentPath = substr( $trimmedPath, 0, - strlen( $trimmedPath ) - strlen( $name ) ); - if ( substr( $parentPath, -1 ) == '/' ) { - $parentPath = substr( $parentPath, 0, -1 ); - } - - $value = substr( $this->text, $data['valueStartByte'], - $data['valueEndByte'] - $data['valueStartByte'] - ); - $this->setVar( $vars, $parentPath, $name, - $this->parseScalar( $value ) ); - } - return $vars; - } - - /** - * Set a value in an array, unless it's set already. For instance, - * setVar( $arr, 'foo/bar', 'baz', 3 ); will set - * $arr['foo']['bar']['baz'] = 3; - * @param $array array - * @param string $path slash-delimited path - * @param $key mixed Key - * @param $value mixed Value - */ - function setVar( &$array, $path, $key, $value ) { - $pathArr = explode( '/', $path ); - $target =& $array; - if ( $path !== '' ) { - foreach ( $pathArr as $p ) { - if ( !isset( $target[$p] ) ) { - $target[$p] = array(); - } - $target =& $target[$p]; - } - } - if ( !isset( $target[$key] ) ) { - $target[$key] = $value; - } - } - - /** - * Parse a scalar value in PHP - * @return mixed Parsed value - */ - function parseScalar( $str ) { - if ( $str !== '' && $str[0] == '\'' ) { - // Single-quoted string - // @todo FIXME: trim() call is due to mystery bug where whitespace gets - // appended to the token; without it we ended up reading in the - // extra quote on the end! - return strtr( substr( trim( $str ), 1, -1 ), - array( '\\\'' => '\'', '\\\\' => '\\' ) ); - } - if ( $str !== '' && $str[0] == '"' ) { - // Double-quoted string - // @todo FIXME: trim() call is due to mystery bug where whitespace gets - // appended to the token; without it we ended up reading in the - // extra quote on the end! - return stripcslashes( substr( trim( $str ), 1, -1 ) ); - } - if ( substr( $str, 0, 4 ) == 'true' ) { - return true; - } - if ( substr( $str, 0, 5 ) == 'false' ) { - return false; - } - if ( substr( $str, 0, 4 ) == 'null' ) { - return null; - } - // Must be some kind of numeric value, so let PHP's weak typing - // be useful for a change - return $str; - } - - /** - * Replace the byte offset region of the source with $newText. - * Works by adding elements to the $this->edits array. - */ - function replaceSourceRegion( $start, $end, $newText = false ) { - // Split all copy operations with a source corresponding to the region - // in question. - $newEdits = array(); - foreach ( $this->edits as $edit ) { - if ( $edit[0] !== 'copy' ) { - $newEdits[] = $edit; - continue; - } - $copyStart = $edit[1]; - $copyEnd = $edit[2]; - if ( $start >= $copyEnd || $end <= $copyStart ) { - // Outside this region - $newEdits[] = $edit; - continue; - } - if ( ( $start < $copyStart && $end > $copyStart ) - || ( $start < $copyEnd && $end > $copyEnd ) - ) { - throw new MWException( "Overlapping regions found, can't do the edit" ); - } - // Split the copy - $newEdits[] = array( 'copy', $copyStart, $start ); - if ( $newText !== false ) { - $newEdits[] = array( 'insert', $newText ); - } - $newEdits[] = array( 'copy', $end, $copyEnd ); - } - $this->edits = $newEdits; - } - - /** - * Finds the source byte region which you would want to delete, if $pathName - * was to be deleted. Includes the leading spaces and tabs, the trailing line - * break, and any comments in between. - * @param $pathName - * @throws MWException - * @return array - */ - function findDeletionRegion( $pathName ) { - if ( !isset( $this->pathInfo[$pathName] ) ) { - throw new MWException( "Can't find path \"$pathName\"" ); - } - $path = $this->pathInfo[$pathName]; - // Find the start - $this->firstToken(); - while ( $this->pos != $path['startToken'] ) { - $this->nextToken(); - } - $regionStart = $path['startByte']; - for ( $offset = -1; $offset >= -$this->pos; $offset-- ) { - $token = $this->getTokenAhead( $offset ); - if ( !$token->isSkip() ) { - // If there is other content on the same line, don't move the start point - // back, because that will cause the regions to overlap. - $regionStart = $path['startByte']; - break; - } - $lfPos = strrpos( $token->text, "\n" ); - if ( $lfPos === false ) { - $regionStart -= strlen( $token->text ); - } else { - // The line start does not include the LF - $regionStart -= strlen( $token->text ) - $lfPos - 1; - break; - } - } - // Find the end - while ( $this->pos != $path['endToken'] ) { - $this->nextToken(); - } - $regionEnd = $path['endByte']; // past the end - for ( $offset = 0; $offset < count( $this->tokens ) - $this->pos; $offset++ ) { - $token = $this->getTokenAhead( $offset ); - if ( !$token->isSkip() ) { - break; - } - $lfPos = strpos( $token->text, "\n" ); - if ( $lfPos === false ) { - $regionEnd += strlen( $token->text ); - } else { - // This should point past the LF - $regionEnd += $lfPos + 1; - break; - } - } - return array( $regionStart, $regionEnd ); - } - - /** - * Find the byte region in the source corresponding to the value part. - * This includes the quotes, but does not include the trailing comma - * or semicolon. - * - * The end position is the past-the-end (end + 1) value as per convention. - * @param $pathName - * @throws MWException - * @return array - */ - function findValueRegion( $pathName ) { - if ( !isset( $this->pathInfo[$pathName] ) ) { - throw new MWException( "Can't find path \"$pathName\"" ); - } - $path = $this->pathInfo[$pathName]; - if ( $path['valueStartByte'] === false || $path['valueEndByte'] === false ) { - throw new MWException( "Can't find value region for path \"$pathName\"" ); - } - return array( $path['valueStartByte'], $path['valueEndByte'] ); - } - - /** - * Find the path name of the last element in the array. - * If the array is empty, this will return the \@extra interstitial element. - * If the specified path is not found or is not an array, it will return false. - * @return bool|int|string - */ - function findLastArrayElement( $path ) { - // Try for a real element - $lastEltPath = false; - foreach ( $this->pathInfo as $candidatePath => $info ) { - $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 ); - $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 ); - if ( $part2 == '@' ) { - // Do nothing - } elseif ( $part1 == "$path/" ) { - $lastEltPath = $candidatePath; - } elseif ( $lastEltPath !== false ) { - break; - } - } - if ( $lastEltPath !== false ) { - return $lastEltPath; - } - - // Try for an interstitial element - $extraPath = false; - foreach ( $this->pathInfo as $candidatePath => $info ) { - $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 ); - if ( $part1 == "$path/" ) { - $extraPath = $candidatePath; - } elseif ( $extraPath !== false ) { - break; - } - } - return $extraPath; - } - - /** - * Find the path name of first element in the array. - * If the array is empty, this will return the \@extra interstitial element. - * If the specified path is not found or is not an array, it will return false. - * @return bool|int|string - */ - function findFirstArrayElement( $path ) { - // Try for an ordinary element - foreach ( $this->pathInfo as $candidatePath => $info ) { - $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 ); - $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 ); - if ( $part1 == "$path/" && $part2 != '@' ) { - return $candidatePath; - } - } - - // Try for an interstitial element - foreach ( $this->pathInfo as $candidatePath => $info ) { - $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 ); - if ( $part1 == "$path/" ) { - return $candidatePath; - } - } - return false; - } - - /** - * Get the indent string which sits after a given start position. - * Returns false if the position is not at the start of the line. - * @return array - */ - function getIndent( $pos, $key = false, $arrowPos = false ) { - $arrowIndent = ' '; - if ( $pos == 0 || $this->text[$pos - 1] == "\n" ) { - $indentLength = strspn( $this->text, " \t", $pos ); - $indent = substr( $this->text, $pos, $indentLength ); - } else { - $indent = false; - } - if ( $indent !== false && $arrowPos !== false ) { - $arrowIndentLength = $arrowPos - $pos - $indentLength - strlen( $key ); - if ( $arrowIndentLength > 0 ) { - $arrowIndent = str_repeat( ' ', $arrowIndentLength ); - } - } - return array( $indent, $arrowIndent ); - } - - /** - * Run the parser on the text. Throws an exception if the string does not - * match our defined subset of PHP syntax. - */ - public function parse() { - $this->initParse(); - $this->pushState( 'file' ); - $this->pushPath( '@extra-' . ( $this->serial++ ) ); - $token = $this->firstToken(); - - while ( !$token->isEnd() ) { - $state = $this->popState(); - if ( !$state ) { - $this->error( 'internal error: empty state stack' ); - } - - switch ( $state ) { - case 'file': - $this->expect( T_OPEN_TAG ); - $token = $this->skipSpace(); - if ( $token->isEnd() ) { - break 2; - } - $this->pushState( 'statement', 'file 2' ); - break; - case 'file 2': - $token = $this->skipSpace(); - if ( $token->isEnd() ) { - break 2; - } - $this->pushState( 'statement', 'file 2' ); - break; - case 'statement': - $token = $this->skipSpace(); - if ( !$this->validatePath( $token->text ) ) { - $this->error( "Invalid variable name \"{$token->text}\"" ); - } - $this->nextPath( $token->text ); - $this->expect( T_VARIABLE ); - $this->skipSpace(); - $arrayAssign = false; - if ( $this->currentToken()->type == '[' ) { - $this->nextToken(); - $token = $this->skipSpace(); - if ( !$token->isScalar() ) { - $this->error( "expected a string or number for the array key" ); - } - if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) { - $text = $this->parseScalar( $token->text ); - } else { - $text = $token->text; - } - if ( !$this->validatePath( $text ) ) { - $this->error( "Invalid associative array name \"$text\"" ); - } - $this->pushPath( $text ); - $this->nextToken(); - $this->skipSpace(); - $this->expect( ']' ); - $this->skipSpace(); - $arrayAssign = true; - } - $this->expect( '=' ); - $this->skipSpace(); - $this->startPathValue(); - if ( $arrayAssign ) { - $this->pushState( 'expression', 'array assign end' ); - } else { - $this->pushState( 'expression', 'statement end' ); - } - break; - case 'array assign end': - case 'statement end': - $this->endPathValue(); - if ( $state == 'array assign end' ) { - $this->popPath(); - } - $this->skipSpace(); - $this->expect( ';' ); - $this->nextPath( '@extra-' . ( $this->serial++ ) ); - break; - case 'expression': - $token = $this->skipSpace(); - if ( $token->type == T_ARRAY ) { - $this->pushState( 'array' ); - } elseif ( $token->isScalar() ) { - $this->nextToken(); - } elseif ( $token->type == T_VARIABLE ) { - $this->nextToken(); - } else { - $this->error( "expected simple expression" ); - } - break; - case 'array': - $this->skipSpace(); - $this->expect( T_ARRAY ); - $this->skipSpace(); - $this->expect( '(' ); - $this->skipSpace(); - $this->pushPath( '@extra-' . ( $this->serial++ ) ); - if ( $this->isAhead( ')' ) ) { - // Empty array - $this->pushState( 'array end' ); - } else { - $this->pushState( 'element', 'array end' ); - } - break; - case 'array end': - $this->skipSpace(); - $this->popPath(); - $this->expect( ')' ); - break; - case 'element': - $token = $this->skipSpace(); - // Look ahead to find the double arrow - if ( $token->isScalar() && $this->isAhead( T_DOUBLE_ARROW, 1 ) ) { - // Found associative element - $this->pushState( 'assoc-element', 'element end' ); - } else { - // Not associative - $this->nextPath( '@next' ); - $this->startPathValue(); - $this->pushState( 'expression', 'element end' ); - } - break; - case 'element end': - $token = $this->skipSpace(); - if ( $token->type == ',' ) { - $this->endPathValue(); - $this->markComma(); - $this->nextToken(); - $this->nextPath( '@extra-' . ( $this->serial++ ) ); - // Look ahead to find ending bracket - if ( $this->isAhead( ")" ) ) { - // Found ending bracket, no continuation - $this->skipSpace(); - } else { - // No ending bracket, continue to next element - $this->pushState( 'element' ); - } - } elseif ( $token->type == ')' ) { - // End array - $this->endPathValue(); - } else { - $this->error( "expected the next array element or the end of the array" ); - } - break; - case 'assoc-element': - $token = $this->skipSpace(); - if ( !$token->isScalar() ) { - $this->error( "expected a string or number for the array key" ); - } - if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) { - $text = $this->parseScalar( $token->text ); - } else { - $text = $token->text; - } - if ( !$this->validatePath( $text ) ) { - $this->error( "Invalid associative array name \"$text\"" ); - } - $this->nextPath( $text ); - $this->nextToken(); - $this->skipSpace(); - $this->markArrow(); - $this->expect( T_DOUBLE_ARROW ); - $this->skipSpace(); - $this->startPathValue(); - $this->pushState( 'expression' ); - break; - } - } - if ( count( $this->stateStack ) ) { - $this->error( 'unexpected end of file' ); - } - $this->popPath(); - } - - /** - * Initialise a parse. - */ - protected function initParse() { - $this->tokens = token_get_all( $this->text ); - $this->stateStack = array(); - $this->pathStack = array(); - $this->firstToken(); - $this->pathInfo = array(); - $this->serial = 1; - } - - /** - * Set the parse position. Do not call this except from firstToken() and - * nextToken(), there is more to update than just the position. - */ - protected function setPos( $pos ) { - $this->pos = $pos; - if ( $this->pos >= count( $this->tokens ) ) { - $this->currentToken = ConfEditorToken::newEnd(); - } else { - $this->currentToken = $this->newTokenObj( $this->tokens[$this->pos] ); - } - return $this->currentToken; - } - - /** - * Create a ConfEditorToken from an element of token_get_all() - * @return ConfEditorToken - */ - function newTokenObj( $internalToken ) { - if ( is_array( $internalToken ) ) { - return new ConfEditorToken( $internalToken[0], $internalToken[1] ); - } else { - return new ConfEditorToken( $internalToken, $internalToken ); - } - } - - /** - * Reset the parse position - */ - function firstToken() { - $this->setPos( 0 ); - $this->prevToken = ConfEditorToken::newEnd(); - $this->lineNum = 1; - $this->colNum = 1; - $this->byteNum = 0; - return $this->currentToken; - } - - /** - * Get the current token - */ - function currentToken() { - return $this->currentToken; - } - - /** - * Advance the current position and return the resulting next token - */ - function nextToken() { - if ( $this->currentToken ) { - $text = $this->currentToken->text; - $lfCount = substr_count( $text, "\n" ); - if ( $lfCount ) { - $this->lineNum += $lfCount; - $this->colNum = strlen( $text ) - strrpos( $text, "\n" ); - } else { - $this->colNum += strlen( $text ); - } - $this->byteNum += strlen( $text ); - } - $this->prevToken = $this->currentToken; - $this->setPos( $this->pos + 1 ); - return $this->currentToken; - } - - /** - * Get the token $offset steps ahead of the current position. - * $offset may be negative, to get tokens behind the current position. - * @return ConfEditorToken - */ - function getTokenAhead( $offset ) { - $pos = $this->pos + $offset; - if ( $pos >= count( $this->tokens ) || $pos < 0 ) { - return ConfEditorToken::newEnd(); - } else { - return $this->newTokenObj( $this->tokens[$pos] ); - } - } - - /** - * Advances the current position past any whitespace or comments - */ - function skipSpace() { - while ( $this->currentToken && $this->currentToken->isSkip() ) { - $this->nextToken(); - } - return $this->currentToken; - } - - /** - * Throws an error if the current token is not of the given type, and - * then advances to the next position. - */ - function expect( $type ) { - if ( $this->currentToken && $this->currentToken->type == $type ) { - return $this->nextToken(); - } else { - $this->error( "expected " . $this->getTypeName( $type ) . - ", got " . $this->getTypeName( $this->currentToken->type ) ); - } - } - - /** - * Push a state or two on to the state stack. - */ - function pushState( $nextState, $stateAfterThat = null ) { - if ( $stateAfterThat !== null ) { - $this->stateStack[] = $stateAfterThat; - } - $this->stateStack[] = $nextState; - } - - /** - * Pop a state from the state stack. - * @return mixed - */ - function popState() { - return array_pop( $this->stateStack ); - } - - /** - * Returns true if the user input path is valid. - * This exists to allow "/" and "@" to be reserved for string path keys - * @return bool - */ - function validatePath( $path ) { - return strpos( $path, '/' ) === false && substr( $path, 0, 1 ) != '@'; - } - - /** - * Internal function to update some things at the end of a path region. Do - * not call except from popPath() or nextPath(). - */ - function endPath() { - $key = ''; - foreach ( $this->pathStack as $pathInfo ) { - if ( $key !== '' ) { - $key .= '/'; - } - $key .= $pathInfo['name']; - } - $pathInfo['endByte'] = $this->byteNum; - $pathInfo['endToken'] = $this->pos; - $this->pathInfo[$key] = $pathInfo; - } - - /** - * Go up to a new path level, for example at the start of an array. - */ - function pushPath( $path ) { - $this->pathStack[] = array( - 'name' => $path, - 'level' => count( $this->pathStack ) + 1, - 'startByte' => $this->byteNum, - 'startToken' => $this->pos, - 'valueStartToken' => false, - 'valueStartByte' => false, - 'valueEndToken' => false, - 'valueEndByte' => false, - 'nextArrayIndex' => 0, - 'hasComma' => false, - 'arrowByte' => false - ); - } - - /** - * Go down a path level, for example at the end of an array. - */ - function popPath() { - $this->endPath(); - array_pop( $this->pathStack ); - } - - /** - * Go to the next path on the same level. This ends the current path and - * starts a new one. If $path is \@next, the new path is set to the next - * numeric array element. - */ - function nextPath( $path ) { - $this->endPath(); - $i = count( $this->pathStack ) - 1; - if ( $path == '@next' ) { - $nextArrayIndex =& $this->pathStack[$i]['nextArrayIndex']; - $this->pathStack[$i]['name'] = $nextArrayIndex; - $nextArrayIndex++; - } else { - $this->pathStack[$i]['name'] = $path; - } - $this->pathStack[$i] = - array( - 'startByte' => $this->byteNum, - 'startToken' => $this->pos, - 'valueStartToken' => false, - 'valueStartByte' => false, - 'valueEndToken' => false, - 'valueEndByte' => false, - 'hasComma' => false, - 'arrowByte' => false, - ) + $this->pathStack[$i]; - } - - /** - * Mark the start of the value part of a path. - */ - function startPathValue() { - $path =& $this->pathStack[count( $this->pathStack ) - 1]; - $path['valueStartToken'] = $this->pos; - $path['valueStartByte'] = $this->byteNum; - } - - /** - * Mark the end of the value part of a path. - */ - function endPathValue() { - $path =& $this->pathStack[count( $this->pathStack ) - 1]; - $path['valueEndToken'] = $this->pos; - $path['valueEndByte'] = $this->byteNum; - } - - /** - * Mark the comma separator in an array element - */ - function markComma() { - $path =& $this->pathStack[count( $this->pathStack ) - 1]; - $path['hasComma'] = true; - } - - /** - * Mark the arrow separator in an associative array element - */ - function markArrow() { - $path =& $this->pathStack[count( $this->pathStack ) - 1]; - $path['arrowByte'] = $this->byteNum; - } - - /** - * Generate a parse error - */ - function error( $msg ) { - throw new ConfEditorParseError( $this, $msg ); - } - - /** - * Get a readable name for the given token type. - * @return string - */ - function getTypeName( $type ) { - if ( is_int( $type ) ) { - return token_name( $type ); - } else { - return "\"$type\""; - } - } - - /** - * Looks ahead to see if the given type is the next token type, starting - * from the current position plus the given offset. Skips any intervening - * whitespace. - * @return bool - */ - function isAhead( $type, $offset = 0 ) { - $ahead = $offset; - $token = $this->getTokenAhead( $offset ); - while ( !$token->isEnd() ) { - if ( $token->isSkip() ) { - $ahead++; - $token = $this->getTokenAhead( $ahead ); - continue; - } elseif ( $token->type == $type ) { - // Found the type - return true; - } else { - // Not found - return false; - } - } - return false; - } - - /** - * Get the previous token object - */ - function prevToken() { - return $this->prevToken; - } - - /** - * Echo a reasonably readable representation of the tokenizer array. - */ - function dumpTokens() { - $out = ''; - foreach ( $this->tokens as $token ) { - $obj = $this->newTokenObj( $token ); - $out .= sprintf( "%-28s %s\n", - $this->getTypeName( $obj->type ), - addcslashes( $obj->text, "\0..\37" ) ); - } - echo "<pre>" . htmlspecialchars( $out ) . "</pre>"; - } -} - -/** - * Exception class for parse errors - */ -class ConfEditorParseError extends MWException { - var $lineNum, $colNum; - function __construct( $editor, $msg ) { - $this->lineNum = $editor->lineNum; - $this->colNum = $editor->colNum; - parent::__construct( "Parse error on line {$editor->lineNum} " . - "col {$editor->colNum}: $msg" ); - } - - function highlight( $text ) { - $lines = StringUtils::explode( "\n", $text ); - foreach ( $lines as $lineNum => $line ) { - if ( $lineNum == $this->lineNum - 1 ) { - return "$line\n" . str_repeat( ' ', $this->colNum - 1 ) . "^\n"; - } - } - return ''; - } - -} - -/** - * Class to wrap a token from the tokenizer. - */ -class ConfEditorToken { - var $type, $text; - - static $scalarTypes = array( T_LNUMBER, T_DNUMBER, T_STRING, T_CONSTANT_ENCAPSED_STRING ); - static $skipTypes = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ); - - static function newEnd() { - return new self( 'END', '' ); - } - - function __construct( $type, $text ) { - $this->type = $type; - $this->text = $text; - } - - function isSkip() { - return in_array( $this->type, self::$skipTypes ); - } - - function isScalar() { - return in_array( $this->type, self::$scalarTypes ); - } - - function isEnd() { - return $this->type == 'END'; - } -} diff --git a/includes/Cookie.php b/includes/Cookie.php index ecf4667d..cb041904 100644 --- a/includes/Cookie.php +++ b/includes/Cookie.php @@ -43,8 +43,8 @@ class Cookie { * cookies. Used internally after a request to parse the * Set-Cookie headers. * - * @param string $value the value of the cookie - * @param array $attr possible key/values: + * @param string $value The value of the cookie + * @param array $attr Possible key/values: * expires A date string * path The path this cookie is used on * domain Domain this cookie is used on @@ -85,18 +85,21 @@ class Cookie { * @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably * not a big problem in practice, but there are test cases) * - * @param string $domain the domain to validate + * @param string $domain The domain to validate * @param string $originDomain (optional) the domain the cookie originates from - * @return Boolean + * @return bool */ public static function validateCookieDomain( $domain, $originDomain = null ) { - // Don't allow a trailing dot - if ( substr( $domain, -1 ) == '.' ) { + $dc = explode( ".", $domain ); + + // Don't allow a trailing dot or addresses without a or just a leading dot + if ( substr( $domain, -1 ) == '.' || + count( $dc ) <= 1 || + count( $dc ) == 2 && $dc[0] === '' + ) { return false; } - $dc = explode( ".", $domain ); - // Only allow full, valid IP addresses if ( preg_match( '/^[0-9.]+$/', $domain ) ) { if ( count( $dc ) != 4 ) { @@ -131,8 +134,14 @@ class Cookie { } if ( substr( $domain, 0, 1 ) == '.' - && substr_compare( $originDomain, $domain, -strlen( $domain ), - strlen( $domain ), true ) != 0 ) { + && substr_compare( + $originDomain, + $domain, + -strlen( $domain ), + strlen( $domain ), + true + ) != 0 + ) { return false; } } @@ -143,9 +152,9 @@ class Cookie { /** * Serialize the cookie jar into a format useful for HTTP Request headers. * - * @param string $path the path that will be used. Required. - * @param string $domain the domain that will be used. Required. - * @return String + * @param string $path The path that will be used. Required. + * @param string $domain The domain that will be used. Required. + * @return string */ public function serializeToHttpRequest( $path, $domain ) { $ret = ''; @@ -160,15 +169,22 @@ class Cookie { } /** - * @param $domain + * @param string $domain * @return bool */ protected function canServeDomain( $domain ) { if ( $domain == $this->domain || ( strlen( $domain ) > strlen( $this->domain ) && substr( $this->domain, 0, 1 ) == '.' - && substr_compare( $domain, $this->domain, -strlen( $this->domain ), - strlen( $this->domain ), true ) == 0 ) ) { + && substr_compare( + $domain, + $this->domain, + -strlen( $this->domain ), + strlen( $this->domain ), + true + ) == 0 + ) + ) { return true; } @@ -176,7 +192,7 @@ class Cookie { } /** - * @param $path + * @param string $path * @return bool */ protected function canServePath( $path ) { @@ -197,6 +213,9 @@ class CookieJar { /** * Set a cookie in the cookie jar. Make sure only one cookie per-name exists. * @see Cookie::set() + * @param string $name + * @param string $value + * @param array $attr */ public function setCookie( $name, $value, $attr ) { /* cookies: case insensitive, so this should work. @@ -213,6 +232,8 @@ class CookieJar { /** * @see Cookie::serializeToHttpRequest + * @param string $path + * @param string $domain * @return string */ public function serializeToHttpRequest( $path, $domain ) { @@ -232,8 +253,8 @@ class CookieJar { /** * Parse the content of an Set-Cookie HTTP Response header. * - * @param $cookie String - * @param string $domain cookie's domain + * @param string $cookie + * @param string $domain Cookie's domain * @return null */ public function parseCookieResponseHeader( $cookie, $domain ) { diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 78568107..71268932 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -15,7 +15,7 @@ * performed in LocalSettings.php. * * Documentation is in the source and on: - * http://www.mediawiki.org/wiki/Manual:Configuration_settings + * https://www.mediawiki.org/wiki/Manual:Configuration_settings * * @warning Note: this (and other things) will break if the autoloader is not * enabled. Please include includes/AutoLoader.php before including this file. @@ -60,10 +60,22 @@ if ( !defined( 'MEDIAWIKI' ) ) { $wgConf = new SiteConfiguration; /** + * Registry of factory functions to create config objects: + * The 'main' key must be set, and the value should be a valid + * callable. + * @since 1.23 + */ +$wgConfigRegistry = array( + 'main' => 'GlobalVarConfig::newInstance' +); + +/** * MediaWiki version number + * Note that MediaWikiVersionFetcher::fetchVersion() uses a regex to check this. + * Using single quotes is, therefore, important here. * @since 1.2 */ -$wgVersion = '1.22.15'; +$wgVersion = '1.24.1'; /** * Name of the site. It must be changed in LocalSettings.php @@ -97,6 +109,13 @@ $wgServer = WebRequest::detectServer(); */ $wgCanonicalServer = false; +/** + * Server name. This is automatically computed by parsing the bare + * hostname out of $wgCanonicalServer. It should not be customized. + * @since 1.24 + */ +$wgServerName = false; + /************************************************************************//** * @name Script path settings * @{ @@ -236,7 +255,7 @@ $wgFileCacheDirectory = false; /** * The URL path of the wiki logo. The logo size should be 135x135 pixels. - * Defaults to "{$wgStylePath}/common/images/wiki.png". + * Defaults to "$wgResourceBasePath/resources/assets/wiki.png". */ $wgLogo = false; @@ -339,11 +358,6 @@ $wgEnableAsyncUploads = false; $wgIllegalFileChars = ":"; /** - * @deprecated since 1.17 use $wgDeletedDirectory - */ -$wgFileStore = array(); - -/** * What directory to place deleted uploads in. * Defaults to "{$wgUploadDirectory}/deleted". */ @@ -355,11 +369,20 @@ $wgDeletedDirectory = false; $wgImgAuthDetails = false; /** - * If this is enabled, img_auth.php will not allow image access unless the wiki - * is private. This improves security when image uploads are hosted on a - * separate domain. + * Map of relative URL directories to match to internal mwstore:// base storage paths. + * For img_auth.php requests, everything after "img_auth.php/" is checked to see + * if starts with any of the prefixes defined here. The prefixes should not overlap. + * The prefix that matches has a corresponding storage path, which the rest of the URL + * is assumed to be relative to. The file at that path (or a 404) is send to the client. + * + * Example: + * $wgImgAuthUrlPathMap['/timeline/'] = 'mwstore://local-fs/timeline-render/'; + * The above maps ".../img_auth.php/timeline/X" to "mwstore://local-fs/timeline-render/". + * The name "local-fs" should correspond by name to an entry in $wgFileBackends. + * + * @see $wgFileBackends */ -$wgImgAuthPublicTest = true; +$wgImgAuthUrlPathMap = array(); /** * File repository structures @@ -384,8 +407,6 @@ $wgImgAuthPublicTest = true; * url : base URL to the root of the zone * urlsByExt : map of file extension types to base URLs * (useful for using a different cache for videos) - * handlerUrl : base script-handled URL to the root of the zone - * (see FileRepo::getZoneHandlerUrl() function) * Zones default to using "<repo name>-<zone name>" as the container name * and default to using the container root as the zone's root directory. * Nesting of zone locations within other zones should be avoided. @@ -574,7 +595,7 @@ $wgCacheSharedUploads = true; /** * Allow for upload to be copied from an URL. - * The timeout for copy uploads is set by $wgHTTPTimeout. + * The timeout for copy uploads is set by $wgCopyUploadTimeout. * You have to assign the user right 'upload_by_url' to a user group, to use this. */ $wgAllowCopyUploads = false; @@ -739,7 +760,7 @@ $wgFileBlacklist = array( 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl' ); /** - * Files with these mime types will never be allowed as uploads + * Files with these MIME types will never be allowed as uploads * if $wgVerifyMimeType is enabled. */ $wgMimeTypeBlacklist = array( @@ -791,7 +812,7 @@ $wgDisableUploadScriptChecks = false; $wgUploadSizeWarning = false; /** - * list of trusted media-types and mime types. + * list of trusted media-types and MIME types. * Use the MEDIATYPE_xxx constants to represent media types. * This list is used by File::isSafeFile * @@ -839,13 +860,22 @@ $wgContentHandlers = array( CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // dumb version, no syntax highlighting CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', + // simple implementation, for use by extensions, etc. + CONTENT_MODEL_JSON => 'JsonContentHandler', // dumb version, no syntax highlighting CONTENT_MODEL_CSS => 'CssContentHandler', - // plain text, for use by extensions etc + // plain text, for use by extensions, etc. CONTENT_MODEL_TEXT => 'TextContentHandler', ); /** + * Whether to enable server-side image thumbnailing. If false, images will + * always be sent to the client in full resolution, with appropriate width= and + * height= attributes on the <img> tag for the client to do its own scaling. + */ +$wgUseImageResize = true; + +/** * Resizing can be done using PHP's internal image libraries or using * ImageMagick or another third-party converter, e.g. GraphicMagick. * These support more file formats than PHP, which only supports PNG, @@ -861,11 +891,6 @@ $wgUseImageMagick = false; $wgImageMagickConvertCommand = '/usr/bin/convert'; /** - * The identify command shipped with ImageMagick - */ -$wgImageMagickIdentifyCommand = '/usr/bin/identify'; - -/** * Sharpening parameter to ImageMagick */ $wgSharpenParameter = '0x0.4'; @@ -921,7 +946,8 @@ $wgSVGConverters = array( 'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output', 'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output', 'inkscape' => '$path/inkscape -z -w $width -f $input -e $output', - 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', + 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d ' + . '$output $input', 'rsvg' => '$path/rsvg -w $width -h $height $input $output', 'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output', 'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ), @@ -1008,6 +1034,14 @@ $wgTiffThumbnailType = false; $wgThumbnailEpoch = '20030516000000'; /** + * Certain operations are avoided if there were too many recent failures, + * for example, thumbnail generation. Bump this value to invalidate all + * memory of failed operations and thus allow further attempts to resume. + * This is useful when a cause for the failures has been found and fixed. + */ +$wgAttemptFailureEpoch = 1; + +/** * If set, inline scaled images will still produce "<img>" tags ready for * output instead of showing an error message. * @@ -1035,11 +1069,6 @@ $wgGenerateThumbnailOnParse = true; $wgShowArchiveThumbnails = true; /** - * Obsolete, always true, kept for compatibility with extensions - */ -$wgUseImageResize = true; - -/** * If set to true, images that contain certain the exif orientation tag will * be rotated accordingly. If set to null, try to auto-detect whether a scaler * is available that can rotate. @@ -1108,45 +1137,45 @@ $wgAntivirusSetup = array( $wgAntivirusRequired = true; /** - * Determines if the mime type of uploaded files should be checked + * Determines if the MIME type of uploaded files should be checked */ $wgVerifyMimeType = true; /** - * Sets the mime type definition file to use by MimeMagic.php. + * Sets the MIME type definition file to use by MimeMagic.php. * Set to null, to use built-in defaults only. * example: $wgMimeTypeFile = '/etc/mime.types'; */ $wgMimeTypeFile = 'includes/mime.types'; /** - * Sets the mime type info file to use by MimeMagic.php. + * Sets the MIME type info file to use by MimeMagic.php. * Set to null, to use built-in defaults only. */ $wgMimeInfoFile = 'includes/mime.info'; /** - * Sets an external mime detector program. The command must print only - * the mime type to standard output. + * Sets an external MIME detector program. The command must print only + * the MIME type to standard output. * The name of the file to process will be appended to the command given here. - * If not set or NULL, mime_content_type will be used if available. + * If not set or NULL, PHP's fileinfo extension will be used if available. * * @par Example: * @code - * #$wgMimeDetectorCommand = "file -bi"; # use external mime detector (Linux) + * #$wgMimeDetectorCommand = "file -bi"; # use external MIME detector (Linux) * @endcode */ $wgMimeDetectorCommand = null; /** - * Switch for trivial mime detection. Used by thumb.php to disable all fancy + * Switch for trivial MIME detection. Used by thumb.php to disable all fancy * things, because only a few types of images are needed and file extensions * can be trusted. */ $wgTrivialMimeDetection = false; /** - * Additional XML types we can allow via mime-detection. + * Additional XML types we can allow via MIME-detection. * array = ( 'rootElement' => 'associatedMimeType' ) */ $wgXMLMimeTypes = array( @@ -1188,6 +1217,34 @@ $wgThumbLimits = array( ); /** + * When defined, is an array of image widths used as buckets for thumbnail generation. + * The goal is to save resources by generating thumbnails based on reference buckets instead of + * always using the original. This will incur a speed gain but cause a quality loss. + * + * The buckets generation is chained, with each bucket generated based on the above bucket + * when possible. File handlers have to opt into using that feature. For now only BitmapHandler + * supports it. + */ +$wgThumbnailBuckets = null; + +/** + * When using thumbnail buckets as defined above, this sets the minimum distance to the bucket + * above the requested size. The distance represents how many extra pixels of width the bucket + * needs in order to be used as the reference for a given thumbnail. For example, with the + * following buckets: + * + * $wgThumbnailBuckets = array ( 128, 256, 512 ); + * + * and a distance of 50: + * + * $wgThumbnailMinimumBucketDistance = 50; + * + * If we want to render a thumbnail of width 220px, the 512px bucket will be used, + * because 220 + 50 = 270 and the closest bucket bigger than 270px is 512. + */ +$wgThumbnailMinimumBucketDistance = 50; + +/** * Default parameters for the "<gallery>" tag */ $wgGalleryOptions = array( @@ -1251,7 +1308,7 @@ $wgDjvuTxt = null; * Path of the djvutoxml executable * This works like djvudump except much, much slower as of version 3.5. * - * For now we recommend you use djvudump instead. The djvuxml output is + * For now we recommend you use djvudump instead. The djvuxml output is * probably more stable, so we'll switch back to it as soon as they fix * the efficiency problem. * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583 @@ -1265,7 +1322,7 @@ $wgDjvuToXML = null; /** * Shell command for the DJVU post processor - * Default: pnmtopng, since ddjvu generates ppm output + * Default: pnmtojpeg, since ddjvu generates ppm output * Set this to false to output the ppm file directly. */ $wgDjvuPostProcessor = 'pnmtojpeg'; @@ -1284,24 +1341,27 @@ $wgDjvuOutputExtension = 'jpg'; * @{ */ -$serverName = substr( $wgServer, strrpos( $wgServer, '/' ) + 1 ); /** * Site admin email address. + * + * Defaults to "wikiadmin@{$wgServerName}". */ -$wgEmergencyContact = 'wikiadmin@' . $serverName; +$wgEmergencyContact = false; /** * Password reminder email address. * * The address we should use as sender when a user is requesting his password. + * + * Defaults to "apache@{$wgServerName}". */ -$wgPasswordSender = 'apache@' . $serverName; - -unset( $serverName ); # Don't leak local variables to global scope +$wgPasswordSender = false; /** * Password reminder name + * + * @deprecated since 1.23; use the system message 'emailsender' instead. */ $wgPasswordSenderName = 'MediaWiki Mail'; @@ -1352,6 +1412,18 @@ $wgNewPasswordExpiry = 3600 * 24 * 7; $wgUserEmailConfirmationTokenExpiry = 7 * 24 * 60 * 60; /** + * The number of days that a user's password is good for. After this number of days, the + * user will be asked to reset their password. Set to false to disable password expiration. + */ +$wgPasswordExpirationDays = false; + +/** + * If a user's password is expired, the number of seconds when they can still login, + * and cancel their password change, but are sent to the password change form on each login. + */ +$wgPasswordExpireGrace = 3600 * 24 * 7; // 7 days + +/** * SMTP Mode. * * For using a direct (authenticated) SMTP server connection. @@ -1469,7 +1541,7 @@ $wgUsersNotifiedOnAllChanges = array(); $wgDBserver = 'localhost'; /** - * Database port number (for PostgreSQL) + * Database port number (for PostgreSQL and Microsoft SQL Server). */ $wgDBport = 5432; @@ -1495,11 +1567,21 @@ $wgDBtype = 'mysql'; /** * Whether to use SSL in DB connection. + * + * This setting is only used $wgLBFactoryConf['class'] is set to + * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise + * the DBO_SSL flag must be set in the 'flags' option of the database + * connection to achieve the same functionality. */ $wgDBssl = false; /** * Whether to use compression in DB connection. + * + * This setting is only used $wgLBFactoryConf['class'] is set to + * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise + * the DBO_COMPRESS flag must be set in the 'flags' option of the database + * connection to achieve the same functionality. */ $wgDBcompress = false; @@ -1551,7 +1633,7 @@ $wgSQLMode = ''; /** * Mediawiki schema */ -$wgDBmwschema = 'mediawiki'; +$wgDBmwschema = null; /** * To override default SQLite data directory ($docroot/../data) @@ -1582,10 +1664,10 @@ $wgAllDBsAreLocalhost = false; * $wgSharedPrefix is the table prefix for the shared database. It defaults to * $wgDBprefix. * - * @deprecated In new code, use the $wiki parameter to wfGetLB() to access - * remote databases. Using wfGetLB() allows the shared database to reside on - * separate servers to the wiki's own database, with suitable configuration - * of $wgLBFactoryConf. + * @deprecated since 1.21 In new code, use the $wiki parameter to wfGetLB() to + * access remote databases. Using wfGetLB() allows the shared database to + * reside on separate servers to the wiki's own database, with suitable + * configuration of $wgLBFactoryConf. */ $wgSharedDB = null; @@ -1607,8 +1689,13 @@ $wgSharedTables = array( 'user', 'user_properties' ); * - dbname: Default database name * - user: DB user * - password: DB password - * - type: "mysql" or "postgres" - * - load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0 + * - type: DB type + * + * - load: Ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0. + * If this is zero for any given server, no normal query traffic will be + * sent to it. It will be excluded from lag checks in maintenance scripts. + * The only way it can receive traffic is if groupLoads is used. + * * - groupLoads: array of load ratios, the key is the query group name. A query may belong * to several groups, the most specific group defined here is used. * @@ -1623,7 +1710,6 @@ $wgSharedTables = array( 'user', 'user_properties' ); * if available * * - max lag: (optional) Maximum replication lag before a slave will taken out of rotation - * - max threads: (optional) Maximum number of running threads * * These and any other user-defined properties will be assigned to the mLBInfo member * variable of the Database object. @@ -1654,13 +1740,14 @@ $wgDBservers = false; * The class identified here is responsible for reading $wgDBservers, * $wgDBserver, etc., so overriding it may cause those globals to be ignored. * - * The LBFactory_Multi class is provided for this purpose, please see - * includes/db/LBFactory_Multi.php for configuration information. + * The LBFactoryMulti class is provided for this purpose, please see + * includes/db/LBFactoryMulti.php for configuration information. */ -$wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' ); +$wgLBFactoryConf = array( 'class' => 'LBFactorySimple' ); /** * How long to wait for a slave to catch up to the master + * @deprecated since 1.24 */ $wgMasterWaitTimeout = 10; @@ -1690,11 +1777,6 @@ $wgDBerrorLog = false; $wgDBerrorLogTZ = false; /** - * When to give an error message - */ -$wgDBClusterTimeout = 10; - -/** * Scale load balancer polling time so that under overload conditions, the * database server receives a SHOW STATUS query at an average interval of this * many microseconds @@ -1767,6 +1849,11 @@ $wgSlaveLagWarning = 10; */ $wgSlaveLagCritical = 30; +/** + * Use Windows Authentication instead of $wgDBuser / $wgDBpassword for MS SQL Server + */ +$wgDBWindowsAuthentication = false; + /**@}*/ # End of DB settings } /************************************************************************//** @@ -1793,7 +1880,7 @@ $wgCompressRevisions = false; * * CAUTION: Access to database might lead to code execution */ -$wgExternalStores = false; +$wgExternalStores = array(); /** * An array of external MySQL servers. @@ -1806,7 +1893,7 @@ $wgExternalStores = false; * ); * @endcode * - * Used by LBFactory_Simple, may be ignored if $wgLBFactoryConf is set to + * Used by LBFactorySimple, may be ignored if $wgLBFactoryConf is set to * another class. */ $wgExternalServers = array(); @@ -1921,9 +2008,6 @@ $wgCacheDirectory = false; * - CACHE_DB: Store cache objects in the DB * - CACHE_MEMCACHED: MemCached, must specify servers in $wgMemCachedServers * - CACHE_ACCEL: APC, XCache or WinCache - * - CACHE_DBA: Use PHP's DBA extension to store in a DBM-style - * database. This is slow, and is not recommended for - * anything other than debugging. * - (other): A string may be used which identifies a cache * configuration in $wgObjectCaches. * @@ -1976,15 +2060,10 @@ $wgLanguageConverterCacheType = CACHE_ANYTHING; * the value is an associative array of parameters. The "class" parameter is the * class name which will be used. Alternatively, a "factory" parameter may be * given, giving a callable function which will generate a suitable cache object. - * - * The other parameters are dependent on the class used. - * - CACHE_DBA uses $wgTmpDirectory by default. The 'dir' parameter let you - * overrides that. */ $wgObjectCaches = array( CACHE_NONE => array( 'class' => 'EmptyBagOStuff' ), CACHE_DB => array( 'class' => 'SqlBagOStuff', 'table' => 'objectcache' ), - CACHE_DBA => array( 'class' => 'DBABagOStuff' ), CACHE_ANYTHING => array( 'factory' => 'ObjectCache::newAnything' ), CACHE_ACCEL => array( 'factory' => 'ObjectCache::newAccelerator' ), @@ -1999,16 +2078,32 @@ $wgObjectCaches = array( ); /** - * The expiry time for the parser cache, in seconds. - * The default is 86400 (one day). + * Map of bloom filter store names to configuration arrays. + * + * Example: + * $wgBloomFilterStores['main'] = array( + * 'cacheId' => 'main-v1', + * 'class' => 'BloomCacheRedis', + * 'redisServers' => array( '127.0.0.1:6379' ), + * 'redisConfig' => array( 'connectTimeout' => 2 ) + * ); + * + * A primary bloom filter must be created manually. + * Example in eval.php: + * <code> + * BloomCache::get( 'main' )->init( 'shared', 1000000000, .001 ); + * </code> + * The size should be as large as practical given wiki size and resources. + * + * @since 1.24 */ -$wgParserCacheExpireTime = 86400; +$wgBloomFilterStores = array(); /** - * Select which DBA handler <http://www.php.net/manual/en/dba.requirements.php> - * to use as CACHE_DBA backend. + * The expiry time for the parser cache, in seconds. + * The default is 86400 (one day). */ -$wgDBAhandler = 'db3'; +$wgParserCacheExpireTime = 86400; /** * Deprecated alias for $wgSessionsInObjectCache. @@ -2118,6 +2213,12 @@ $wgCachePages = true; $wgCacheEpoch = '20030516000000'; /** + * Directory where GitInfo will look for pre-computed cache files. If false, + * $wgCacheDirectory/gitinfo will be used. + */ +$wgGitInfoCacheDirectory = false; + +/** * Bump this number when changing the global style sheets and JavaScript. * * It should be appended in the query string of static CSS and JS includes, @@ -2129,7 +2230,7 @@ $wgStyleVersion = '303'; /** * This will cache static pages for non-logged-in users to reduce * database traffic on public sites. - * Must set $wgShowIPinHeader = false + * Automatically sets $wgShowIPinHeader = false * ResourceLoader requests to default language and skins are cached * as well as single module requests. */ @@ -2218,7 +2319,7 @@ $wgInvalidateCacheOnLocalSettingsChange = true; * although they are referred to as Squid settings for historical reasons. * * Achieving a high hit ratio with an HTTP proxy requires special - * configuration. See http://www.mediawiki.org/wiki/Manual:Squid_caching for + * configuration. See https://www.mediawiki.org/wiki/Manual:Squid_caching for * more details. * * @{ @@ -2226,7 +2327,7 @@ $wgInvalidateCacheOnLocalSettingsChange = true; /** * Enable/disable Squid. - * See http://www.mediawiki.org/wiki/Manual:Squid_caching + * See https://www.mediawiki.org/wiki/Manual:Squid_caching */ $wgUseSquid = false; @@ -2285,7 +2386,9 @@ $wgSquidServers = array(); /** * As above, except these servers aren't purged on page changes; use to set a - * list of trusted proxies, etc. + * list of trusted proxies, etc. Supports both individual IP addresses and + * CIDR blocks. + * @since 1.23 Supports CIDR ranges */ $wgSquidServersNoPurge = array(); @@ -2369,42 +2472,6 @@ $wgSquidPurgeUseHostHeader = true; $wgHTCPRouting = array(); /** - * @deprecated since 1.22, please use $wgHTCPRouting instead. - * - * Whenever this is set and $wgHTCPRouting evaluates to false, $wgHTCPRouting - * will be set to this value. - * This is merely for back compatibility. - * - * @since 1.20 - */ -$wgHTCPMulticastRouting = null; - -/** - * HTCP multicast address. Set this to a multicast IP address to enable HTCP. - * - * Note that MediaWiki uses the old non-RFC compliant HTCP format, which was - * present in the earliest Squid implementations of the protocol. - * - * This setting is DEPRECATED in favor of $wgHTCPRouting , and kept for - * backwards compatibility only. If $wgHTCPRouting is set, this setting is - * ignored. If $wgHTCPRouting is not set and this setting is, it is used to - * populate $wgHTCPRouting. - * - * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in - * favor of $wgHTCPRouting. - */ -$wgHTCPMulticastAddress = false; - -/** - * HTCP multicast port. - * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in - * favor of $wgHTCPRouting. - * - * @see $wgHTCPMulticastAddress - */ -$wgHTCPPort = 4827; - -/** * HTCP multicast TTL. * @see $wgHTCPRouting */ @@ -2467,6 +2534,21 @@ $wgInterwikiMagic = true; $wgHideInterlanguageLinks = false; /** + * List of additional interwiki prefixes that should be treated as + * interlanguage links (i.e. placed in the sidebar). + * Notes: + * - This will not do anything unless the prefixes are defined in the interwiki + * map. + * - The display text for these custom interlanguage links will be fetched from + * the system message "interlanguage-link-xyz" where xyz is the prefix in + * this array. + * - A friendly name for each site, used for tooltip text, may optionally be + * placed in the system message "interlanguage-link-sitename-xyz" where xyz is + * the prefix in this array. + */ +$wgExtraInterlanguageLinkPrefixes = array(); + +/** * List of language names or overrides for default names in Names.php */ $wgExtraLanguageNames = array(); @@ -2642,11 +2724,6 @@ $wgDisableLangConversion = false; $wgDisableTitleConversion = false; /** - * Whether to enable canonical language links in meta data. - */ -$wgCanonicalLanguageLinks = true; - -/** * Default variant code, if false, the default will be the language code */ $wgDefaultLanguageVariant = false; @@ -2794,6 +2871,23 @@ $wgHtml5 = true; $wgHtml5Version = null; /** + * Temporary variable that allows HTMLForms to be rendered as tables. + * Table based layouts cause various issues when designing for mobile. + * This global allows skins or extensions a means to force non-table based rendering. + * Setting to false forces form components to always render as div elements. + * @since 1.24 + */ +$wgHTMLFormAllowTableFormat = true; + +/** + * Temporary variable that applies MediaWiki UI wherever it can be supported. + * Temporary variable that should be removed when mediawiki ui is more + * stable and change has been communicated. + * @since 1.24 + */ +$wgUseMediaWikiUIEverywhere = false; + +/** * Enabled RDFa attributes for use in wikitext. * NOTE: Interaction with HTML5 is somewhat underspecified. */ @@ -2834,7 +2928,7 @@ $wgWellFormedXml = true; * Normally we wouldn't have to define this in the root "<html>" * element, but IE needs it there in some circumstances. * - * This is ignored if $wgMimeType is set to a non-XML mimetype. + * This is ignored if $wgMimeType is set to a non-XML MIME type. */ $wgXhtmlNamespaces = array(); @@ -2855,11 +2949,6 @@ $wgShowIPinHeader = true; $wgSiteNotice = ''; /** - * A subtitle to add to the tagline, for skins that have it/ - */ -$wgExtraSubtitle = ''; - -/** * If this is set, a "donate" link will appear in the sidebar. Set it to a URL. */ $wgSiteSupportPage = ''; @@ -2873,24 +2962,29 @@ $wgValidateAllHtml = false; /** * Default skin, for new users and anonymous visitors. Registered users may * change this to any one of the other available skins in their preferences. - * This has to be completely lowercase; see the "skins" directory for the list - * of available skins. */ $wgDefaultSkin = 'vector'; /** - * Specify the name of a skin that should not be presented in the list of - * available skins. Use for blacklisting a skin which you do not want to - * remove from the .../skins/ directory + * Fallback skin used when the skin defined by $wgDefaultSkin can't be found. + * + * @since 1.24 */ -$wgSkipSkin = ''; +$wgFallbackSkin = 'fallback'; /** - * Array for more like $wgSkipSkin. + * Specify the names of skins that should not be presented in the list of + * available skins in user preferences. If you want to remove a skin entirely, + * remove it from the skins/ directory and its entry from LocalSettings.php. */ $wgSkipSkins = array(); /** + * @deprecated since 1.23; use $wgSkipSkins instead + */ +$wgSkipSkin = ''; + +/** * Allow user Javascript page? * This enables a lot of neat customizations, but may * increase security risk to users and server load. @@ -3010,7 +3104,8 @@ $wgFooterIcons = array( ), "poweredby" => array( "mediawiki" => array( - "src" => null, // Defaults to "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" + // src defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png" + "src" => null, "url" => "//www.mediawiki.org/", "alt" => "Powered by MediaWiki", ) @@ -3026,33 +3121,11 @@ $wgFooterIcons = array( $wgUseCombinedLoginLink = false; /** - * Search form look for Vector skin only. - * - true = use an icon search button - * - false = use Go & Search buttons - */ -$wgVectorUseSimpleSearch = true; - -/** - * Watch and unwatch as an icon rather than a link for Vector skin only. - * - true = use an icon watch/unwatch button - * - false = use watch/unwatch text link - */ -$wgVectorUseIconWatch = true; - -/** * Display user edit counts in various prominent places. */ $wgEdititis = false; /** - * Better directionality support (bug 6100 and related). - * Removed in 1.18, still kept here for LiquidThreads backwards compatibility. - * - * @deprecated since 1.18 - */ -$wgBetterDirectionality = true; - -/** * Some web hosts attempt to rewrite all responses with a 404 (not found) * status code, mangling or hiding MediaWiki's output. If you are using such a * host, you should start looking for a better one. While you're doing that, @@ -3083,6 +3156,14 @@ $wgShowRollbackEditCount = 10; */ $wgEnableCanonicalServerLink = false; +/** + * When OutputHandler is used, mangle any output that contains + * <cross-domain-policy>. Without this, an attacker can send their own + * cross-domain policy unless it is prevented by the crossdomain.xml file at + * the domain root. + */ +$wgMangleFlashPolicy = true; + /** @} */ # End of output format settings } /*************************************************************************//** @@ -3110,16 +3191,118 @@ $wgEnableCanonicalServerLink = false; $wgResourceModules = array(); /** + * Skin-specific styles for resource modules. + * + * These are later added to the 'skinStyles' list of the existing module. The 'styles' list can + * not be modified or disabled. + * + * For example, here is a module "bar" and how skin Foo would provide additional styles for it. + * + * @par Example: + * @code + * $wgResourceModules['bar'] = array( + * 'scripts' => 'resources/bar/bar.js', + * 'styles' => 'resources/bar/main.css', + * ); + * + * $wgResourceModuleSkinStyles['foo'] = array( + * 'bar' => 'skins/Foo/bar.css', + * ); + * @endcode + * + * This is mostly equivalent to: + * + * @par Equivalent: + * @code + * $wgResourceModules['bar'] = array( + * 'scripts' => 'resources/bar/bar.js', + * 'styles' => 'resources/bar/main.css', + * 'skinStyles' => array( + * 'foo' => skins/Foo/bar.css', + * ), + * ); + * @endcode + * + * If the module already defines its own entry in `skinStyles` for a given skin, then + * $wgResourceModuleSkinStyles is ignored. + * + * If a module defines a `skinStyles['default']` the skin may want to extend that instead + * of replacing them. This can be done using the `+` prefix. + * + * @par Example: + * @code + * $wgResourceModules['bar'] = array( + * 'scripts' => 'resources/bar/bar.js', + * 'styles' => 'resources/bar/basic.css', + * 'skinStyles' => array( + * 'default' => 'resources/bar/additional.css', + * ), + * ); + * // Note the '+' character: + * $wgResourceModuleSkinStyles['+foo'] = array( + * 'bar' => 'skins/Foo/bar.css', + * ); + * @endcode + * + * This is mostly equivalent to: + * + * @par Equivalent: + * @code + * $wgResourceModules['bar'] = array( + * 'scripts' => 'resources/bar/bar.js', + * 'styles' => 'resources/bar/basic.css', + * 'skinStyles' => array( + * 'default' => 'resources/bar/additional.css', + * 'foo' => array( + * 'resources/bar/additional.css', + * 'skins/Foo/bar.css', + * ), + * ), + * ); + * @endcode + * + * In other words, as a module author, use the `styles` list for stylesheets that may not be + * disabled by a skin. To provide default styles that may be extended or replaced, + * use `skinStyles['default']`. + * + * As with $wgResourceModules, paths default to being relative to the MediaWiki root. + * You should always provide a localBasePath and remoteBasePath (or remoteExtPath/remoteSkinPath). + * Either for all skin styles at once (first example below) or for each module separately (second + * example). + * + * @par Example: + * @code + * $wgResourceModuleSkinStyles['foo'] = array( + * 'bar' => 'bar.css', + * 'quux' => 'quux.css', + * 'remoteSkinPath' => 'Foo', + * 'localBasePath' => __DIR__, + * ); + * + * $wgResourceModuleSkinStyles['foo'] = array( + * 'bar' => array( + * 'bar.css', + * 'remoteSkinPath' => 'Foo', + * 'localBasePath' => __DIR__, + * ), + * 'quux' => array( + * 'quux.css', + * 'remoteSkinPath' => 'Foo', + * 'localBasePath' => __DIR__, + * ), + * ); + * @endcode + */ +$wgResourceModuleSkinStyles = array(); + +/** * Extensions should register foreign module sources here. 'local' is a * built-in source that is not in this array, but defined by * ResourceLoader::__construct() so that it cannot be unset. * * @par Example: * @code - * $wgResourceLoaderSources['foo'] = array( - * 'loadScript' => 'http://example.org/w/load.php', - * 'apiScript' => 'http://example.org/w/api.php' - * ); + * $wgResourceLoaderSources['foo'] = 'http://example.org/w/load.php'; * @endcode */ $wgResourceLoaderSources = array(); @@ -3132,14 +3315,23 @@ $wgResourceBasePath = null; /** * Maximum time in seconds to cache resources served by the resource loader. + * Used to set last modified headers (max-age/s-maxage). + * + * Following options to distinguish: + * - versioned: Used for modules with a version, because changing version + * numbers causes cache misses. This normally has a long expiry time. + * - unversioned: Used for modules without a version to propagate changes + * quickly to clients. Also used for modules with errors to recover quickly. + * This normally has a short expiry time. * - * @todo Document array structure + * Expiry time for the options to distinguish: + * - server: Squid/Varnish but also any other public proxy cache between the + * client and MediaWiki. + * - client: On the client side (e.g. in the browser cache). */ $wgResourceLoaderMaxage = array( 'versioned' => array( - // Squid/Varnish but also any other public proxy cache between the client and MediaWiki 'server' => 30 * 24 * 60 * 60, // 30 days - // On the client side (e.g. in the browser cache). 'client' => 30 * 24 * 60 * 60, // 30 days ), 'unversioned' => array( @@ -3182,6 +3374,15 @@ $wgResourceLoaderMinifierMaxLineLength = 1000; $wgIncludeLegacyJavaScript = true; /** + * Whether to include the jQuery Migrate library, which lets legacy JS that + * requires jQuery 1.8.x to work and breaks with 1.9.x+. + * + * @since 1.24 + * @deprecated since 1.24, to be removed in 1.25 + */ +$wgIncludejQueryMigrate = false; + +/** * Whether to preload the mediawiki.util module as blocking module in the top * queue. * @@ -3270,12 +3471,14 @@ $wgResourceLoaderValidateStaticJS = false; $wgResourceLoaderExperimentalAsyncLoading = false; /** - * Global LESS variables. An associative array binding variable names to CSS - * string values. + * Global LESS variables. An associative array binding variable names to + * LESS code snippets representing their values. * - * Because the hashed contents of this array are used to construct the cache key - * that ResourceLoader uses to look up LESS compilation results, updating this - * array can be used to deliberately invalidate the set of cached results. + * Adding an item here is equivalent to writing `@variable: value;` + * at the beginning of all your .less files, with all the consequences. + * In particular, string values must be escaped and quoted. + * + * Changes to LESS variables do not trigger cache invalidation. * * @par Example: * @code @@ -3293,17 +3496,13 @@ $wgResourceLoaderLESSVars = array(); * Custom LESS functions. An associative array mapping function name to PHP * callable. * - * Changes to LESS functions do not trigger cache invalidation. If you update - * the behavior of a LESS function and need to invalidate stale compilation - * results, you can touch one of values in $wgResourceLoaderLESSVars, as - * documented above. + * Changes to LESS functions do not trigger cache invalidation. * * @since 1.22 + * @deprecated since 1.24 Questionable usefulness and problematic to support, + * will be removed in the future. */ -$wgResourceLoaderLESSFunctions = array( - 'embeddable' => 'ResourceLoaderLESSFunctions::embeddable', - 'embed' => 'ResourceLoaderLESSFunctions::embed', -); +$wgResourceLoaderLESSFunctions = array(); /** * Default import paths for LESS modules. LESS files referenced in @import @@ -3319,10 +3518,26 @@ $wgResourceLoaderLESSFunctions = array( * @since 1.22 */ $wgResourceLoaderLESSImportPaths = array( - "$IP/resources/mediawiki.less/", + "$IP/resources/src/mediawiki.less/", ); /** + * Whether ResourceLoader should attempt to persist modules in localStorage on + * browsers that support the Web Storage API. + * + * @since 1.23 - Client-side module persistence is experimental. Exercise care. + */ +$wgResourceLoaderStorageEnabled = false; + +/** + * Cache version for client-side ResourceLoader module storage. You can trigger + * invalidation of the contents of the module store by incrementing this value. + * + * @since 1.23 + */ +$wgResourceLoaderStorageVersion = 1; + +/** * Whether to allow site-wide CSS (MediaWiki:Common.css and friends) on * restricted pages like Special:UserLogin or Special:Preferences where * JavaScript is disabled for security reasons. As it is possible to @@ -3335,14 +3550,6 @@ $wgResourceLoaderLESSImportPaths = array( */ $wgAllowSiteCSSOnRestrictedPages = false; -/** - * When OutputHandler is used, mangle any output that contains - * <cross-domain-policy>. Without this, an attacker can send their own - * cross-domain policy unless it is prevented by the crossdomain.xml file at - * the domain root. - */ -$wgMangleFlashPolicy = true; - /** @} */ # End of resource loader settings } /*************************************************************************//** @@ -3452,10 +3659,22 @@ $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+"; /** * The interwiki prefix of the current wiki, or false if it doesn't have one. + * + * @deprecated since 1.23; use $wgLocalInterwikis instead */ $wgLocalInterwiki = false; /** + * Array for multiple $wgLocalInterwiki values, in case there are several + * interwiki prefixes that point to the current wiki. If $wgLocalInterwiki is + * set, its value is prepended to this array, for backwards compatibility. + * + * Note, recent changes feeds use only the first entry in this array (or + * $wgLocalInterwiki, if it is set). See $wgRCFeeds + */ +$wgLocalInterwikis = array(); + +/** * Expiry time for cache of interwiki table */ $wgInterwikiExpiry = 10800; @@ -3557,6 +3776,30 @@ $wgNamespacesWithSubpages = array( ); /** + * Array holding default tracking category names. + * + * Array contains the system messages for each tracking category. + * Tracking categories allow pages with certain characteristics to be tracked. + * It works by adding any such page to a category automatically. + * + * A message with the suffix '-desc' should be added as a description message + * to have extra information on Special:TrackingCategories. + * + * @since 1.23 + */ +$wgTrackingCategories = array( + 'index-category', + 'noindex-category', + 'expensive-parserfunction-category', + 'post-expand-template-argument-category', + 'post-expand-template-inclusion-category', + 'hidden-category-category', + 'broken-file-category', + 'node-count-exceeded-category', + 'expansion-depth-exceeded-category', +); + +/** * Array of namespaces which can be deemed to contain valid "content", as far * as the site statistics are concerned. Useful if additional namespaces also * contain "content" which should be considered when generating a count of the @@ -3653,36 +3896,20 @@ $wgMaxTemplateDepth = 40; $wgMaxPPExpandDepth = 40; /** - * The external URL protocols + * URL schemes that should be recognized as valid by wfParseUrl(). + * + * WARNING: Do not add 'file:' to this or internal file links will be broken. + * Instead, if you want to support file links, add 'file://'. The same applies + * to any other protocols with the same name as a namespace. See bug #44011 for + * more information. + * + * @see wfParseUrl */ $wgUrlProtocols = array( - 'http://', - 'https://', - 'ftp://', - 'ftps://', // If we allow ftp:// we should allow the secure version. - 'ssh://', - 'sftp://', // SFTP > FTP - 'irc://', - 'ircs://', // @bug 28503 - 'xmpp:', // Another open communication protocol - 'sip:', - 'sips:', - 'gopher://', - 'telnet://', // Well if we're going to support the above.. -ævar - 'nntp://', // @bug 3808 RFC 1738 - 'worldwind://', - 'mailto:', - 'tel:', // If we can make emails linkable, why not phone numbers? - 'sms:', // Likewise this is standardized too - 'news:', - 'svn://', - 'git://', - 'mms://', - 'bitcoin:', // Even registerProtocolHandler whitelists this along with mailto: - 'magnet:', // No reason to reject torrents over magnet: when they're allowed over http:// - 'urn:', // Allow URNs to be used in Microdata/RDFa <link ... href="urn:...">s - 'geo:', // urls define geo locations, they're useful in Microdata/RDFa and for coordinates - '//', // for protocol-relative URLs + 'bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://', + 'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:', + 'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://', + 'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:', '//' ); /** @@ -3809,13 +4036,16 @@ $wgNoFollowNsExceptions = array(); * (or any subdomains) will not be set to rel="nofollow" regardless of the * value of $wgNoFollowLinks. For instance: * - * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org' ); + * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org', + * 'mediawiki.org' ); * * This would add rel="nofollow" to links to de.wikipedia.org, but not * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org, * etc. + * + * Defaults to mediawiki.org for the links included in the software by default. */ -$wgNoFollowDomainExceptions = array(); +$wgNoFollowDomainExceptions = array( 'mediawiki.org' ); /** * Allow DISPLAYTITLE to change title display @@ -3869,23 +4099,14 @@ $wgTranscludeCacheExpiry = 3600; * - 'any': all pages as considered as valid articles * - 'comma': the page must contain a comma to be considered valid * - 'link': the page must contain a [[wiki link]] to be considered valid - * - null: the value will be set at run time depending on $wgUseCommaCount: - * if $wgUseCommaCount is false, it will be 'link', if it is true - * it will be 'comma' * - * See also See http://www.mediawiki.org/wiki/Manual:Article_count + * See also See https://www.mediawiki.org/wiki/Manual:Article_count * * Retroactively changing this variable will not affect the existing count, * to update it, you will need to run the maintenance/updateArticleCount.php * script. */ -$wgArticleCountMethod = null; - -/** - * Backward compatibility setting, will set $wgArticleCountMethod if it is null. - * @deprecated since 1.18; use $wgArticleCountMethod instead - */ -$wgUseCommaCount = false; +$wgArticleCountMethod = 'link'; /** * wgHitcounterUpdateFreq sets how often page counters should be updated, higher @@ -3914,6 +4135,7 @@ $wgActiveUserDays = 30; /** * For compatibility with old installations set to false + * @deprecated since 1.24 will be removed in future */ $wgPasswordSalt = true; @@ -3924,6 +4146,72 @@ $wgPasswordSalt = true; $wgMinimalPasswordLength = 1; /** + * Specifies if users should be sent to a password-reset form on login, if their + * password doesn't meet the requirements of User::isValidPassword(). + * @since 1.23 + */ +$wgInvalidPasswordReset = true; + +/** + * Default password type to use when hashing user passwords + * + * @since 1.24 + */ +$wgPasswordDefault = 'pbkdf2'; + +/** + * Configuration for built-in password types. Maps the password type + * to an array of options. The 'class' option is the Password class to + * use. All other options are class-dependent. + * + * An advanced example: + * @code + * $wgPasswordConfig['bcrypt-peppered'] = array( + * 'class' => 'EncryptedPassword', + * 'underlying' => 'bcrypt', + * 'secrets' => array(), + * 'cipher' => MCRYPT_RIJNDAEL_256, + * 'mode' => MCRYPT_MODE_CBC, + * 'cost' => 5, + * ); + * @endcode + * + * @since 1.24 + */ +$wgPasswordConfig = array( + 'A' => array( + 'class' => 'MWOldPassword', + ), + 'B' => array( + 'class' => 'MWSaltedPassword', + ), + 'pbkdf2-legacyA' => array( + 'class' => 'LayeredParameterizedPassword', + 'types' => array( + 'A', + 'pbkdf2', + ), + ), + 'pbkdf2-legacyB' => array( + 'class' => 'LayeredParameterizedPassword', + 'types' => array( + 'B', + 'pbkdf2', + ), + ), + 'bcrypt' => array( + 'class' => 'BcryptPassword', + 'cost' => 9, + ), + 'pbkdf2' => array( + 'class' => 'Pbkdf2Password', + 'algo' => 'sha256', + 'cost' => '10000', + 'length' => '128', + ), +); + +/** * Whether to allow password resets ("enter some identifying data, and we'll send an email * with a temporary password you can use to get back into the account") identified by * various bits of data. Setting all of these to false (or the whole variable to false) @@ -3963,8 +4251,8 @@ $wgReservedUsernames = array( /** * Settings added to this array will override the default globals for the user * preferences used by anonymous visitors and newly created accounts. - * For instance, to disable section editing links: - * $wgDefaultUserOptions ['editsection'] = 0; + * For instance, to disable editing on double clicks: + * $wgDefaultUserOptions ['editondblclick'] = 0; */ $wgDefaultUserOptions = array( 'ccmeonemails' => 0, @@ -3972,15 +4260,13 @@ $wgDefaultUserOptions = array( 'date' => 'default', 'diffonly' => 0, 'disablemail' => 0, - 'disablesuggest' => 0, 'editfont' => 'default', 'editondblclick' => 0, - 'editsection' => 1, 'editsectiononrightclick' => 0, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, - 'enotifwatchlistpages' => 0, + 'enotifwatchlistpages' => 1, 'extendwatchlist' => 0, 'fancysig' => 0, 'forceeditsummary' => 0, @@ -3988,34 +4274,28 @@ $wgDefaultUserOptions = array( 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, - 'justify' => 0, 'math' => 1, 'minordefault' => 0, 'newpageshidepatrolled' => 0, - 'nocache' => 0, - 'noconvertlink' => 0, + 'nickname' => '', 'norollbackdiff' => 0, 'numberheadings' => 0, 'previewonfirst' => 0, 'previewontop' => 1, 'rcdays' => 7, 'rclimit' => 50, - 'rememberpassword' => 0, 'rows' => 25, - 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, - 'showtoc' => 1, 'showtoolbar' => 1, 'skin' => false, 'stubthreshold' => 0, - 'thumbsize' => 2, + 'thumbsize' => 5, 'underline' => 2, 'uselivepreview' => 0, 'usenewrc' => 0, - 'vector-simplesearch' => 1, - 'watchcreations' => 0, - 'watchdefault' => 0, + 'watchcreations' => 1, + 'watchdefault' => 1, 'watchdeletion' => 0, 'watchlistdays' => 3.0, 'watchlisthideanons' => 0, @@ -4025,6 +4305,7 @@ $wgDefaultUserOptions = array( 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchmoves' => 0, + 'watchrollback' => 0, 'wllimit' => 250, 'useeditwarning' => 1, 'prefershttps' => 1, @@ -4126,7 +4407,7 @@ $wgBlockDisablesLogin = false; * * @note Also that this will only protect _pages in the wiki_. Uploaded files * will remain readable. You can use img_auth.php to protect uploaded files, - * see http://www.mediawiki.org/wiki/Manual:Image_Authorization + * see https://www.mediawiki.org/wiki/Manual:Image_Authorization */ $wgWhitelistRead = false; @@ -4211,6 +4492,7 @@ $wgGroupPermissions['*']['editmyoptions'] = true; $wgGroupPermissions['user']['move'] = true; $wgGroupPermissions['user']['move-subpages'] = true; $wgGroupPermissions['user']['move-rootuserpages'] = true; // can move root userpages +$wgGroupPermissions['user']['move-categorypages'] = true; $wgGroupPermissions['user']['movefile'] = true; $wgGroupPermissions['user']['read'] = true; $wgGroupPermissions['user']['edit'] = true; @@ -4258,6 +4540,7 @@ $wgGroupPermissions['sysop']['importupload'] = true; $wgGroupPermissions['sysop']['move'] = true; $wgGroupPermissions['sysop']['move-subpages'] = true; $wgGroupPermissions['sysop']['move-rootuserpages'] = true; +$wgGroupPermissions['sysop']['move-categorypages'] = true; $wgGroupPermissions['sysop']['patrol'] = true; $wgGroupPermissions['sysop']['autopatrol'] = true; $wgGroupPermissions['sysop']['protect'] = true; @@ -4279,8 +4562,9 @@ $wgGroupPermissions['sysop']['noratelimit'] = true; $wgGroupPermissions['sysop']['movefile'] = true; $wgGroupPermissions['sysop']['unblockself'] = true; $wgGroupPermissions['sysop']['suppressredirect'] = true; +#$wgGroupPermissions['sysop']['pagelang'] = true; #$wgGroupPermissions['sysop']['upload_by_url'] = true; -#$wgGroupPermissions['sysop']['mergehistory'] = true; +$wgGroupPermissions['sysop']['mergehistory'] = true; // Permission to change users' group assignments $wgGroupPermissions['bureaucrat']['userrights'] = true; @@ -4296,6 +4580,8 @@ $wgGroupPermissions['bureaucrat']['noratelimit'] = true; #$wgGroupPermissions['suppress']['hideuser'] = true; // To hide revisions/log items from users and Sysops #$wgGroupPermissions['suppress']['suppressrevision'] = true; +// To view revisions/log items hidden from users and Sysops +#$wgGroupPermissions['suppress']['viewsuppressed'] = true; // For private suppression log access #$wgGroupPermissions['suppress']['suppressionlog'] = true; @@ -4555,6 +4841,15 @@ $wgAvailableRights = array(); $wgDeleteRevisionsLimit = 0; /** + * The maximum number of edits a user can have and + * can still be hidden by users with the hideuser permission. + * This is limited for performance reason. + * Set to false to disable the limit. + * @since 1.23 + */ +$wgHideUserContribLimit = 1000; + +/** * Number of accounts each IP address may create, 0 to disable. * * @warning Requires memcached @@ -4587,12 +4882,6 @@ $wgSummarySpamRegex = array(); $wgEnableDnsBlacklist = false; /** - * @deprecated since 1.17 Use $wgEnableDnsBlacklist instead, only kept for - * backward compatibility. - */ -$wgEnableSorbs = false; - -/** * List of DNS blacklists to use, if $wgEnableDnsBlacklist is true. * * This is an array of either a URL or an array with the URL and a key (should @@ -4618,12 +4907,6 @@ $wgEnableSorbs = false; $wgDnsBlacklistUrls = array( 'http.dnsbl.sorbs.net.' ); /** - * @deprecated since 1.17 Use $wgDnsBlacklistUrls instead, only kept for - * backward compatibility. - */ -$wgSorbsUrl = array(); - -/** * Proxy whitelist, list of addresses that are assumed to be non-proxy despite * what the other methods might say. */ @@ -4690,10 +4973,19 @@ $wgRateLimits = array( 'ip' => null, 'subnet' => null, ), + 'renderfile-nonstandard' => array( // same as above but for non-standard thumbnails + 'anon' => null, + 'user' => null, + 'newbie' => null, + 'ip' => null, + 'subnet' => null, + ), ); /** * Set to a filename to log rate limiter hits. + * + * @deprecated since 1.23, use $wgDebugLogGroups['ratelimit'] instead */ $wgRateLimitLog = null; @@ -4746,11 +5038,6 @@ $wgSecretKey = false; */ $wgProxyList = array(); -/** - * @deprecated since 1.14 - */ -$wgProxyKey = false; - /** @} */ # end of proxy scanner settings /************************************************************************//** @@ -4759,7 +5046,7 @@ $wgProxyKey = false; */ /** - * Default cookie expiration time. Setting to 0 makes all cookies session-only. + * Default cookie lifetime, in seconds. Setting to 0 makes all cookies session-only. */ $wgCookieExpiration = 180 * 86400; @@ -4806,17 +5093,6 @@ $wgCookiePrefix = false; $wgCookieHttpOnly = true; /** - * If the requesting browser matches a regex in this blacklist, we won't - * send it cookies with HttpOnly mode, even if $wgCookieHttpOnly is on. - */ -$wgHttpOnlyBlacklist = array( - // Internet Explorer for Mac; sometimes the cookies work, sometimes - // they don't. It's difficult to predict, as combinations of path - // and expiration options affect its parsing. - '/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/', -); - -/** * A list of cookies that vary the cache (for use by extensions) */ $wgCacheVaryCookies = array(); @@ -4852,7 +5128,7 @@ $wgUseTeX = false; */ /** - * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug + * Filename for debug logging. See https://www.mediawiki.org/wiki/How_to_debug * The debug log file should be not be publicly accessible if it is used, as it * may contain private data. */ @@ -4895,15 +5171,48 @@ $wgDebugComments = false; $wgDebugDBTransactions = false; /** - * Write SQL queries to the debug log + * Write SQL queries to the debug log. + * + * This setting is only used $wgLBFactoryConf['class'] is set to + * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise + * the DBO_DEBUG flag must be set in the 'flags' option of the database + * connection to achieve the same functionality. */ $wgDebugDumpSql = false; /** - * Set to an array of log group keys to filenames. + * Trim logged SQL queries to this many bytes. Set 0/false/null to do no + * trimming. + * @since 1.24 + */ +$wgDebugDumpSqlLength = 500; + +/** + * Map of string log group names to log destinations. + * * If set, wfDebugLog() output for that group will go to that file instead * of the regular $wgDebugLogFile. Useful for enabling selective logging * in production. + * + * Log destinations may be one of the following: + * - false to completely remove from the output, including from $wgDebugLogFile. + * - string values specifying a filename or URI. + * - associative array mapping 'destination' key to the desired filename or URI. + * The associative array may also contain a 'sample' key with an integer value, + * specifying a sampling factor. + * + * @par Example: + * @code + * $wgDebugLogGroups['redis'] = '/var/log/mediawiki/redis.log'; + * @endcode + * + * @par Advanced example: + * @code + * $wgDebugLogGroups['memcached'] = ( + * 'destination' => '/var/log/mediawiki/memcached.log', + * 'sample' => 1000, // log 1 message out of every 1,000. + * ); + * @endcode */ $wgDebugLogGroups = array(); @@ -4947,6 +5256,11 @@ $wgShowExceptionDetails = false; /** * If true, show a backtrace for database errors + * + * @note This setting only applies when connection errors and query errors are + * reported in the normal manner. $wgShowExceptionDetails applies in other cases, + * including those in which an uncaught exception is thrown from within the + * exception handler. */ $wgShowDBErrorBacktrace = false; @@ -4987,20 +5301,11 @@ $wgProfileLimit = 0.0; /** * Don't put non-profiling info into log file - */ -$wgProfileOnly = false; - -/** - * Log sums from profiling into "profiling" table in db. * - * You have to create a 'profiling' table in your database before using - * this feature. Run set $wgProfileToDatabase to true in - * LocalSettings.php and run maintenance/update.php or otherwise - * manually add patch-profiling.sql to your database. - * - * To enable profiling, edit StartProfiler.php + * @deprecated since 1.23, set the log file in + * $wgDebugLogGroups['profileoutput'] instead. */ -$wgProfileToDatabase = false; +$wgProfileOnly = false; /** * If true, print a raw call tree instead of per-function report @@ -5016,7 +5321,8 @@ $wgProfilePerHost = false; * Host for UDP profiler. * * The host should be running a daemon which can be obtained from MediaWiki - * Subversion at: http://svn.wikimedia.org/svnroot/mediawiki/trunk/udpprofile + * Git at: + * http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile */ $wgUDPProfilerHost = '127.0.0.1'; @@ -5028,9 +5334,9 @@ $wgUDPProfilerPort = '3811'; /** * Format string for the UDP profiler. The UDP profiler invokes sprintf() with - * (profile id, count, cpu, cpu_sq, real, real_sq, entry name) as arguments. - * You can use sprintf's argument numbering/swapping capability to repeat, - * re-order or omit fields. + * (profile id, count, cpu, cpu_sq, real, real_sq, entry name, memory) as + * arguments. You can use sprintf's argument numbering/swapping capability to + * repeat, re-order or omit fields. * * @see $wgStatsFormatString * @since 1.22 @@ -5038,11 +5344,6 @@ $wgUDPProfilerPort = '3811'; $wgUDPProfilerFormatString = "%s - %d %f %f %f %f %s\n"; /** - * Detects non-matching wfProfileIn/wfProfileOut calls - */ -$wgDebugProfiling = false; - -/** * Output debug message on every wfProfileIn/wfProfileOut */ $wgDebugFunctionEntry = false; @@ -5112,21 +5413,6 @@ $wgParserTestFiles = array( ); /** - * If configured, specifies target CodeReview installation to send test - * result data from 'parserTests.php --upload' - * - * Something like this: - * $wgParserTestRemote = array( - * 'api-url' => 'http://www.mediawiki.org/w/api.php', - * 'repo' => 'MediaWiki', - * 'suite' => 'ParserTests', - * 'path' => '/trunk/phase3', // not used client-side; for reference - * 'secret' => 'qmoicj3mc4mcklmqw', // Shared secret used in HMAC validation - * ); - */ -$wgParserTestRemote = false; - -/** * Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit). */ $wgEnableJavaScriptTest = false; @@ -5190,18 +5476,6 @@ $wgAdvancedSearchHighlighting = false; $wgSearchHighlightBoundaries = '[\p{Z}\p{P}\p{C}]'; /** - * Set to true to have the search engine count total - * search matches to present in the Special:Search UI. - * Not supported by every search engine shipped with MW. - * - * This could however be slow on larger wikis, and is pretty flaky - * with the current title vs content split. Recommend avoiding until - * that's been worked out cleanly; but this may aid in testing the - * search UI and API to confirm that the result count works. - */ -$wgCountTotalSearchHits = false; - -/** * Template for OpenSearch suggestions, defaults to API action=opensearch * * Sites with heavy load would typically have these point to a custom @@ -5220,6 +5494,12 @@ $wgOpenSearchTemplate = false; $wgEnableOpenSearchSuggest = true; /** + * Integer defining default number of entries to show on + * OpenSearch call. + */ +$wgOpenSearchDefaultLimit = 10; + +/** * Expiry time for search suggestion responses */ $wgSearchSuggestCacheExpiry = 1200; @@ -5244,25 +5524,6 @@ $wgNamespacesToBeSearchedDefault = array( ); /** - * Namespaces to be searched when user clicks the "Help" tab - * on Special:Search. - * - * Same format as $wgNamespacesToBeSearchedDefault. - */ -$wgNamespacesToBeSearchedHelp = array( - NS_PROJECT => true, - NS_HELP => true, -); - -/** - * If set to true the 'searcheverything' preference will be effective only for - * logged-in users. - * Useful for big wikis to maintain different search profiles for anonymous and - * logged-in users. - */ -$wgSearchEverythingOnlyLoggedIn = false; - -/** * Disable the internal MySQL-based search, to allow it to be * implemented by an extension instead. */ @@ -5350,11 +5611,6 @@ $wgPreviewOnOpenNamespaces = array( ); /** - * Go button goes straight to the edit screen if the article doesn't exist. - */ -$wgGoToEdit = false; - -/** * Enable the UniversalEditButton for browsers that support it * (currently only Firefox with an extension) * See http://universaleditbutton.org for more background information @@ -5391,13 +5647,6 @@ if ( !isset( $wgCommandLineMode ) ) { $wgCommandLineDarkBg = false; /** - * Array for extensions to register their maintenance scripts with the - * system. The key is the name of the class and the value is the full - * path to the file - */ -$wgMaintenanceScripts = array(); - -/** * Set this to a string to put the wiki into read-only mode. The text will be * used as an explanation to users. * @@ -5445,9 +5694,10 @@ $wgGitBin = '/usr/bin/git'; * @since 1.20 */ $wgGitRepositoryViewers = array( - 'https://gerrit.wikimedia.org/r/p/(.*)' => 'https://git.wikimedia.org/commit/%r/%H', - 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' - => 'https://git.wikimedia.org/commit/%r/%H', + 'https://(?:[a-z0-9_]+@)?gerrit.wikimedia.org/r/(?:p/)?(.*)' => + 'https://git.wikimedia.org/tree/%r/%H', + 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' => + 'https://git.wikimedia.org/tree/%r/%H', ); /** @} */ # End of maintenance } @@ -5474,74 +5724,44 @@ $wgRCMaxAge = 13 * 7 * 24 * 3600; $wgRCFilterByAge = false; /** - * List of Days and Limits options to list in the Special:Recentchanges and + * List of Limits options to list in the Special:Recentchanges and * Special:Recentchangeslinked pages. */ $wgRCLinkLimits = array( 50, 100, 250, 500 ); -$wgRCLinkDays = array( 1, 3, 7, 14, 30 ); - -/** - * Send recent changes updates via UDP. The updates will be formatted for IRC. - * Set this to the IP address of the receiver. - * - * @deprecated since 1.22, use $wgRCFeeds - */ -$wgRC2UDPAddress = false; - -/** - * Port number for RC updates - * - * @deprecated since 1.22, use $wgRCFeeds - */ -$wgRC2UDPPort = false; - -/** - * Prefix to prepend to each UDP packet. - * This can be used to identify the wiki. A script is available called - * mxircecho.py which listens on a UDP port, and uses a prefix ending in a - * tab to identify the IRC channel to send the log line to. - * - * @deprecated since 1.22, use $wgRCFeeds - */ -$wgRC2UDPPrefix = ''; - -/** - * If this is set to true, $wgLocalInterwiki will be prepended to links in the - * IRC feed. If this is set to a string, that string will be used as the prefix. - * - * @deprecated since 1.22, use $wgRCFeeds - */ -$wgRC2UDPInterwikiPrefix = false; /** - * Set to true to omit "bot" edits (by users with the bot permission) from the - * UDP feed. - * - * @deprecated since 1.22, use $wgRCFeeds + * List of Days options to list in the Special:Recentchanges and + * Special:Recentchangeslinked pages. */ -$wgRC2UDPOmitBots = false; +$wgRCLinkDays = array( 1, 3, 7, 14, 30 ); /** * Destinations to which notifications about recent changes * should be sent. * - * As of MediaWiki 1.22, the only supported 'engine' parameter option in core - * is 'UDPRCFeedEngine', which is used to send recent changes over UDP to the - * specified server. + * As of MediaWiki 1.22, there are 2 supported 'engine' parameter option in core: + * * 'UDPRCFeedEngine', which is used to send recent changes over UDP to the + * specified server. + * * 'RedisPubSubFeedEngine', which is used to send recent changes to Redis. + * * The common options are: * * 'uri' -- the address to which the notices are to be sent. * * 'formatter' -- the class name (implementing RCFeedFormatter) which will - * produce the text to send. + * produce the text to send. This can also be an object of the class. * * 'omit_bots' -- whether the bot edits should be in the feed + * * 'omit_anon' -- whether anonymous edits should be in the feed + * * 'omit_user' -- whether edits by registered users should be in the feed + * * 'omit_minor' -- whether minor edits should be in the feed + * * 'omit_patrolled' -- whether patrolled edits should be in the feed + * * The IRC-specific options are: * * 'add_interwiki_prefix' -- whether the titles should be prefixed with - * $wgLocalInterwiki. + * the first entry in the $wgLocalInterwikis array (or the value of + * $wgLocalInterwiki, if set) + * * The JSON-specific options are: * * 'channel' -- if set, the 'channel' parameter is also set in JSON values. * - * To ensure backwards-compatability, whenever $wgRC2UDPAddress is set, a - * 'default' feed will be created reusing the deprecated $wgRC2UDP* variables. - * * @example $wgRCFeeds['example'] = array( * 'formatter' => 'JSONRCFeedFormatter', * 'uri' => "udp://localhost:1336", @@ -5568,13 +5788,6 @@ $wgRCEngines = array( ); /** - * Enable user search in Special:Newpages - * This is really a temporary hack around an index install bug on some Wikipedias. - * Kill it once fixed. - */ -$wgEnableNewpagesUserFilter = true; - -/** * Use RC Patrolling to check for vandalism */ $wgUseRCPatrol = true; @@ -5707,24 +5920,42 @@ $wgUnwatchedPageThreshold = false; * To register a new one: * @code * $wgRecentChangesFlags['flag'] => array( + * // message for the letter displayed next to rows on changes lists * 'letter' => 'letter-msg', - * 'title' => 'tooltip-msg' + * // message for the tooltip of the letter + * 'title' => 'tooltip-msg', + * // optional (defaults to 'tooltip-msg'), message to use in the legend box + * 'legend' => 'legend-msg', + * // optional (defaults to 'flag'), CSS class to put on changes lists rows + * 'class' => 'css-class', * ); * @endcode * - * Optional 'class' allows to set a css class different than the flag name. - * * @since 1.22 */ $wgRecentChangesFlags = array( - 'newpage' => array( 'letter' => 'newpageletter', - 'title' => 'recentchanges-label-newpage' ), - 'minor' => array( 'letter' => 'minoreditletter', - 'title' => 'recentchanges-label-minor', 'class' => 'minoredit' ), - 'bot' => array( 'letter' => 'boteditletter', - 'title' => 'recentchanges-label-bot', 'class' => 'botedit' ), - 'unpatrolled' => array( 'letter' => 'unpatrolledletter', - 'title' => 'recentchanges-label-unpatrolled' ), + 'newpage' => array( + 'letter' => 'newpageletter', + 'title' => 'recentchanges-label-newpage', + 'legend' => 'recentchanges-legend-newpage', + ), + 'minor' => array( + 'letter' => 'minoreditletter', + 'title' => 'recentchanges-label-minor', + 'legend' => 'recentchanges-legend-minor', + 'class' => 'minoredit', + ), + 'bot' => array( + 'letter' => 'boteditletter', + 'title' => 'recentchanges-label-bot', + 'legend' => 'recentchanges-legend-bot', + 'class' => 'botedit', + ), + 'unpatrolled' => array( + 'letter' => 'unpatrolledletter', + 'title' => 'recentchanges-label-unpatrolled', + 'legend' => 'recentchanges-legend-unpatrolled', + ), ); /** @} */ # end RC/watchlist } @@ -5764,11 +5995,6 @@ $wgRightsText = null; $wgRightsIcon = null; /** - * Set to an array of metadata terms. Else they will be loaded based on $wgRightsUrl - */ -$wgLicenseTerms = false; - -/** * Set this to some HTML to override the rights icon with an arbitrary logo * @deprecated since 1.18 Use $wgFooterIcons['copyright']['copyright'] */ @@ -5806,6 +6032,17 @@ $wgShowCreditsIfMax = true; * Special:Import (for sysops). Since complete page history can be imported, * these should be 'trusted'. * + * This can either be a regular array, or an associative map specifying + * subprojects on the interwiki map of the target wiki, or a mix of the two, + * e.g. + * @code + * $wgImportSources = array( + * 'wikipedia' => array( 'cs', 'en', 'fr', 'zh' ), + * 'wikispecies', + * 'wikia' => array( 'animanga', 'brickipedia', 'desserts' ), + * ); + * @endcode + * * If a user has the 'import' permission but not the 'importupload' permission, * they will only be able to run imports through this transwiki interface. */ @@ -5886,6 +6123,16 @@ $wgExtensionFunctions = array(); * Variables defined in extensions will override conflicting variables defined * in the core. * + * Since MediaWiki 1.23, use of this variable to define messages is discouraged; instead, store + * messages in JSON format and use $wgMessagesDirs. For setting other variables than + * $messages, $wgExtensionMessagesFiles should still be used. Use a DIFFERENT key because + * any entry having a key that also exists in $wgMessagesDirs will be ignored. + * + * Extensions using the JSON message format can preserve backward compatibility with + * earlier versions of MediaWiki by using a compatibility shim, such as one generated + * by the generateJsonI18n.php maintenance script, listing it under the SAME key + * as for the $wgMessagesDirs entry. + * * @par Example: * @code * $wgExtensionMessagesFiles['ConfirmEdit'] = __DIR__.'/ConfirmEdit.i18n.php'; @@ -5894,6 +6141,34 @@ $wgExtensionFunctions = array(); $wgExtensionMessagesFiles = array(); /** + * Extension messages directories. + * + * Associative array mapping extension name to the path of the directory where message files can + * be found. The message files are expected to be JSON files named for their language code, e.g. + * en.json, de.json, etc. Extensions with messages in multiple places may specify an array of + * message directories. + * + * @par Simple example: + * @code + * $wgMessagesDirs['Example'] = __DIR__ . '/i18n'; + * @endcode + * + * @par Complex example: + * @code + * $wgMessagesDirs['Example'] = array( + * __DIR__ . '/lib/ve/i18n', + * __DIR__ . '/lib/oojs-ui/i18n', + * __DIR__ . '/i18n', + * ) + * @endcode + * @since 1.23 + */ +$wgMessagesDirs = array( + 'core' => "$IP/languages/i18n", + 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n", +); + +/** * Array of files with list(s) of extension entry points to be used in * maintenance/mergeMessageFileList.php * @since 1.22 @@ -5922,19 +6197,20 @@ $wgParserOutputHooks = array(); $wgEnableParserLimitReporting = true; /** - * List of valid skin names. + * List of valid skin names + * * The key should be the name in all lower case, the value should be a properly - * cased name for the skin. This value will be prefixed with "Skin" to create the - * class name of the skin to load, and if the skin's class cannot be found through - * the autoloader it will be used to load a .php file by that name in the skins directory. - * The default skins will be added later, by Skin::getSkinNames(). Use - * Skin::getSkinNames() as an accessor if you wish to have access to the full list. + * cased name for the skin. This value will be prefixed with "Skin" to create + * the class name of the skin to load. Use Skin::getSkinNames() as an accessor + * if you wish to have access to the full list. */ $wgValidSkinNames = array(); /** - * Special page list. - * See the top of SpecialPage.php for documentation. + * Special page list. This is an associative array mapping the (canonical) names of + * special pages to either a class name to be instantiated, or a callback to use for + * creating the special page object. In both cases, the result must be an instance of + * SpecialPage. */ $wgSpecialPages = array(); @@ -5944,30 +6220,63 @@ $wgSpecialPages = array(); $wgAutoloadClasses = array(); /** - * An array of extension types and inside that their names, versions, authors, - * urls, descriptions and pointers to localized description msgs. Note that - * the version, url, description and descriptionmsg key can be omitted. + * Switch controlling legacy case-insensitive classloading. + * Do not disable if your wiki must support data created by PHP4, or by + * MediaWiki 1.4 or earlier. + */ +$wgAutoloadAttemptLowercase = true; + +/** + * An array of information about installed extensions keyed by their type. + * + * All but 'name', 'path' and 'author' can be omitted. * * @code * $wgExtensionCredits[$type][] = array( - * 'name' => 'Example extension', - * 'version' => 1.9, * 'path' => __FILE__, - * 'author' => 'Foo Barstein', - * 'url' => 'http://www.example.com/Example%20Extension/', - * 'description' => 'An example extension', + * 'name' => 'Example extension', + * 'namemsg' => 'exampleextension-name', + * 'author' => array( + * 'Foo Barstein', + * ), + * 'version' => '1.9.0', + * 'url' => 'http://example.org/example-extension/', * 'descriptionmsg' => 'exampleextension-desc', + * 'license-name' => 'GPL-2.0', * ); * @endcode * - * Where $type is 'specialpage', 'parserhook', 'variable', 'media' or 'other'. - * Where 'descriptionmsg' can be an array with message key and parameters: - * 'descriptionmsg' => array( 'exampleextension-desc', param1, param2, ... ), + * The extensions are listed on Special:Version. This page also looks for a file + * named COPYING or LICENSE (optional .txt extension) and provides a link to + * view said file. When the 'license-name' key is specified, this file is + * interpreted as wikitext. + * + * - $type: One of 'specialpage', 'parserhook', 'variable', 'media', 'antispam', + * 'skin', 'api', or 'other', or any additional types as specified through the + * ExtensionTypes hook as used in SpecialVersion::getExtensionTypes(). + * + * - name: Name of extension as an inline string instead of localizable message. + * Do not omit this even if 'namemsg' is provided, as it is used to override + * the path Special:Version uses to find extension's license info, and is + * required for backwards-compatibility with MediaWiki 1.23 and older. + * + * - namemsg (since MW 1.24): A message key for a message containing the + * extension's name, if the name is localizable. (For example, skin names + * usually are.) + * + * - author: A string or an array of strings. Authors can be linked using + * the regular wikitext link syntax. To have an internationalized version of + * "and others" show, add an element "...". This element can also be linked, + * for instance "[http://example ...]". + * + * - descriptionmsg: A message key or an an array with message key and parameters: + * `'descriptionmsg' => 'exampleextension-desc',` + * + * - description: Description of extension as an inline string instead of + * localizable message (omit in favour of 'descriptionmsg'). * - * author can be a string or an array of strings. Authors can be linked using - * the regular wikitext link syntax. To have an internationalized version of - * "and others" show, add an element "...". This element can also be linked, - * for instance "[http://example ...]". + * - license-name: Short name of the license (used as label for the link), such + * as "GPL-2.0" or "MIT" (https://spdx.org/licenses/ for a list of identifiers). */ $wgExtensionCredits = array(); @@ -6019,7 +6328,7 @@ $wgHooks = array(); */ $wgJobClasses = array( 'refreshLinks' => 'RefreshLinksJob', - 'refreshLinks2' => 'RefreshLinksJob2', + 'refreshLinks2' => 'RefreshLinksJob2', // b/c 'htmlCacheUpdate' => 'HTMLCacheUpdateJob', 'sendMail' => 'EmaillingJob', 'enotifNotify' => 'EnotifNotifyJob', @@ -6039,10 +6348,22 @@ $wgJobClasses = array( * - Jobs that you would never want to run as part of a page rendering request. * - Jobs that you want to run on specialized machines ( like transcoding, or a particular * machine on your cluster has 'outside' web access you could restrict uploadFromUrl ) + * These settings should be global to all wikis. */ $wgJobTypesExcludedFromDefaultQueue = array( 'AssembleUploadChunks', 'PublishStashedFile' ); /** + * Map of job types to how many job "work items" should be run per second + * on each job runner process. The meaning of "work items" varies per job, + * but typically would be something like "pages to update". A single job + * may have a variable number of work items, as is the case with batch jobs. + * This is used by runJobs.php and not jobs run via $wgJobRunRate. + * These settings should be global to all wikis. + * @var float[] + */ +$wgJobBackoffThrottling = array(); + +/** * Map of job types to configuration arrays. * This determines which queue class and storage system is used for each job type. * Job types that do not have explicit configuration will use the 'default' config. @@ -6065,7 +6386,8 @@ $wgJobQueueAggregator = array( * Expensive Querypages are already updated. */ $wgSpecialPageCacheUpdates = array( - 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' ) + 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' ), + 'Activeusers' => array( 'SpecialActiveUsers', 'cacheUpdate' ), ); /** @@ -6262,9 +6584,6 @@ $wgLogActions = array( 'protect/modify' => 'modifiedarticleprotection', 'protect/unprotect' => 'unprotectedarticle', 'protect/move_prot' => 'movedarticleprotection', - 'upload/upload' => 'uploadedimage', - 'upload/overwrite' => 'overwroteimage', - 'upload/revert' => 'uploadedimage', 'import/upload' => 'import-logentry-upload', 'import/interwiki' => 'import-logentry-interwiki', 'merge/merge' => 'pagemerge-logentry', @@ -6291,6 +6610,9 @@ $wgLogActionsHandlers = array( 'patrol/patrol' => 'PatrolLogFormatter', 'rights/rights' => 'RightsLogFormatter', 'rights/autopromote' => 'RightsLogFormatter', + 'upload/upload' => 'LogFormatter', + 'upload/overwrite' => 'LogFormatter', + 'upload/revert' => 'LogFormatter', ); /** @@ -6325,11 +6647,6 @@ $wgDisableQueryPageUpdate = false; $wgSpecialPageGroups = array(); /** - * Whether or not to sort special pages in Special:Specialpages - */ -$wgSortSpecialPages = true; - -/** * On Special:Unusedimages, consider images "used", if they are put * into a category. Default (false) is not to count those as used. */ @@ -6379,12 +6696,6 @@ $wgActions = array( 'watch' => true, ); -/** - * Array of disabled article actions, e.g. view, edit, delete, etc. - * @deprecated since 1.18; just set $wgActions['action'] = false instead - */ -$wgDisabledActions = array(); - /** @} */ # end actions } /*************************************************************************//** @@ -6470,7 +6781,7 @@ $wgExemptFromUserRobotsControl = null; * Enable the MediaWiki API for convenient access to * machine-readable data via api.php * - * See http://www.mediawiki.org/wiki/API + * See https://www.mediawiki.org/wiki/API */ $wgEnableAPI = true; @@ -6499,13 +6810,76 @@ $wgDebugAPI = false; /** * API module extensions. - * Associative array mapping module name to class name. + * + * Associative array mapping module name to modules specs; + * Each module spec is an associative array containing at least + * the 'class' key for the module's class, and optionally a + * 'factory' key for the factory function to use for the module. + * + * That factory function will be called with two parameters, + * the parent module (an instance of ApiBase, usually ApiMain) + * and the name the module was registered under. The return + * value must be an instance of the class given in the 'class' + * field. + * + * For backward compatibility, the module spec may also be a + * simple string containing the module's class name. In that + * case, the class' constructor will be called with the parent + * module and module name as parameters, as described above. + * + * Examples for registering API modules: + * + * @code + * $wgAPIModules['foo'] = 'ApiFoo'; + * $wgAPIModules['bar'] = array( + * 'class' => 'ApiBar', + * 'factory' => function( $main, $name ) { ... } + * ); + * $wgAPIModules['xyzzy'] = array( + * 'class' => 'ApiXyzzy', + * 'factory' => array( 'XyzzyFactory', 'newApiModule' ) + * ); + * @endcode + * * Extension modules may override the core modules. - * @todo Describe each of the variables, group them and add examples + * See ApiMain::$Modules for a list of the core modules. */ $wgAPIModules = array(); + +/** + * API format module extensions. + * Associative array mapping format module name to module specs (see $wgAPIModules). + * Extension modules may override the core modules. + * + * See ApiMain::$Formats for a list of the core format modules. + */ +$wgAPIFormatModules = array(); + +/** + * API Query meta module extensions. + * Associative array mapping meta module name to module specs (see $wgAPIModules). + * Extension modules may override the core modules. + * + * See ApiQuery::$QueryMetaModules for a list of the core meta modules. + */ $wgAPIMetaModules = array(); + +/** + * API Query prop module extensions. + * Associative array mapping prop module name to module specs (see $wgAPIModules). + * Extension modules may override the core modules. + * + * See ApiQuery::$QueryPropModules for a list of the core prop modules. + */ $wgAPIPropModules = array(); + +/** + * API Query list module extensions. + * Associative array mapping list module name to module specs (see $wgAPIModules). + * Extension modules may override the core modules. + * + * See ApiQuery::$QueryListModules for a list of the core list modules. + */ $wgAPIListModules = array(); /** @@ -6683,12 +7057,12 @@ $wgShellLocale = 'en_US.utf8'; */ /** - * Timeout for HTTP requests done internally + * Timeout for HTTP requests done internally, in seconds. */ $wgHTTPTimeout = 25; /** - * Timeout for Asynchronous (background) HTTP requests + * Timeout for Asynchronous (background) HTTP requests, in seconds. */ $wgAsyncHTTPTimeout = 25; @@ -6720,6 +7094,14 @@ $wgHTTPConnectTimeout = 5e0; $wgJobRunRate = 1; /** + * When $wgJobRunRate > 0, try to run jobs asynchronously, spawning a new process + * to handle the job execution, instead of blocking the request until the job + * execution finishes. + * @since 1.23 + */ +$wgRunJobsAsync = true; + +/** * Number of rows to update per job */ $wgUpdateRowsPerJob = 500; @@ -6729,15 +7111,6 @@ $wgUpdateRowsPerJob = 500; */ $wgUpdateRowsPerQuery = 100; -/** - * Do not purge all the pages that use a page when it is edited - * if there are more than this many such pages. This is used to - * avoid invalidating a large portion of the squid/parser cache. - * - * This setting should factor in any squid/parser cache expiry settings. - */ -$wgMaxBacklinksInvalidate = false; - /** @} */ # End job queue } /************************************************************************//** @@ -6886,10 +7259,45 @@ $wgSiteTypes = array( ); /** - * Formerly a list of files for HipHop compilation - * @deprecated since 1.22 + * Whether the page_props table has a pp_sortkey column. Set to false in case + * the respective database schema change was not applied. + * @since 1.23 + */ +$wgPagePropsHaveSortkey = true; + +/** + * Port where you have HTTPS running + * Supports HTTPS on non-standard ports + * @see bug 65184 + * @since 1.24 + */ +$wgHttpsPort = 443; + +/** + * Secret and algorithm for hmac-based key derivation function (fast, + * cryptographically secure random numbers). + * This should be set in LocalSettings.php, otherwise wgSecretKey will + * be used. + * @since 1.24 + */ +$wgHKDFSecret = false; +$wgHKDFAlgorithm = 'sha256'; + +/** + * Enable page language feature + * Allows setting page language in database + * @var bool + * @since 1.24 + */ +$wgPageLanguageUseDB = false; + +/** + * Enable use of the *_namespace fields of the pagelinks, redirect, and templatelinks tables. + * Set this only if the fields are fully populated. This may be removed in 1.25. + * @var bool + * @since 1.24 */ -$wgCompiledFiles = array(); +$wgUseLinkNamespaceDBFields = true; /** * For really cool vim folding this needs to be at the end: diff --git a/includes/Defines.php b/includes/Defines.php index 86c5520b..017e9ea4 100644 --- a/includes/Defines.php +++ b/includes/Defines.php @@ -59,7 +59,6 @@ define( 'DB_MASTER', -2 ); # Write to master (or only server) # Obsolete aliases define( 'DB_READ', -1 ); define( 'DB_WRITE', -2 ); -define( 'DB_LAST', -3 ); # deprecated since 2008, usage throws exception /**@{ * Virtual namespaces; don't appear in the page database @@ -113,23 +112,33 @@ define( 'CACHE_NONE', 0 ); // Do not cache define( 'CACHE_DB', 1 ); // Store cache objects in the DB define( 'CACHE_MEMCACHED', 2 ); // MemCached, must specify servers in $wgMemCacheServers define( 'CACHE_ACCEL', 3 ); // APC, XCache or WinCache -define( 'CACHE_DBA', 4 ); // Use PHP's DBA extension to store in a DBM-style database /**@}*/ /**@{ * Media types. * This defines constants for the value returned by File::getMediaType() */ -define( 'MEDIATYPE_UNKNOWN', 'UNKNOWN' ); // unknown format -define( 'MEDIATYPE_BITMAP', 'BITMAP' ); // some bitmap image or image source (like psd, etc). Can't scale up. -define( 'MEDIATYPE_DRAWING', 'DRAWING' ); // some vector drawing (SVG, WMF, PS, ...) or image source (oo-draw, etc). Can scale up. -define( 'MEDIATYPE_AUDIO', 'AUDIO' ); // simple audio file (ogg, mp3, wav, midi, whatever) -define( 'MEDIATYPE_VIDEO', 'VIDEO' ); // simple video file (ogg, mpg, etc; no not include formats here that may contain executable sections or scripts!) -define( 'MEDIATYPE_MULTIMEDIA', 'MULTIMEDIA' ); // Scriptable Multimedia (flash, advanced video container formats, etc) -define( 'MEDIATYPE_OFFICE', 'OFFICE' ); // Office Documents, Spreadsheets (office formats possibly containing apples, scripts, etc) -define( 'MEDIATYPE_TEXT', 'TEXT' ); // Plain text (possibly containing program code or scripts) -define( 'MEDIATYPE_EXECUTABLE', 'EXECUTABLE' ); // binary executable -define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' ); // archive file (zip, tar, etc) +// unknown format +define( 'MEDIATYPE_UNKNOWN', 'UNKNOWN' ); +// some bitmap image or image source (like psd, etc). Can't scale up. +define( 'MEDIATYPE_BITMAP', 'BITMAP' ); +// some vector drawing (SVG, WMF, PS, ...) or image source (oo-draw, etc). Can scale up. +define( 'MEDIATYPE_DRAWING', 'DRAWING' ); +// simple audio file (ogg, mp3, wav, midi, whatever) +define( 'MEDIATYPE_AUDIO', 'AUDIO' ); +// simple video file (ogg, mpg, etc; +// no not include formats here that may contain executable sections or scripts!) +define( 'MEDIATYPE_VIDEO', 'VIDEO' ); +// Scriptable Multimedia (flash, advanced video container formats, etc) +define( 'MEDIATYPE_MULTIMEDIA', 'MULTIMEDIA' ); +// Office Documents, Spreadsheets (office formats possibly containing apples, scripts, etc) +define( 'MEDIATYPE_OFFICE', 'OFFICE' ); +// Plain text (possibly containing program code or scripts) +define( 'MEDIATYPE_TEXT', 'TEXT' ); +// binary executable +define( 'MEDIATYPE_EXECUTABLE', 'EXECUTABLE' ); +// archive file (zip, tar, etc) +define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' ); /**@}*/ /**@{ @@ -155,11 +164,6 @@ define( 'ALF_NO_BLOCK_LOCK', 8 ); * Date format selectors; used in user preference storage and by * Language::date() and co. */ -/*define( 'MW_DATE_DEFAULT', '0' ); -define( 'MW_DATE_MDY', '1' ); -define( 'MW_DATE_DMY', '2' ); -define( 'MW_DATE_YMD', '3' ); -define( 'MW_DATE_ISO', 'ISO 8601' );*/ define( 'MW_DATE_DEFAULT', 'default' ); define( 'MW_DATE_MDY', 'mdy' ); define( 'MW_DATE_DMY', 'dmy' ); @@ -172,9 +176,7 @@ define( 'MW_DATE_ISO', 'ISO 8601' ); */ define( 'RC_EDIT', 0 ); define( 'RC_NEW', 1 ); -define( 'RC_MOVE', 2 ); // obsolete define( 'RC_LOG', 3 ); -define( 'RC_MOVE_OVER_REDIRECT', 4 ); // obsolete define( 'RC_EXTERNAL', 5 ); /**@}*/ @@ -279,6 +281,7 @@ define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' ); define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' ); define( 'CONTENT_MODEL_CSS', 'css' ); define( 'CONTENT_MODEL_TEXT', 'text' ); +define( 'CONTENT_MODEL_JSON', 'json' ); /**@}*/ /**@{ @@ -288,12 +291,20 @@ define( 'CONTENT_MODEL_TEXT', 'text' ); * Extensions are free to use the below formats, or define their own. * It is recommended to stick with the conventions for MIME types. */ -define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); // wikitext -define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); // for js pages -define( 'CONTENT_FORMAT_CSS', 'text/css' ); // for css pages -define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); // for future use, e.g. with some plain-html messages. -define( 'CONTENT_FORMAT_HTML', 'text/html' ); // for future use, e.g. with some plain-html messages. -define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); // for future use with the api and for extensions -define( 'CONTENT_FORMAT_JSON', 'application/json' ); // for future use with the api, and for use by extensions -define( 'CONTENT_FORMAT_XML', 'application/xml' ); // for future use with the api, and for use by extensions +// wikitext +define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); +// for js pages +define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); +// for css pages +define( 'CONTENT_FORMAT_CSS', 'text/css' ); +// for future use, e.g. with some plain-html messages. +define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); +// for future use, e.g. with some plain-html messages. +define( 'CONTENT_FORMAT_HTML', 'text/html' ); +// for future use with the api and for extensions +define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); +// for future use with the api, and for use by extensions +define( 'CONTENT_FORMAT_JSON', 'application/json' ); +// for future use with the api, and for use by extensions +define( 'CONTENT_FORMAT_XML', 'application/xml' ); /**@}*/ diff --git a/includes/DeprecatedGlobal.php b/includes/DeprecatedGlobal.php index d48bd0b0..14329d32 100644 --- a/includes/DeprecatedGlobal.php +++ b/includes/DeprecatedGlobal.php @@ -23,20 +23,22 @@ /** * Class to allow throwing wfDeprecated warnings * when people use globals that we do not want them to. - * (For example like $wgArticle) */ class DeprecatedGlobal extends StubObject { - // The m's are to stay consistent with parent class. - protected $mRealValue, $mVersion; + protected $realValue, $version; function __construct( $name, $realValue, $version = false ) { parent::__construct( $name ); - $this->mRealValue = $realValue; - $this->mVersion = $version; + $this->realValue = $realValue; + $this->version = $version; } + // @codingStandardsIgnoreStart + // PSR2.Methods.MethodDeclaration.Underscore + // PSR2.Classes.PropertyDeclaration.ScopeMissing function _newObject() { + /* Put the caller offset for wfDeprecated as 6, as * that gives the function that uses this object, since: * 1 = this function ( _newObject ) @@ -49,7 +51,8 @@ class DeprecatedGlobal extends StubObject { * sequences for this method, but that seems to be * rather unlikely. */ - wfDeprecated( '$' . $this->mGlobal, $this->mVersion, false, 6 ); - return $this->mRealValue; + wfDeprecated( '$' . $this->global, $this->version, false, 6 ); + return $this->realValue; } + // @codingStandardsIgnoreEnd } diff --git a/includes/EditPage.php b/includes/EditPage.php index 4dd83845..128244a8 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -1,6 +1,6 @@ <?php /** - * Page edition user interface. + * User interface for page editing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,7 +36,6 @@ * headaches, which may be fatal. */ class EditPage { - /** * Status: Article successfully updated */ @@ -68,11 +67,6 @@ class EditPage { const AS_CONTENT_TOO_BIG = 216; /** - * Status: User cannot edit? (not used) - */ - const AS_USER_CANNOT_EDIT = 217; - - /** * Status: this anonymous user is not allowed to edit this page */ const AS_READ_ONLY_PAGE_ANON = 218; @@ -105,7 +99,7 @@ class EditPage { const AS_NO_CREATE_PERMISSION = 223; /** - * Status: user tried to create a blank page + * Status: user tried to create a blank page and wpIgnoreBlankArticle == false */ const AS_BLANK_ARTICLE = 224; @@ -131,11 +125,6 @@ class EditPage { const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229; /** - * not used - */ - const AS_OK = 230; - - /** * Status: WikiPage::doEdit() was unsuccessful */ const AS_END = 231; @@ -182,8 +171,9 @@ class EditPage { * The cookie will be removed instantly if the JavaScript runs. * * Otherwise, though, we don't want the cookies to accumulate. - * RFC 2109 ( https://www.ietf.org/rfc/rfc2109.txt ) specifies a possible limit of only 20 cookies per domain. - * This still applies at least to some versions of IE without full updates: + * RFC 2109 ( https://www.ietf.org/rfc/rfc2109.txt ) specifies a possible + * limit of only 20 cookies per domain. This still applies at least to some + * versions of IE without full updates: * https://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx * * A value of 20 minutes should be enough to take into account slow loads and minor @@ -191,65 +181,166 @@ class EditPage { */ const POST_EDIT_COOKIE_DURATION = 1200; - /** - * @var Article - */ - var $mArticle; + /** @var Article */ + public $mArticle; - /** - * @var Title - */ - var $mTitle; + /** @var Title */ + public $mTitle; + + /** @var null|Title */ private $mContextTitle = null; - var $action = 'submit'; - var $isConflict = false; - var $isCssJsSubpage = false; - var $isCssSubpage = false; - var $isJsSubpage = false; - var $isWrongCaseCssJsPage = false; - var $isNew = false; // new page or new section - var $deletedSinceEdit; - var $formtype; - var $firsttime; - var $lastDelete; - var $mTokenOk = false; - var $mTokenOkExceptSuffix = false; - var $mTriedSave = false; - var $incompleteForm = false; - var $tooBig = false; - var $kblength = false; - var $missingComment = false; - var $missingSummary = false; - var $allowBlankSummary = false; - var $autoSumm = ''; - var $hookError = ''; - #var $mPreviewTemplates; - - /** - * @var ParserOutput - */ - var $mParserOutput; - - /** - * Has a summary been preset using GET parameter &summary= ? - * @var Bool - */ - var $hasPresetSummary = false; - - var $mBaseRevision = false; - var $mShowSummaryField = true; + + /** @var string */ + public $action = 'submit'; + + /** @var bool */ + public $isConflict = false; + + /** @var bool */ + public $isCssJsSubpage = false; + + /** @var bool */ + public $isCssSubpage = false; + + /** @var bool */ + public $isJsSubpage = false; + + /** @var bool */ + public $isWrongCaseCssJsPage = false; + + /** @var bool New page or new section */ + public $isNew = false; + + /** @var bool */ + public $deletedSinceEdit; + + /** @var string */ + public $formtype; + + /** @var bool */ + public $firsttime; + + /** @var bool|stdClass */ + public $lastDelete; + + /** @var bool */ + public $mTokenOk = false; + + /** @var bool */ + public $mTokenOkExceptSuffix = false; + + /** @var bool */ + public $mTriedSave = false; + + /** @var bool */ + public $incompleteForm = false; + + /** @var bool */ + public $tooBig = false; + + /** @var bool */ + public $kblength = false; + + /** @var bool */ + public $missingComment = false; + + /** @var bool */ + public $missingSummary = false; + + /** @var bool */ + public $allowBlankSummary = false; + + /** @var bool */ + protected $blankArticle = false; + + /** @var bool */ + protected $allowBlankArticle = false; + + /** @var string */ + public $autoSumm = ''; + + /** @var string */ + public $hookError = ''; + + /** @var ParserOutput */ + public $mParserOutput; + + /** @var bool Has a summary been preset using GET parameter &summary= ? */ + public $hasPresetSummary = false; + + /** @var bool */ + public $mBaseRevision = false; + + /** @var bool */ + public $mShowSummaryField = true; # Form values - var $save = false, $preview = false, $diff = false; - var $minoredit = false, $watchthis = false, $recreate = false; - var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false; - var $edittime = '', $section = '', $sectiontitle = '', $starttime = ''; - var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true; - var $contentModel = null, $contentFormat = null; + + /** @var bool */ + public $save = false; + + /** @var bool */ + public $preview = false; + + /** @var bool */ + public $diff = false; + + /** @var bool */ + public $minoredit = false; + + /** @var bool */ + public $watchthis = false; + + /** @var bool */ + public $recreate = false; + + /** @var string */ + public $textbox1 = ''; + + /** @var string */ + public $textbox2 = ''; + + /** @var string */ + public $summary = ''; + + /** @var bool */ + public $nosummary = false; + + /** @var string */ + public $edittime = ''; + + /** @var string */ + public $section = ''; + + /** @var string */ + public $sectiontitle = ''; + + /** @var string */ + public $starttime = ''; + + /** @var int */ + public $oldid = 0; + + /** @var string */ + public $editintro = ''; + + /** @var null */ + public $scrolltop = null; + + /** @var bool */ + public $bot = true; + + /** @var null|string */ + public $contentModel = null; + + /** @var null|string */ + public $contentFormat = null; # Placeholders for text injection by hooks (must be HTML) # extensions should take care to _append_ to the present value - public $editFormPageTop = ''; // Before even the preview + + /** @var string Before even the preview */ + public $editFormPageTop = ''; public $editFormTextTop = ''; public $editFormTextBeforeContent = ''; public $editFormTextAfterWarn = ''; @@ -265,15 +356,17 @@ class EditPage { public $suppressIntro = false; - /** - * Set to true to allow editing of non-text content types. - * - * @var bool - */ + /** @var bool Set to true to allow editing of non-text content types. */ public $allowNonTextContent = false; + /** @var bool */ + protected $edit; + + /** @var bool */ + public $live; + /** - * @param $article Article + * @param Article $article */ public function __construct( Article $article ) { $this->mArticle = $article; @@ -303,7 +396,7 @@ class EditPage { /** * Set the context Title object * - * @param $title Title object or null + * @param Title|null $title Title object or null */ public function setContextTitle( $title ) { $this->mContextTitle = $title; @@ -314,7 +407,7 @@ class EditPage { * If not set, $wgTitle will be returned. This behavior might change in * the future to return $this->mTitle instead. * - * @return Title object + * @return Title */ public function getContextTitle() { if ( is_null( $this->mContextTitle ) ) { @@ -325,6 +418,18 @@ class EditPage { } } + /** + * Returns if the given content model is editable. + * + * @param string $modelId The ID of the content model to test. Use CONTENT_MODEL_XXX constants. + * @return bool + * @throws MWException If $modelId has no known handler + */ + public function isSupportedContentModel( $modelId ) { + return $this->allowNonTextContent || + ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler; + } + function submit() { $this->edit(); } @@ -406,6 +511,7 @@ class EditPage { $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage(); $this->isCssSubpage = $this->mTitle->isCssSubpage(); $this->isJsSubpage = $this->mTitle->isJsSubpage(); + // @todo FIXME: Silly assignment. $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage(); # Show applicable editing introductions @@ -463,9 +569,9 @@ class EditPage { # Ignore some permissions errors when a user is just previewing/viewing diffs $remove = array(); foreach ( $permErrors as $error ) { - if ( ( $this->preview || $this->diff ) && - ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) ) - { + if ( ( $this->preview || $this->diff ) + && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) + ) { $remove[] = $error; } } @@ -482,8 +588,8 @@ class EditPage { * "View source for ..." page displaying the source code after the error message. * * @since 1.19 - * @param array $permErrors of permissions errors, as returned by - * Title::getUserPermissionsErrors(). + * @param array $permErrors Array of permissions errors, as returned by + * Title::getUserPermissionsErrors(). * @throws PermissionsError */ protected function displayPermissionsError( array $permErrors ) { @@ -506,8 +612,13 @@ class EditPage { throw new PermissionsError( $action, $permErrors ); } + wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) ); + $wgOut->setPageTitle( wfMessage( + 'viewsource-title', + $this->getContextTitle()->getPrefixedText() + ) ); $wgOut->addBacklinkSubtitle( $this->getContextTitle() ); $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) ); $wgOut->addHTML( "<hr />\n" ); @@ -535,26 +646,6 @@ class EditPage { } /** - * Show a read-only error - * Parameters are the same as OutputPage:readOnlyPage() - * Redirect to the article page if redlink=1 - * @deprecated in 1.19; use displayPermissionsError() instead - */ - function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) { - wfDeprecated( __METHOD__, '1.19' ); - - global $wgRequest, $wgOut; - if ( $wgRequest->getBool( 'redlink' ) ) { - // The edit page was reached via a red link. - // Redirect to the article page and let them click the edit tab if - // they really want a permission error. - $wgOut->redirect( $this->mTitle->getFullURL() ); - } else { - $wgOut->readOnlyPage( $source, $protected, $reasons, $action ); - } - } - - /** * Should we show a preview when the edit form is first shown? * * @return bool @@ -570,13 +661,15 @@ class EditPage { } elseif ( $this->section == 'new' ) { // Nothing *to* preview for new sections return false; - } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) { + } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) + && $wgUser->getOption( 'previewonfirst' ) + ) { // Standard preference behavior return true; - } elseif ( !$this->mTitle->exists() && - isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) && - $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) - { + } elseif ( !$this->mTitle->exists() + && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) + && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] + ) { // Categories are special return true; } else { @@ -609,7 +702,7 @@ class EditPage { * Subclasses may override this to replace the default behavior, which is * to check ContentHandler::supportsSections. * - * @return bool true if this edit page supports sections, false otherwise. + * @return bool True if this edit page supports sections, false otherwise. */ protected function isSectionEditSupported() { $contentHandler = ContentHandler::getForTitle( $this->mTitle ); @@ -618,7 +711,8 @@ class EditPage { /** * This function collects the form data and uses it to populate various member variables. - * @param $request WebRequest + * @param WebRequest $request + * @throws ErrorPageError */ function importFormData( &$request ) { global $wgContLang, $wgUser; @@ -685,9 +779,13 @@ class EditPage { // suhosin.request.max_value_length (d'oh) $this->incompleteForm = true; } else { - // edittime should be one of our last fields; if it's missing, - // the submission probably broke somewhere in the middle. - $this->incompleteForm = is_null( $this->edittime ); + // If we receive the last parameter of the request, we can fairly + // claim the POST request has not been truncated. + + // TODO: softened the check for cutover. Once we determine + // that it is safe, we should complete the transition by + // removing the "edittime" clause. + $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' ) && is_null( $this->edittime ) ); } if ( $this->incompleteForm ) { # If the form is incomplete, force to preview. @@ -734,15 +832,18 @@ class EditPage { $this->watchthis = $request->getCheck( 'wpWatchthis' ); # Don't force edit summaries when a user is editing their own user or talk page - if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) && - $this->mTitle->getText() == $wgUser->getName() ) - { + if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) + && $this->mTitle->getText() == $wgUser->getName() + ) { $this->allowBlankSummary = true; } else { - $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' ); + $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) + || !$wgUser->getOption( 'forceeditsummary' ); } $this->autoSumm = $request->getText( 'wpAutoSummary' ); + + $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' ); } else { # Not a posted form? Start with nothing. wfDebug( __METHOD__ . ": Not a posted form.\n" ); @@ -756,7 +857,8 @@ class EditPage { $this->save = false; $this->diff = false; $this->minoredit = false; - $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overridden by request parameters + // Watch may be overridden by request parameters + $this->watchthis = $request->getBool( 'watchthis', false ); $this->recreate = false; // When creating a new section, we can preload a section title by passing it as the @@ -765,8 +867,7 @@ class EditPage { $this->sectiontitle = $request->getVal( 'preloadtitle' ); // Once wpSummary isn't being use for setting section titles, we should delete this. $this->summary = $request->getVal( 'preloadtitle' ); - } - elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) { + } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) { $this->summary = $request->getText( 'summary' ); if ( $this->summary !== '' ) { $this->hasPresetSummary = true; @@ -783,12 +884,26 @@ class EditPage { $this->bot = $request->getBool( 'bot', true ); $this->nosummary = $request->getBool( 'nosummary' ); - $content_handler = ContentHandler::getForTitle( $this->mTitle ); - $this->contentModel = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision - $this->contentFormat = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision + // May be overridden by revision. + $this->contentModel = $request->getText( 'model', $this->contentModel ); + // May be overridden by revision. + $this->contentFormat = $request->getText( 'format', $this->contentFormat ); - #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed - #TODO: check if the desired content model supports the given content format! + if ( !ContentHandler::getForModelID( $this->contentModel ) + ->isSupportedFormat( $this->contentFormat ) + ) { + throw new ErrorPageError( + 'editpage-notsupportedcontentformat-title', + 'editpage-notsupportedcontentformat-text', + array( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) ) + ); + } + + /** + * @todo Check if the desired model is allowed in this namespace, and if + * a transition from the page's current model to the new model is + * allowed. + */ $this->live = $request->getCheck( 'live' ); $this->editintro = $request->getText( 'editintro', @@ -807,7 +922,7 @@ class EditPage { * this method should be overridden and return the page text that will be used * for saving, preview parsing and so on... * - * @param $request WebRequest + * @param WebRequest $request */ protected function importContentFormData( &$request ) { return; // Don't do anything, EditPage already extracted wpTextbox1 @@ -816,7 +931,7 @@ class EditPage { /** * Initialise form fields in the object * Called on the first invocation, e.g. when a user clicks an edit link - * @return bool -- if the requested section is valid + * @return bool If the requested section is valid */ function initialiseForm() { global $wgUser; @@ -850,37 +965,14 @@ class EditPage { } /** - * Fetch initial editing page content. - * - * @param $def_text string|bool - * @return mixed string on success, $def_text for invalid sections - * @private - * @deprecated since 1.21, get WikiPage::getContent() instead. - */ - function getContent( $def_text = false ) { - ContentHandler::deprecated( __METHOD__, '1.21' ); - - if ( $def_text !== null && $def_text !== false && $def_text !== '' ) { - $def_content = $this->toEditContent( $def_text ); - } else { - $def_content = false; - } - - $content = $this->getContentObject( $def_content ); - - // Note: EditPage should only be used with text based content anyway. - return $this->toEditText( $content ); - } - - /** * @param Content|null $def_content The default value to return * - * @return mixed Content on success, $def_content for invalid sections + * @return Content|null Content on success, $def_content for invalid sections * * @since 1.21 */ protected function getContentObject( $def_content = null ) { - global $wgOut, $wgRequest; + global $wgOut, $wgRequest, $wgUser, $wgContLang; wfProfileIn( __METHOD__ ); @@ -900,14 +992,15 @@ class EditPage { $preload = $wgRequest->getVal( 'preload', // Custom preload text for new sections $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' ); + $params = $wgRequest->getArray( 'preloadparams', array() ); - $content = $this->getPreloadedContent( $preload ); + $content = $this->getPreloadedContent( $preload, $params ); } // For existing pages, get text based on "undo" or section parameters. } else { if ( $this->section != '' ) { // Get section edit text (returns $def_text for invalid sections) - $orig = $this->getOriginalContent(); + $orig = $this->getOriginalContent( $wgUser ); $content = $orig ? $orig->getSection( $this->section ) : null; if ( !$content ) { @@ -918,10 +1011,6 @@ class EditPage { $undo = $wgRequest->getInt( 'undo' ); if ( $undo > 0 && $undoafter > 0 ) { - if ( $undo < $undoafter ) { - # If they got undoafter and undo round the wrong way, switch them - list( $undo, $undoafter ) = array( $undoafter, $undo ); - } $undorev = Revision::newFromId( $undo ); $oldrev = Revision::newFromId( $undoafter ); @@ -930,8 +1019,6 @@ class EditPage { # the revisions exist and they were not deleted. # Otherwise, $content will be left as-is. if ( !is_null( $undorev ) && !is_null( $oldrev ) && - $undorev->getPage() == $oldrev->getPage() && - $undorev->getPage() == $this->mTitle->getArticleID() && !$undorev->isDeleted( Revision::DELETED_TEXT ) && !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) { @@ -941,34 +1028,45 @@ class EditPage { # Warn the user that something went wrong $undoMsg = 'failure'; } else { - # Inform the user of our success and set an automatic edit summary - $undoMsg = 'success'; - - # If we just undid one rev, use an autosummary - $firstrev = $oldrev->getNext(); - if ( $firstrev && $firstrev->getId() == $undo ) { - $userText = $undorev->getUserText(); - if ( $userText === '' ) { - $undoSummary = wfMessage( - 'undo-summary-username-hidden', - $undo - )->inContentLanguage()->text(); - } else { - $undoSummary = wfMessage( - 'undo-summary', - $undo, - $userText - )->inContentLanguage()->text(); - } - if ( $this->summary === '' ) { - $this->summary = $undoSummary; - } else { - $this->summary = $undoSummary . wfMessage( 'colon-separator' ) - ->inContentLanguage()->text() . $this->summary; + $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW ); + $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); + $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts ); + + if ( $newContent->equals( $oldContent ) ) { + # Tell the user that the undo results in no change, + # i.e. the revisions were already undone. + $undoMsg = 'nochange'; + $content = false; + } else { + # Inform the user of our success and set an automatic edit summary + $undoMsg = 'success'; + + # If we just undid one rev, use an autosummary + $firstrev = $oldrev->getNext(); + if ( $firstrev && $firstrev->getId() == $undo ) { + $userText = $undorev->getUserText(); + if ( $userText === '' ) { + $undoSummary = wfMessage( + 'undo-summary-username-hidden', + $undo + )->inContentLanguage()->text(); + } else { + $undoSummary = wfMessage( + 'undo-summary', + $undo, + $userText + )->inContentLanguage()->text(); + } + if ( $this->summary === '' ) { + $this->summary = $undoSummary; + } else { + $this->summary = $undoSummary . wfMessage( 'colon-separator' ) + ->inContentLanguage()->text() . $this->summary; + } + $this->undidRev = $undo; } - $this->undidRev = $undo; + $this->formtype = 'diff'; } - $this->formtype = 'diff'; } } else { // Failed basic sanity checks. @@ -977,14 +1075,14 @@ class EditPage { $undoMsg = 'norev'; } - // Messages: undo-success, undo-failure, undo-norev + // Messages: undo-success, undo-failure, undo-norev, undo-nochange $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}"; $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" . wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true ); } if ( $content === false ) { - $content = $this->getOriginalContent(); + $content = $this->getOriginalContent( $wgUser ); } } } @@ -1005,9 +1103,10 @@ class EditPage { * 'missing-revision' message. * * @since 1.19 + * @param User $user The user to get the revision for * @return Content|null */ - private function getOriginalContent() { + private function getOriginalContent( User $user ) { if ( $this->section == 'new' ) { return $this->getCurrentContent(); } @@ -1020,7 +1119,7 @@ class EditPage { return $handler->makeEmptyContent(); } - $content = $revision->getContent(); + $content = $revision->getContent( Revision::FOR_THIS_USER, $user ); return $content; } @@ -1053,23 +1152,9 @@ class EditPage { } /** - * Use this method before edit() to preload some text into the edit box - * - * @param $text string - * @deprecated since 1.21, use setPreloadedContent() instead. - */ - public function setPreloadedText( $text ) { - ContentHandler::deprecated( __METHOD__, "1.21" ); - - $content = $this->toEditContent( $text ); - - $this->setPreloadedContent( $content ); - } - - /** * Use this method before edit() to preload some content into the edit box * - * @param $content Content + * @param Content $content * * @since 1.21 */ @@ -1081,32 +1166,14 @@ class EditPage { * Get the contents to be preloaded into the box, either set by * an earlier setPreloadText() or by loading the given page. * - * @param string $preload representing the title to preload from. - * - * @return String - * - * @deprecated since 1.21, use getPreloadedContent() instead - */ - protected function getPreloadedText( $preload ) { - ContentHandler::deprecated( __METHOD__, "1.21" ); - - $content = $this->getPreloadedContent( $preload ); - $text = $this->toEditText( $content ); - - return $text; - } - - /** - * Get the contents to be preloaded into the box, either set by - * an earlier setPreloadText() or by loading the given page. - * - * @param string $preload representing the title to preload from. + * @param string $preload Representing the title to preload from. + * @param array $params Parameters to use (interface-message style) in the preloaded text * * @return Content * * @since 1.21 */ - protected function getPreloadedContent( $preload ) { + protected function getPreloadedContent( $preload, $params = array() ) { global $wgUser; if ( !empty( $this->mPreloadContent ) ) { @@ -1160,13 +1227,13 @@ class EditPage { $content = $converted; } - return $content->preloadTransform( $title, $parserOptions ); + return $content->preloadTransform( $title, $parserOptions, $params ); } /** * Make sure the form isn't faking a user's credentials. * - * @param $request WebRequest + * @param WebRequest $request * @return bool * @private */ @@ -1189,18 +1256,24 @@ class EditPage { * marked HttpOnly. The JavaScript code converts the cookie to a wgPostEdit config * variable. * - * We use a path of '/' since wgCookiePath is not exposed to JS - * * If the variable were set on the server, it would be cached, which is unwanted * since the post-edit state should only apply to the load right after the save. + * + * @param int $statusValue The status value (to check for new article status) */ - protected function setPostEditCookie() { + protected function setPostEditCookie( $statusValue ) { $revisionId = $this->mArticle->getLatest(); $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId; + $val = 'saved'; + if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) { + $val = 'created'; + } elseif ( $this->oldid ) { + $val = 'restored'; + } + $response = RequestContext::getMain()->getRequest()->response(); - $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array( - 'path' => '/', + $response->setcookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array( 'httpOnly' => false, ) ); } @@ -1208,20 +1281,41 @@ class EditPage { /** * Attempt submission * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError - * @return bool false if output is done, true if the rest of the form should be displayed + * @return bool False if output is done, true if the rest of the form should be displayed */ - function attemptSave() { - global $wgUser, $wgOut; + public function attemptSave() { + global $wgUser; $resultDetails = false; # Allow bots to exempt some edits from bot flagging $bot = $wgUser->isAllowed( 'bot' ) && $this->bot; $status = $this->internalAttemptSave( $resultDetails, $bot ); - // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status - if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) { + + return $this->handleStatus( $status, $resultDetails ); + } + + /** + * Handle status, such as after attempt save + * + * @param Status $status + * @param array|bool $resultDetails + * + * @throws ErrorPageError + * @return bool False, if output is done, true if rest of the form should be displayed + */ + private function handleStatus( Status $status, $resultDetails ) { + global $wgUser, $wgOut; + + /** + * @todo FIXME: once the interface for internalAttemptSave() is made + * nicer, this should use the message in $status + */ + if ( $status->value == self::AS_SUCCESS_UPDATE + || $status->value == self::AS_SUCCESS_NEW_ARTICLE + ) { $this->didSave = true; if ( !$resultDetails['nullEdit'] ) { - $this->setPostEditCookie(); + $this->setPostEditCookie( $status->value ); } } @@ -1234,6 +1328,7 @@ class EditPage { case self::AS_TEXTBOX_EMPTY: case self::AS_MAX_ARTICLE_SIZE_EXCEEDED: case self::AS_END: + case self::AS_BLANK_ARTICLE: return true; case self::AS_HOOK_ERROR: @@ -1254,7 +1349,10 @@ class EditPage { $sectionanchor = $resultDetails['sectionanchor']; // Give extensions a chance to modify URL query on update - wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) ); + wfRunHooks( + 'ArticleUpdateBeforeRedirect', + array( $this->mArticle, &$sectionanchor, &$extraQuery ) + ); if ( $resultDetails['redirect'] ) { if ( $extraQuery == '' ) { @@ -1266,10 +1364,6 @@ class EditPage { $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor ); return false; - case self::AS_BLANK_ARTICLE: - $wgOut->redirect( $this->getContextTitle()->getFullURL() ); - return false; - case self::AS_SPAM_ERROR: $this->spamPageWithContent( $resultDetails['spam'] ); return false; @@ -1312,9 +1406,9 @@ class EditPage { /** * Run hooks that can filter edits just before they get saved. * - * @param Content $content the Content to filter. - * @param Status $status for reporting the outcome to the caller - * @param User $user the user performing the edit + * @param Content $content The Content to filter. + * @param Status $status For reporting the outcome to the caller + * @param User $user The user performing the edit * * @return bool */ @@ -1358,20 +1452,58 @@ class EditPage { } /** + * Return the summary to be used for a new section. + * + * @param string $sectionanchor Set to the section anchor text + * @return string + */ + private function newSectionSummary( &$sectionanchor = null ) { + global $wgParser; + + if ( $this->sectiontitle !== '' ) { + $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle ); + // If no edit summary was specified, create one automatically from the section + // title and have it link to the new section. Otherwise, respect the summary as + // passed. + if ( $this->summary === '' ) { + $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); + return wfMessage( 'newsectionsummary' ) + ->rawParams( $cleanSectionTitle )->inContentLanguage()->text(); + } + } elseif ( $this->summary !== '' ) { + $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); + # This is a new section, so create a link to the new section + # in the revision summary. + $cleanSummary = $wgParser->stripSectionName( $this->summary ); + return wfMessage( 'newsectionsummary' ) + ->rawParams( $cleanSummary )->inContentLanguage()->text(); + } + return $this->summary; + } + + /** * Attempt submission (no UI) * - * @param array $result array to add statuses to, currently with the possible keys: - * spam - string - Spam string from content if any spam is detected by matchSpamRegex - * sectionanchor - string - Section anchor for a section save - * nullEdit - boolean - Set if doEditContent is OK. True if null edit, false otherwise. - * redirect - boolean - Set if doEditContent is OK. True if resulting revision is a redirect + * @param array $result Array to add statuses to, currently with the + * possible keys: + * - spam (string): Spam string from content if any spam is detected by + * matchSpamRegex. + * - sectionanchor (string): Section anchor for a section save. + * - nullEdit (boolean): Set if doEditContent is OK. True if null edit, + * false otherwise. + * - redirect (bool): Set if doEditContent is OK. True if resulting + * revision is a redirect. * @param bool $bot True if edit is being made under the bot right. * - * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value, + * @return Status Status object, possibly with a message, but always with + * one of the AS_* constants in $status->value, * - * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are - * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g. - * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time. + * @todo FIXME: This interface is TERRIBLE, but hard to get rid of due to + * various error display idiosyncrasies. There are also lots of cases + * where error metadata is set in the object and retrieved later instead + * of being returned, e.g. AS_CONTENT_TOO_BIG and + * AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some + * time. */ function internalAttemptSave( &$result, $bot = false ) { global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize; @@ -1412,7 +1544,12 @@ class EditPage { # Construct Content object $textbox_content = $this->toEditContent( $this->textbox1 ); } catch ( MWContentSerializationException $ex ) { - $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() ); + $status->fatal( + 'content-failed-to-parse', + $this->contentModel, + $this->contentFormat, + $ex->getMessage() + ); $status->value = self::AS_PARSE_ERROR; wfProfileOut( __METHOD__ . '-checks' ); wfProfileOut( __METHOD__ ); @@ -1422,7 +1559,8 @@ class EditPage { # Check image redirect if ( $this->mTitle->getNamespace() == NS_FILE && $textbox_content->isRedirect() && - !$wgUser->isAllowed( 'upload' ) ) { + !$wgUser->isAllowed( 'upload' ) + ) { $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; $status->setResult( false, $code ); @@ -1461,7 +1599,10 @@ class EditPage { wfProfileOut( __METHOD__ ); return $status; } - if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) { + if ( !wfRunHooks( + 'EditFilter', + array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) + ) { # Error messages etc. could be handled within the hook... $status->fatal( 'hookaborted' ); $status->value = self::AS_HOOK_ERROR; @@ -1572,7 +1713,9 @@ class EditPage { $defaultText = ''; } - if ( $this->textbox1 === $defaultText ) { + if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) { + $this->blankArticle = true; + $status->fatal( 'blankarticle' ); $status->setResult( false, self::AS_BLANK_ARTICLE ); wfProfileOut( __METHOD__ ); return $status; @@ -1590,30 +1733,11 @@ class EditPage { if ( $this->sectiontitle !== '' ) { // Insert the section title above the content. $content = $content->addSectionHeader( $this->sectiontitle ); - - // Jump to the new section - $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle ); - - // If no edit summary was specified, create one automatically from the section - // title and have it link to the new section. Otherwise, respect the summary as - // passed. - if ( $this->summary === '' ) { - $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); - $this->summary = wfMessage( 'newsectionsummary' ) - ->rawParams( $cleanSectionTitle )->inContentLanguage()->text(); - } } elseif ( $this->summary !== '' ) { // Insert the section title above the content. $content = $content->addSectionHeader( $this->summary ); - - // Jump to the new section - $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); - - // Create a link to the new section from the edit summary. - $cleanSummary = $wgParser->stripSectionName( $this->summary ); - $this->summary = wfMessage( 'newsectionsummary' ) - ->rawParams( $cleanSummary )->inContentLanguage()->text(); } + $this->summary = $this->newSectionSummary( $result['sectionanchor'] ); } $status->value = self::AS_SUCCESS_NEW_ARTICLE; @@ -1631,18 +1755,24 @@ class EditPage { $this->isConflict = true; if ( $this->section == 'new' ) { if ( $this->mArticle->getUserText() == $wgUser->getName() && - $this->mArticle->getComment() == $this->summary ) { + $this->mArticle->getComment() == $this->newSectionSummary() + ) { // Probably a duplicate submission of a new comment. // This can happen when squid resends a request after // a timeout but the first one actually went through. - wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" ); + wfDebug( __METHOD__ + . ": duplicate new section submission; trigger edit conflict!\n" ); } else { // New comment; suppress conflict. $this->isConflict = false; wfDebug( __METHOD__ . ": conflict suppressed; new section\n" ); } - } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(), - $wgUser->getId(), $this->edittime ) ) { + } elseif ( $this->section == '' + && Revision::userWasLastToEdit( + DB_MASTER, $this->mTitle->getArticleID(), + $wgUser->getId(), $this->edittime + ) + ) { # Suppress edit conflict with self, except for section edits where merging is required. wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" ); $this->isConflict = false; @@ -1659,13 +1789,23 @@ class EditPage { $content = null; if ( $this->isConflict ) { - wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" - . " (article time '{$timestamp}')\n" ); - - $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime ); + wfDebug( __METHOD__ + . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" + . " (article time '{$timestamp}')\n" ); + + $content = $this->mArticle->replaceSectionContent( + $this->section, + $textbox_content, + $sectionTitle, + $this->edittime + ); } else { wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" ); - $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle ); + $content = $this->mArticle->replaceSectionContent( + $this->section, + $textbox_content, + $sectionTitle + ); } if ( is_null( $content ) ) { @@ -1715,7 +1855,7 @@ class EditPage { return $status; } } elseif ( !$this->allowBlankSummary - && !$content->equals( $this->getOriginalContent() ) + && !$content->equals( $this->getOriginalContent( $wgUser ) ) && !$content->isRedirect() && md5( $this->summary ) == $this->autoSumm ) { @@ -1730,31 +1870,15 @@ class EditPage { wfProfileIn( __METHOD__ . '-sectionanchor' ); $sectionanchor = ''; if ( $this->section == 'new' ) { - if ( $this->sectiontitle !== '' ) { - $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle ); - // If no edit summary was specified, create one automatically from the section - // title and have it link to the new section. Otherwise, respect the summary as - // passed. - if ( $this->summary === '' ) { - $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); - $this->summary = wfMessage( 'newsectionsummary' ) - ->rawParams( $cleanSectionTitle )->inContentLanguage()->text(); - } - } elseif ( $this->summary !== '' ) { - $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); - # This is a new section, so create a link to the new section - # in the revision summary. - $cleanSummary = $wgParser->stripSectionName( $this->summary ); - $this->summary = wfMessage( 'newsectionsummary' ) - ->rawParams( $cleanSummary )->inContentLanguage()->text(); - } + $this->summary = $this->newSectionSummary( $sectionanchor ); } elseif ( $this->section != '' ) { - # Try to get a section anchor from the section source, redirect to edited section if header found - # XXX: might be better to integrate this into Article::replaceSection - # for duplicate heading checking and maybe parsing + # Try to get a section anchor from the section source, redirect + # to edited section if header found. + # XXX: Might be better to integrate this into Article::replaceSection + # for duplicate heading checking and maybe parsing. $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches ); - # we can't deal with anchors, includes, html etc in the header for now, - # headline would need to be parsed to improve this + # We can't deal with anchors, includes, html etc in the header for now, + # headline would need to be parsed to improve this. if ( $hasmatch && strlen( $matches[2] ) > 0 ) { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] ); } @@ -1773,7 +1897,7 @@ class EditPage { } // Check for length errors again now that the section is merged in - $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 ); + $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 ); if ( $this->kblength > $wgMaxArticleSize ) { $this->tooBig = true; $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED ); @@ -1786,17 +1910,23 @@ class EditPage { ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) | ( $bot ? EDIT_FORCE_BOT : 0 ); - $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags, - false, null, $this->contentFormat ); + $doEditStatus = $this->mArticle->doEditContent( + $content, + $this->summary, + $flags, + false, + null, + $content->getDefaultFormat() + ); if ( !$doEditStatus->isOK() ) { // Failure from doEdit() // Show the edit conflict page for certain recognized errors from doEdit(), // but don't show it for errors from extension hooks $errors = $doEditStatus->getErrorsArray(); - if ( in_array( $errors[0][0], array( 'edit-gone-missing', 'edit-conflict', - 'edit-already-exists' ) ) ) - { + if ( in_array( $errors[0][0], + array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) ) + ) { $this->isConflict = true; // Destroys data doEdit() put in $status->value but who cares $doEditStatus->value = self::AS_END; @@ -1831,44 +1961,20 @@ class EditPage { // Do this in its own transaction to reduce contention... $dbw = wfGetDB( DB_MASTER ); - $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) { - $dbw->begin( $fname ); + $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) { WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser ); - $dbw->commit( $fname ); } ); } } /** - * Attempts to merge text content with base and current revisions - * - * @param $editText string - * - * @return bool - * @deprecated since 1.21, use mergeChangesIntoContent() instead - */ - function mergeChangesInto( &$editText ) { - ContentHandler::deprecated( __METHOD__, "1.21" ); - - $editContent = $this->toEditContent( $editText ); - - $ok = $this->mergeChangesIntoContent( $editContent ); - - if ( $ok ) { - $editText = $this->toEditText( $editContent ); - return true; - } - return false; - } - - /** * Attempts to do 3-way merge of edit content with a base revision * and current content, in case of edit conflict, in whichever way appropriate * for the content type. * * @since 1.21 * - * @param $editContent + * @param Content $editContent * * @return bool */ @@ -1915,20 +2021,18 @@ class EditPage { function getBaseRevision() { if ( !$this->mBaseRevision ) { $db = wfGetDB( DB_MASTER ); - $baseRevision = Revision::loadFromTimestamp( + $this->mBaseRevision = Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime ); - return $this->mBaseRevision = $baseRevision; - } else { - return $this->mBaseRevision; } + return $this->mBaseRevision; } /** * Check given input text against $wgSpamRegex, and return the text of the first match. * - * @param $text string + * @param string $text * - * @return string|bool matching string or false + * @return string|bool Matching string or false */ public static function matchSpamRegex( $text ) { global $wgSpamRegex; @@ -1940,9 +2044,9 @@ class EditPage { /** * Check given input text against $wgSummarySpamRegex, and return the text of the first match. * - * @param $text string + * @param string $text * - * @return string|bool matching string or false + * @return string|bool Matching string or false */ public static function matchSummarySpamRegex( $text ) { global $wgSummarySpamRegex; @@ -1951,8 +2055,8 @@ class EditPage { } /** - * @param $text string - * @param $regexes array + * @param string $text + * @param array $regexes * @return bool|string */ protected static function matchSpamRegexInternal( $text, $regexes ) { @@ -1979,9 +2083,6 @@ class EditPage { $wgOut->addModules( 'mediawiki.action.edit.editWarning' ); } - // Bug #19334: textarea jumps when editing articles in IE8 - $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); # Enabled article-related sidebar, toplinks, etc. @@ -1993,9 +2094,14 @@ class EditPage { } elseif ( $contextTitle->exists() && $this->section != '' ) { $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection'; } else { - $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ? - 'editing' : 'creating'; + $msg = $contextTitle->exists() + || ( $contextTitle->getNamespace() == NS_MEDIAWIKI + && $contextTitle->getDefaultMessageText() !== false + ) + ? 'editing' + : 'creating'; } + # Use the title defined by DISPLAYTITLE magic word when present $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false; if ( $displayTitle === false ) { @@ -2045,14 +2151,15 @@ class EditPage { $username = $parts[0]; $user = User::newFromName( $username, false /* allow IP users*/ ); $ip = User::isIP( $username ); + $block = Block::newFromTarget( $user, $user ); if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>", array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) ); - } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked + } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked LogEventsList::showLogExtract( $wgOut, 'block', - $user->getUserPage(), + MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(), '', array( 'lim' => 1, @@ -2115,7 +2222,7 @@ class EditPage { if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) { global $wgOut; // Added using template syntax, to take <noinclude>'s into account. - $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle ); + $wgOut->addWikiTextTitleTidy( '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', $this->mTitle ); return true; } } @@ -2129,14 +2236,16 @@ class EditPage { * * If $content is null or false or a string, $content is returned unchanged. * - * If the given Content object is not of a type that can be edited using the text base EditPage, - * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual + * If the given Content object is not of a type that can be edited using + * the text base EditPage, an exception will be raised. Set + * $this->allowNonTextContent to true to allow editing of non-textual * content. * * @param Content|null|bool|string $content - * @return String the editable text form of the content. + * @return string The editable text form of the content. * - * @throws MWException if $content is not an instance of TextContent and $this->allowNonTextContent is not true. + * @throws MWException If $content is not an instance of TextContent and + * $this->allowNonTextContent is not true. */ protected function toEditText( $content ) { if ( $content === null || $content === false ) { @@ -2147,9 +2256,9 @@ class EditPage { return $content; } - if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) { - throw new MWException( "This content model can not be edited as text: " - . ContentHandler::getLocalizedName( $content->getModel() ) ); + if ( !$this->isSupportedContentModel( $content->getModel() ) ) { + throw new MWException( 'This content model is not supported: ' + . ContentHandler::getLocalizedName( $content->getModel() ) ); } return $content->serialize( $this->contentFormat ); @@ -2158,16 +2267,18 @@ class EditPage { /** * Turns the given text into a Content object by unserializing it. * - * If the resulting Content object is not of a type that can be edited using the text base EditPage, - * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual + * If the resulting Content object is not of a type that can be edited using + * the text base EditPage, an exception will be raised. Set + * $this->allowNonTextContent to true to allow editing of non-textual * content. * * @param string|null|bool $text Text to unserialize - * @return Content The content object created from $text. If $text was false or null, false resp. null will be - * returned instead. + * @return Content The content object created from $text. If $text was false + * or null, false resp. null will be returned instead. * - * @throws MWException if unserializing the text results in a Content object that is not an instance of TextContent - * and $this->allowNonTextContent is not true. + * @throws MWException If unserializing the text results in a Content + * object that is not an instance of TextContent and + * $this->allowNonTextContent is not true. */ protected function toEditContent( $text ) { if ( $text === false || $text === null ) { @@ -2177,8 +2288,8 @@ class EditPage { $content = ContentHandler::makeContent( $text, $this->getTitle(), $this->contentModel, $this->contentFormat ); - if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) { - throw new MWException( "This content model can not be edited as text: " + if ( !$this->isSupportedContentModel( $content->getModel() ) ) { + throw new MWException( 'This content model is not supported: ' . ContentHandler::getLocalizedName( $content->getModel() ) ); } @@ -2187,7 +2298,7 @@ class EditPage { /** * Send the edit form and related headers to $wgOut - * @param $formCallback Callback|null that takes an OutputPage parameter; will be called + * @param callable|null $formCallback That takes an OutputPage parameter; will be called * during form output near the top, for captchas and the like. */ function showEditForm( $formCallback = null ) { @@ -2235,9 +2346,16 @@ class EditPage { // @todo add EditForm plugin interface and use it here! // search for textarea1 and textares2, and allow EditForm to override all uses. - $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID, - 'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ), - 'enctype' => 'multipart/form-data' ) ) ); + $wgOut->addHTML( Html::openElement( + 'form', + array( + 'id' => self::EDITFORM_ID, + 'name' => self::EDITFORM_ID, + 'method' => 'post', + 'action' => $this->getActionURL( $this->getContextTitle() ), + 'enctype' => 'multipart/form-data' + ) + ) ); if ( is_callable( $formCallback ) ) { call_user_func_array( $formCallback, array( &$wgOut ) ); @@ -2246,8 +2364,20 @@ class EditPage { // Add an empty field to trip up spambots $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) ) - . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() ) - . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) ) + . Html::rawElement( + 'label', + array( 'for' => 'wpAntiSpam' ), + wfMessage( 'simpleantispam-label' )->parse() + ) + . Xml::element( + 'input', + array( + 'type' => 'text', + 'name' => 'wpAntispam', + 'id' => 'wpAntispam', + 'value' => '' + ) + ) . Xml::closeElement( 'div' ) ); @@ -2321,6 +2451,10 @@ class EditPage { $wgOut->addHTML( EditPage::getEditToolbar() ); } + if ( $this->blankArticle ) { + $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) ); + } + if ( $this->isConflict ) { // In an edit conflict bypass the overridable content form method // and fallback to the raw wpTextbox1 since editconflicts can't be @@ -2364,11 +2498,19 @@ class EditPage { $this->showConflict(); } catch ( MWContentSerializationException $ex ) { // this can't really happen, but be nice if it does. - $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() ); + $msg = wfMessage( + 'content-failed-to-parse', + $this->contentModel, + $this->contentFormat, + $ex->getMessage() + ); $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' ); } } + // Marker for detecting truncated form data. This must be the last + // parameter sent in order to be of use, so do not move me. + $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) ); $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" ); if ( !$wgUser->getOption( 'previewontop' ) ) { @@ -2382,7 +2524,7 @@ class EditPage { * Extract the section title from current section text, if any. * * @param string $text - * @return Mixed|string or false + * @return string|bool String or false */ public static function extractSectionTitle( $text ) { preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches ); @@ -2394,8 +2536,12 @@ class EditPage { } } + /** + * @return bool + */ protected function showHeader() { global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang; + global $wgAllowUserCss, $wgAllowUserJs; if ( $this->mTitle->isTalkPage() ) { $wgOut->addWikiMsg( 'talkpagetext' ); @@ -2437,6 +2583,10 @@ class EditPage { $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' ); } + if ( $this->blankArticle ) { + $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' ); + } + if ( $this->hookError !== '' ) { $wgOut->addWikiText( $this->hookError ); } @@ -2451,9 +2601,15 @@ class EditPage { // Let sysop know that this will make private content public if saved if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' ); + $wgOut->wrapWikiMsg( + "<div class='mw-warning plainlinks'>\n$1\n</div>\n", + 'rev-deleted-text-permission' + ); } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' ); + $wgOut->wrapWikiMsg( + "<div class='mw-warning plainlinks'>\n$1\n</div>\n", + 'rev-deleted-text-view' + ); } if ( !$revision->isCurrent() ) { @@ -2470,32 +2626,55 @@ class EditPage { } if ( wfReadOnly() ) { - $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) ); + $wgOut->wrapWikiMsg( + "<div id=\"mw-read-only-warning\">\n$1\n</div>", + array( 'readonlywarning', wfReadOnlyReason() ) + ); } elseif ( $wgUser->isAnon() ) { if ( $this->formtype != 'preview' ) { - $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' ); + $wgOut->wrapWikiMsg( + "<div id='mw-anon-edit-warning'>\n$1\n</div>", + array( 'anoneditwarning', + // Log-in link + '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}', + // Sign-up link + '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' ) + ); } else { - $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' ); + $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", + 'anonpreviewwarning' + ); } } else { if ( $this->isCssJsSubpage ) { # Check the skin exists if ( $this->isWrongCaseCssJsPage ) { - $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) ); + $wgOut->wrapWikiMsg( + "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", + array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) + ); } if ( $this->formtype !== 'preview' ) { - if ( $this->isCssSubpage ) { - $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) ); + if ( $this->isCssSubpage && $wgAllowUserCss ) { + $wgOut->wrapWikiMsg( + "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", + array( 'usercssyoucanpreview' ) + ); } - if ( $this->isJsSubpage ) { - $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) ); + if ( $this->isJsSubpage && $wgAllowUserJs ) { + $wgOut->wrapWikiMsg( + "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", + array( 'userjsyoucanpreview' ) + ); } } } } - if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) { + if ( $this->mTitle->isProtected( 'edit' ) && + MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' ) + ) { # Is the title semi-protected? if ( $this->mTitle->isSemiProtected() ) { $noticeMsg = 'semiprotectedpagewarning'; @@ -2534,16 +2713,27 @@ class EditPage { if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) { $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>", - array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) ); + array( + 'longpageerror', + $wgLang->formatNum( $this->kblength ), + $wgLang->formatNum( $wgMaxArticleSize ) + ) + ); } else { if ( !wfMessage( 'longpage-hint' )->isDisabled() ) { $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>", - array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) ) + array( + 'longpage-hint', + $wgLang->formatSize( strlen( $this->textbox1 ) ), + strlen( $this->textbox1 ) + ) ); } } # Add header copyright warning $this->showHeaderCopyrightWarning(); + + return true; } /** @@ -2555,12 +2745,14 @@ class EditPage { * * @param string $summary The value of the summary input * @param string $labelText The html to place inside the label - * @param array $inputAttrs of attrs to use on the input - * @param array $spanLabelAttrs of attrs to use on the span inside the label + * @param array $inputAttrs Array of attrs to use on the input + * @param array $spanLabelAttrs Array of attrs to use on the span inside the label * * @return array An array in the format array( $label, $input ) */ - function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) { + function getSummaryInput( $summary = "", $labelText = null, + $inputAttrs = null, $spanLabelAttrs = null + ) { // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters. $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array( 'id' => 'wpSummary', @@ -2577,7 +2769,11 @@ class EditPage { $label = null; if ( $labelText ) { - $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText ); + $label = Xml::tags( + 'label', + $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, + $labelText + ); $label = Xml::tags( 'span', $spanLabelAttrs, $label ); } @@ -2587,11 +2783,10 @@ class EditPage { } /** - * @param $isSubjectPreview Boolean: true if this is the section subject/title - * up top, or false if this is the comment summary - * down below the textarea + * @param bool $isSubjectPreview True if this is the section subject/title + * up top, or false if this is the comment summary + * down below the textarea * @param string $summary The text of the summary to display - * @return String */ protected function showSummaryInput( $isSubjectPreview, $summary = "" ) { global $wgOut, $wgContLang; @@ -2608,16 +2803,21 @@ class EditPage { } $summary = $wgContLang->recodeForEdit( $summary ); $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse(); - list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() ); + list( $label, $input ) = $this->getSummaryInput( + $summary, + $labelText, + array( 'class' => $summaryClass ), + array() + ); $wgOut->addHTML( "{$label} {$input}" ); } /** - * @param $isSubjectPreview Boolean: true if this is the section subject/title - * up top, or false if this is the comment summary - * down below the textarea - * @param string $summary the text of the summary to display - * @return String + * @param bool $isSubjectPreview True if this is the section subject/title + * up top, or false if this is the comment summary + * down below the textarea + * @param string $summary The text of the summary to display + * @return string */ protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) { // avoid spaces in preview, gets always trimmed on save @@ -2635,7 +2835,8 @@ class EditPage { $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview'; - $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview ); + $summary = wfMessage( $message )->parse() + . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview ); return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary ); } @@ -2689,15 +2890,17 @@ HTML * The $textoverride method can be used by subclasses overriding showContentForm * to pass back to this method. * - * @param array $customAttribs of html attributes to use in the textarea - * @param string $textoverride optional text to override $this->textarea1 with + * @param array $customAttribs Array of html attributes to use in the textarea + * @param string $textoverride Optional text to override $this->textarea1 with */ protected function showTextbox1( $customAttribs = null, $textoverride = null ) { if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) { $attribs = array( 'style' => 'display:none;' ); } else { $classes = array(); // Textarea CSS - if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) { + if ( $this->mTitle->isProtected( 'edit' ) && + MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' ) + ) { # Is the title semi-protected? if ( $this->mTitle->isSemiProtected() ) { $classes[] = 'mw-textarea-sprotected'; @@ -2725,7 +2928,11 @@ HTML } } - $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs ); + $this->showTextbox( + $textoverride !== null ? $textoverride : $this->textbox1, + 'wpTextbox1', + $attribs + ); } protected function showTextbox2() { @@ -2749,7 +2956,9 @@ HTML 'id' => $name, 'cols' => $wgUser->getIntOption( 'cols' ), 'rows' => $wgUser->getIntOption( 'rows' ), - 'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work + // Avoid PHP notices when appending preferences + // (appending allows customAttribs['style'] to still work). + 'style' => '' ); $pageLang = $this->mTitle->getPageLanguage(); @@ -2784,7 +2993,12 @@ HTML try { $this->showDiff(); } catch ( MWContentSerializationException $ex ) { - $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() ); + $msg = wfMessage( + 'content-failed-to-parse', + $this->contentModel, + $this->contentFormat, + $ex->getMessage() + ); $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' ); } } @@ -2794,7 +3008,7 @@ HTML * Append preview output to $wgOut. * Includes category rendering if this is a category page. * - * @param string $text the HTML to be output for the preview. + * @param string $text The HTML to be output for the preview. */ protected function showPreview( $text ) { global $wgOut; @@ -2914,6 +3128,7 @@ HTML * Get the copyright warning * * Renamed to getCopyrightWarning(), old name kept around for backwards compatibility + * @return string */ protected function getCopywarn() { return self::getCopyrightWarning( $this->mTitle ); @@ -2923,8 +3138,7 @@ HTML * Get the copyright warning, by default returns wikitext * * @param Title $title - * @param string $format output format, valid values are any function of - * a Message object + * @param string $format Output format, valid values are any function of a Message object * @return string */ public static function getCopyrightWarning( $title, $format = 'plain' ) { @@ -2972,7 +3186,7 @@ HTML foreach ( $output->getLimitReportData() as $key => $value ) { if ( wfRunHooks( 'ParserLimitReportFormat', - array( $key, $value, &$limitReport, true, true ) + array( $key, &$value, &$limitReport, true, true ) ) ) { $keyMsg = wfMessage( $key ); $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) ); @@ -2998,7 +3212,7 @@ HTML } protected function showStandardInputs( &$tabindex = 2 ) { - global $wgOut; + global $wgOut, $wgUseMediaWikiUIEverywhere; $wgOut->addHTML( "<div class='editOptions'>\n" ); if ( $this->section != 'new' ) { @@ -3023,14 +3237,26 @@ HTML array( 'class' => 'mw-editButtons-pipe-separator' ), wfMessage( 'pipe-separator' )->text() ); } - $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() ); - $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' . - wfMessage( 'edithelp |