From: jenkins-bot Date: Wed, 3 Sep 2014 03:51:55 +0000 (+0000) Subject: Merge "Use protocol default port with HTTP_X_FORWARDED_PROTO" X-Git-Tag: 1.31.0-rc.0~14187 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_aide%28?a=commitdiff_plain;h=e59e1e3c3b39642b6102567a23456cfc14b52ea7;hp=e2fb163be2cc558a6100a3e1af7ec0dbce2d4927;p=lhc%2Fweb%2Fwiklou.git Merge "Use protocol default port with HTTP_X_FORWARDED_PROTO" --- diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index c2a1e2b70c..aea6ec5451 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -42,8 +42,8 @@ production. configurations are $wgDeletedDirectory and $wgHashedUploadDirectory. * The deprecated $wgUseCommaCount variable has been removed. * $wgEnableSorbs and $wgSorbsUrl have been removed. -* The UserCryptPassword and UserComparePassword hooks are no longer called. Any extensions - using them must be updated to use the Password Hashing API. +* The UserCryptPassword and UserComparePassword hooks are no longer called. + Any extensions using them must be updated to use the Password Hashing API. * $wgCompiledFiles has been removed. * $wgSortSpecialPages was removed, the listing on Special:SpecialPages is now always sorted. @@ -65,8 +65,15 @@ production. may need to adjust your default user settings to compensate for the index change. * $wgDeferredUpdateList is now deprecated, you should use DeferredUpdates::addUpdate() instead. +* $wgCanonicalLanguageLinks has been removed. Per Google recommendations, we + will not send a rel=canonical pointing to a variant-neutral page, however + we will send rel=alternate. === New features in 1.24 === +* Added new hook WatchlistEditorBeforeFormRender, allowing subscribers to + manipulate the list of pages and/or preload lots of data at once. +* Added new argument &$link in hook WatchlistEditorBuildRemoveLine, allowing the + link to the title to be changed. * Added a new hook, "WhatLinksHereProps", to allow extensions to annotate WhatLinksHere entries. * Added a new hook, "ContentGetParserOutput", to customize parser output for @@ -171,6 +178,9 @@ production. * (bug 35045) Redirects to sections will now update the URL in browser's address bar using the HTML5 History API. When [[Dog]] redirects to [[Animals#Dog]], the user will now see "Animals#Dog" in their browser instead of "Dog#Dog". +* API token handling has been rewritten. Any API module using tokens will need + to be updated. See the entry below under "Action API internal changes". +* Added HTMLAutoCompleteSelectField. === Bug fixes in 1.24 === * (bug 50572) MediaWiki:Blockip should support gender @@ -203,21 +213,22 @@ production. * (bugs 57238, 65206) Blank pages can now be directly created. * (bug 69789) Title::getContentModel() now loads from the database when necessary instead of incorrectly returning the default content model. +* (bug 69249) wfBaseConvert() now works around PHP Bug #50175 when using GMP. -=== Web API changes in 1.24 === +=== Action API changes in 1.24 === * action=parse API now supports prop=modules, which provides the list of ResourceLoader modules that should be used to enhance the parsed content. * action=query&meta=siteinfo&siprop=interwikimap returns a new "protorel" - field which is true iff protocol-relative urls can be used to access + field which is true if protocol-relative urls can be used to access a particular interwiki map entry. -* ApiQueryLogEvents now provides logpage, which is the page ID from the +* list=logevents now provides logpage, which is the page ID from the logging table, if ids are requested and the user has the permissions. * action=edit now requires that appendtext, prependtext, or section=new be used when using the 'redirect' parameter, to prevent clients accidentally overwriting the target page with the content of the redirect. -* action=logevents will now return an error if both letitle and leprefix are +* list=logevents will now return an error if both letitle and leprefix are specified. -* action=logevents has a new parameter, lenamespace, to allow filtering by +* list=logevents has a new parameter, lenamespace, to allow filtering by namespace. * action=expandtemplates has a new parameter, prop, and a new output format. The old format is still used if prop isn't provided, but this is deprecated. @@ -229,6 +240,21 @@ production. * (bug 60734) Actions that use ApiPageSet (e.g. purge, watch, setnotificationtimestamp) will now include continuation information when using a generator. +* Removed 'props' and 'errors' from action=paraminfo, as they have extremely + limited use and are generally inaccurate, unmaintained, and impossible to + properly maintain. +* Formats dbg, dump, txt, wddx, and yaml are now deprecated. +* action=paraminfo now indicates when a parameter is specifying a submodule. +* The iwurl parameter to prop=iwlinks is deprecated in favor of iwprop=url, for + parallelism with prop=langlinks. +* All tokens should be fetched from action=query&meta=tokens; all other methods + of fetching tokens are deprecated. The value needed for meta=tokens's 'type' + parameter for each module is documented in the action=help output and is + returned from action=paraminfo. + +=== Action API internal changes in 1.24 === +* Methods for handling continuation are added to ApiResult, so actions other + than query that use generators can easily support continuation. * $wgAPIModules (and the related $wgAPIFormatModules, $wgAPIMetaModules, $wgAPIPropModules, and $wgAPIListModules settings) now allow API modules to be specified using a "module spec" array instead of a plain class name. @@ -237,16 +263,66 @@ production. to use for the module. This is intended for extensions that want control over the instantiation of their API modules, to allow for proper dependency injection. -* Removed 'props' and 'errors' from action=paraminfo, as they have extremely - limited use and are generally inaccurate, unmaintained, and impossible to - properly maintain. Also removed the corresponding methods from ApiBase and - the 'APIGetPossibleErrors' and 'APIGetResultProperties' hooks. -* Formats dbg, dump, txt, wddx, and yaml are now deprecated. -* action=paraminfo now indicates when a parameter is specifying a submodule. - Internally, a new param type 'submodule' is available to indicate this which - automatically queries the list of submodule names from the ApiModuleManager. -* The iwurl parameter to prop=iwlinks is deprecated in favor of iwprop=url, for - parallelism with prop=langlinks. +* A new param type 'submodule' is available. Parameters of this type will take + the list of valid values from the module's ApiModuleManager for the group + corresponding to the parameter name. +* The 'APIGetPossibleErrors' and 'APIGetResultProperties' hooks are no longer used. +* API token handling has been rewritten. Any API module using tokens will need + to be updated: + * ApiBase::needsToken now returns a token type instead of boolean true when a + token is needed. Returning true will throw an exception. See documentation + of that method for details. + * Information for the 'token' parameter is automatically set by ApiBase + getFinalParams and getFinalParamDescription. + * ApiBase::getTokenSalt has been removed. + * The hooks APIQueryInfoTokens, APIQueryRevisionsTokens, + APIQueryRecentChangesTokens, APIQueryUsersTokens, and + ApiTokensGetTokenTypes are deprecated, but are still called to support + backwards-compatible token access. +* ApiBase::validateLimit and ApiBase::validateTimestamp are now protected. +* The following methods have been deprecated and may be removed in a future + release: + * ApiBase::getResultProperties + * ApiBase::getFinalResultProperties + * ApiBase::addTokenProperties + * ApiBase::getRequireOnlyOneParameterErrorMessages + * ApiBase::getRequireMaxOneParameterErrorMessages + * ApiBase::getRequireAtLeastOneParameterErrorMessages + * ApiBase::getTitleOrPageIdErrorMessage + * ApiBase::getPossibleErrors + * ApiBase::getFinalPossibleErrors + * ApiBase::parseErrors + * ApiQuery::setGeneratorContinue + * ApiQueryBase::checkRowCount + * ApiQueryBase::titleToKey + * ApiQueryBase::keyToTitle + * ApiQueryBase::keyPartToTitle + * ApiQueryInfo::getTokenFunctions + * ApiQueryInfo::resetTokenCache + * ApiQueryInfo::getEditToken + * ApiQueryInfo::getDeleteToken + * ApiQueryInfo::getProtectToken + * ApiQueryInfo::getMoveToken + * ApiQueryInfo::getBlockToken + * ApiQueryInfo::getUnblockToken + * ApiQueryInfo::getEmailToken + * ApiQueryInfo::getImportToken + * ApiQueryInfo::getWatchToken + * ApiQueryInfo::getOptionsToken + * ApiQueryRecentChanges::getTokenFunctions + * ApiQueryRecentChanges::getPatrolToken + * ApiQueryRevisions::getTokenFunctions + * ApiQueryRevisions::getRollbackToken + * ApiQueryUsers::getTokenFunctions + * ApiQueryUsers::getUserrightsToken +* The following classes have been deprecated and may be removed in a future + release: + * ApiFormatDbg + * ApiFormatDump + * ApiFormatTxt + * ApiFormatWddx + * ApiFormatYaml + * ApiTokens === Languages updated in 1.24 === @@ -357,6 +433,12 @@ changes to languages because of Bugzilla reports. * Removed WikiPage::estimateRevisionCount(). (deprecated since 1.19) * MYSQL: Enum item added to "major MIME type" columns. Running update.php on MySQL < v5.1 may result in heavy processing. +* RSS and Atom feeds generated by MediaWiki no longer include a fallback + stylesheet. It was ignored by most browsers these days anyway. +* SpecialSearchNoResults hook has been removed. SpecialSearchResults is now + called unconditionally. +* TablePager::getBody() is now 'final' and can't be overridden in subclasses. +* TablePager::getBody() is deprecated, use getBodyOutput() or getFullOutput(). ==== Renamed classes ==== * CLDRPluralRuleConverter_Expression to CLDRPluralRuleConverterExpression diff --git a/docs/hooks.txt b/docs/hooks.txt index d704eb54be..82da289324 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -402,36 +402,39 @@ an action=query submodule. Use this to extend core API modules. &$module: Module object &$resultPageSet: ApiPageSet object -'APIQueryInfoTokens': Use this hook to add custom tokens to prop=info. Every -token has an action, which will be used in the intoken parameter and in the -output (actiontoken="..."), and a callback function which should return the -token, or false if the user isn't allowed to obtain it. The prototype of the -callback function is func($pageid, $title), where $pageid is the page ID of the -page the token is requested for and $title is the associated Title object. In -the hook, just add your callback to the $tokenFunctions array and return true -(returning false makes no sense). +'APIQueryInfoTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead. +Use this hook to add custom tokens to prop=info. Every token has an action, +which will be used in the intoken parameter and in the output +(actiontoken="..."), and a callback function which should return the token, or +false if the user isn't allowed to obtain it. The prototype of the callback +function is func($pageid, $title), where $pageid is the page ID of the page the +token is requested for and $title is the associated Title object. In the hook, +just add your callback to the $tokenFunctions array and return true (returning +false makes no sense). $tokenFunctions: array(action => callback) -'APIQueryRevisionsTokens': Use this hook to add custom tokens to prop=revisions. -Every token has an action, which will be used in the rvtoken parameter and in -the output (actiontoken="..."), and a callback function which should return the -token, or false if the user isn't allowed to obtain it. The prototype of the -callback function is func($pageid, $title, $rev), where $pageid is the page ID -of the page associated to the revision the token is requested for, $title the +'APIQueryRevisionsTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead. +Use this hook to add custom tokens to prop=revisions. Every token has an +action, which will be used in the rvtoken parameter and in the output +(actiontoken="..."), and a callback function which should return the token, or +false if the user isn't allowed to obtain it. The prototype of the callback +function is func($pageid, $title, $rev), where $pageid is the page ID of the +page associated to the revision the token is requested for, $title the associated Title object and $rev the associated Revision object. In the hook, just add your callback to the $tokenFunctions array and return true (returning false makes no sense). $tokenFunctions: array(action => callback) -'APIQueryRecentChangesTokens': Use this hook to add custom tokens to -list=recentchanges. Every token has an action, which will be used in the rctoken -parameter and in the output (actiontoken="..."), and a callback function which -should return the token, or false if the user isn't allowed to obtain it. The -prototype of the callback function is func($pageid, $title, $rc), where $pageid -is the page ID of the page associated to the revision the token is requested -for, $title the associated Title object and $rc the associated RecentChange -object. In the hook, just add your callback to the $tokenFunctions array and -return true (returning false makes no sense). +'APIQueryRecentChangesTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead. +Use this hook to add custom tokens to list=recentchanges. Every token has an +action, which will be used in the rctoken parameter and in the output +(actiontoken="..."), and a callback function which should return the token, or +false if the user isn't allowed to obtain it. The prototype of the callback +function is func($pageid, $title, $rc), where $pageid is the page ID of the +page associated to the revision the token is requested for, $title the +associated Title object and $rc the associated RecentChange object. In the +hook, just add your callback to the $tokenFunctions array and return true +(returning false makes no sense). $tokenFunctions: array(action => callback) 'APIQuerySiteInfoGeneralInfo': Use this hook to add extra information to the @@ -443,13 +446,19 @@ $module: the current ApiQuerySiteInfo module sites statistics information. &$results: array of results, add things here -'APIQueryUsersTokens': Use this hook to add custom token to list=users. Every -token has an action, which will be used in the ustoken parameter and in the -output (actiontoken="..."), and a callback function which should return the -token, or false if the user isn't allowed to obtain it. The prototype of the -callback function is func($user) where $user is the User object. In the hook, -just add your callback to the $tokenFunctions array and return true (returning -false makes no sense). +'ApiQueryTokensRegisterTypes': Use this hook to add additional token types to +action=query&meta=tokens. Note that most modules will probably be able to use +the 'csrf' token instead of creating their own token types. +&$salts: array( type => salt to pass to User::getEditToken() ) + +'APIQueryUsersTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead. +Use this hook to add custom token to list=users. Every token has an action, +which will be used in the ustoken parameter and in the output +(actiontoken="..."), and a callback function which should return the token, or +false if the user isn't allowed to obtain it. The prototype of the callback +function is func($user) where $user is the User object. In the hook, just add +your callback to the $tokenFunctions array and return true (returning false +makes no sense). $tokenFunctions: array(action => callback) 'ApiMain::onException': Called by ApiMain::executeActionWithErrorHandling() when @@ -463,8 +472,8 @@ key for the array that represents the service data. In this data array, the key-value-pair identified by the apiLink key is required. &$apis: array of services -'ApiTokensGetTokenTypes': Use this hook to extend action=tokens with new token -types. +'ApiTokensGetTokenTypes': DEPRECATED! Use ApiQueryTokensRegisterTypes instead. +Use this hook to extend action=tokens with new token types. &$tokenTypes: supported token types in format 'type' => callback function used to retrieve this type of tokens. @@ -2504,16 +2513,11 @@ $specialSearch: SpecialSearch object ($this) $output: $wgOut $term: Search term specified by the user -'SpecialSearchResults': Called before search result display when there are -matches. +'SpecialSearchResults': Called before search result display $term: string of search term &$titleMatches: empty or SearchResultSet object &$textMatches: empty or SearchResultSet object -'SpecialSearchNoResults': Called before search result display when there are no -matches. -$term: string of search term - 'SpecialStatsAddExtra': Add extra statistic at the end of Special:Statistics. &$extraStats: Array to save the new stats ( $extraStats[''] => ; ) @@ -2938,12 +2942,18 @@ $page: WikiPage object to be watched $user: user that watched $page: WikiPage object watched +'WatchlistEditorBeforeFormRender': Before building the Special:EditWatchlist +form, used to manipulate the list of pages or preload data based on that list. +&$watchlistInfo: array of watchlisted pages in + [namespaceId => ['title1' => 1, 'title2' => 1]] format + 'WatchlistEditorBuildRemoveLine': when building remove lines in Special:Watchlist/edit. &$tools: array of extra links $title: Title object $redirect: whether the page is a redirect $skin: Skin object +&$link: HTML link to title 'WebRequestPathInfoRouter': While building the PathRouter to parse the REQUEST_URI. diff --git a/docs/uidesign/design.html b/docs/uidesign/design.html index a285a5b22f..51c1b55204 100644 --- a/docs/uidesign/design.html +++ b/docs/uidesign/design.html @@ -1,7 +1,7 @@ - + diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index dde8467f2f..9bc92be94c 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -47,12 +47,19 @@ 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"] ) ) { @@ -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 41cbd24ca7..8e9f490fa2 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -70,12 +70,19 @@ class AjaxResponse { */ private $mText; + /** + * @var Config + */ + private $mConfig; + /** * @param string|null $text + * @param Config|null $config */ - function __construct( $text = null ) { + 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 = ''; @@ -150,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 ); @@ -170,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 { diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 2c71ce869e..661f4d6410 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -37,7 +37,7 @@ $wgAutoloadLocalClasses = array( 'Autopromote' => 'includes/Autopromote.php', 'Block' => 'includes/Block.php', 'Category' => 'includes/Category.php', - 'Categoryfinder' => 'includes/Categoryfinder.php', + 'CategoryFinder' => 'includes/CategoryFinder.php', 'CategoryViewer' => 'includes/CategoryViewer.php', 'ChangeTags' => 'includes/ChangeTags.php', 'ChannelFeed' => 'includes/Feed.php', @@ -81,6 +81,7 @@ $wgAutoloadLocalClasses = array( 'Html' => 'includes/Html.php', 'HtmlFormatter' => 'includes/HtmlFormatter.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', @@ -227,7 +228,7 @@ $wgAutoloadLocalClasses = array( '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', @@ -301,6 +302,7 @@ $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', diff --git a/includes/CategoryFinder.php b/includes/CategoryFinder.php new file mode 100644 index 0000000000..9fd388352a --- /dev/null +++ b/includes/CategoryFinder.php @@ -0,0 +1,236 @@ + + * # 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->seed( + * array( 12345 ), + * array( 'Category 1', 'Category 2' ), + * 'AND' + * ); + * $a = $cf->run(); + * print implode( ',' , $a ); + * + * + */ +class CategoryFinder { + /** @var int[] The original article IDs passed to the seed function */ + protected $articles = array(); + + /** @var array Array of DBKEY category names for categories that don't have a page */ + protected $deadend = array(); + + /** @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 array $articleIds Array of article IDs + * @param array $categories FIXME + * @param string $mode FIXME, default 'AND'. + * @todo FIXME: $categories/$mode + */ + 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 + $this->targets = array(); + foreach ( $categories as $c ) { + $ct = Title::makeTitleSafe( NS_CATEGORY, $c ); + if ( $ct ) { + $c = $ct->getDBkey(); + $this->targets[$c] = $c; + } + } + } + + /** + * Iterates through the parent tree starting with the seed values, + * then checks the articles if they match the conditions + * @return array Array of page_ids (those given to seed() that match the conditions) + */ + public function run() { + $this->dbr = wfGetDB( DB_SLAVE ); + while ( count( $this->next ) > 0 ) { + $this->scanNextLayer(); + } + + # Now check if this applies to the individual articles + $ret = array(); + + foreach ( $this->articles as $article ) { + $conds = $this->targets; + if ( $this->check( $article, $conds ) ) { + # Matches the conditions + $ret[] = $article; + } + } + return $ret; + } + + /** + * 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 + * @return bool Does this match the conditions? + */ + private function check( $id, &$conds, $path = array() ) { + // Check for loops and stop! + if ( in_array( $id, $path ) ) { + return false; + } + + $path[] = $id; + + # Shortcut (runtime paranoia): No conditions=all matched + if ( count( $conds ) == 0 ) { + return true; + } + + if ( !isset( $this->parents[$id] ) ) { + return false; + } + + # iterate through the parents + foreach ( $this->parents[$id] as $p ) { + $pname = $p->cl_to; + + # Is this a condition? + if ( isset( $conds[$pname] ) ) { + # This key is in the category list! + if ( $this->mode == 'OR' ) { + # One found, that's enough! + $conds = array(); + return true; + } else { + # Assuming "AND" as default + unset( $conds[$pname] ); + if ( count( $conds ) == 0 ) { + # All conditions met, done + return true; + } + } + } + + # Not done yet, try sub-parents + if ( !isset( $this->name2id[$pname] ) ) { + # No sub-parent + continue; + } + $done = $this->check( $this->name2id[$pname], $conds, $path ); + if ( $done || count( $conds ) == 0 ) { + # Subparents have done it! + return true; + } + } + return false; + } + + /** + * Scans a "parent layer" of the articles/categories in $this->next + */ + private function scanNextLayer() { + $profiler = new ProfileSection( __METHOD__ ); + + # Find all parents of the article currently in $this->next + $layer = array(); + $res = $this->dbr->select( + /* FROM */ 'categorylinks', + /* SELECT */ '*', + /* WHERE */ array( 'cl_from' => $this->next ), + __METHOD__ . '-1' + ); + foreach ( $res as $o ) { + $k = $o->cl_to; + + # Update parent tree + if ( !isset( $this->parents[$o->cl_from] ) ) { + $this->parents[$o->cl_from] = array(); + } + $this->parents[$o->cl_from][$k] = $o; + + # Ignore those we already have + if ( in_array( $k, $this->deadend ) ) { + continue; + } + + if ( isset( $this->name2id[$k] ) ) { + continue; + } + + # Hey, new category! + $layer[$k] = $k; + } + + $this->next = array(); + + # Find the IDs of all category pages in $layer, if they exist + if ( count( $layer ) > 0 ) { + $res = $this->dbr->select( + /* FROM */ 'page', + /* SELECT */ array( 'page_id', 'page_title' ), + /* WHERE */ array( 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ), + __METHOD__ . '-2' + ); + foreach ( $res as $o ) { + $id = $o->page_id; + $name = $o->page_title; + $this->name2id[$name] = $id; + $this->next[] = $id; + unset( $layer[$name] ); + } + } + + # Mark dead ends + foreach ( $layer as $v ) { + $this->deadend[$v] = $v; + } + } +} diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index ec8efae7b7..60694c95e3 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -87,12 +87,11 @@ class CategoryViewer extends ContextSource { function __construct( $title, IContextSource $context, $from = array(), $until = array(), $query = array() ) { - global $wgCategoryPagingLimit; $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(); @@ -105,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(); diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php deleted file mode 100644 index a5415afd3e..0000000000 --- a/includes/Categoryfinder.php +++ /dev/null @@ -1,241 +0,0 @@ - - * # 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->seed( - * array( 12345 ), - * array( 'Category 1', 'Category 2' ), - * 'AND' - * ); - * $a = $cf->run(); - * print implode( ',' , $a ); - * - * - */ -class Categoryfinder { - /** @var int[] The original article IDs passed to the seed function */ - protected $articles = array(); - - /** @var array Array of DBKEY category names for categories that don't have a page */ - protected $deadend = array(); - - /** @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; - - function __construct() { - } - - /** - * Initializes the instance. Do this prior to calling run(). - * @param array $article_ids 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; - $this->mode = $mode; - - # Set the list of target categories; convert them to DBKEY form first - $this->targets = array(); - foreach ( $categories as $c ) { - $ct = Title::makeTitleSafe( NS_CATEGORY, $c ); - if ( $ct ) { - $c = $ct->getDBkey(); - $this->targets[$c] = $c; - } - } - } - - /** - * Iterates through the parent tree starting with the seed values, - * then checks the articles if they match the conditions - * @return array Array of page_ids (those given to seed() that match the conditions) - */ - function run() { - $this->dbr = wfGetDB( DB_SLAVE ); - while ( count( $this->next ) > 0 ) { - $this->scan_next_layer(); - } - - # Now check if this applies to the individual articles - $ret = array(); - - foreach ( $this->articles as $article ) { - $conds = $this->targets; - if ( $this->check( $article, $conds ) ) { - # Matches the conditions - $ret[] = $article; - } - } - return $ret; - } - - /** - * 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 - * @return bool Does this match the conditions? - */ - function check( $id, &$conds, $path = array() ) { - // Check for loops and stop! - if ( in_array( $id, $path ) ) { - return false; - } - - $path[] = $id; - - # Shortcut (runtime paranoia): No conditions=all matched - if ( count( $conds ) == 0 ) { - return true; - } - - if ( !isset( $this->parents[$id] ) ) { - return false; - } - - # iterate through the parents - foreach ( $this->parents[$id] as $p ) { - $pname = $p->cl_to; - - # Is this a condition? - if ( isset( $conds[$pname] ) ) { - # This key is in the category list! - if ( $this->mode == 'OR' ) { - # One found, that's enough! - $conds = array(); - return true; - } else { - # Assuming "AND" as default - unset( $conds[$pname] ); - if ( count( $conds ) == 0 ) { - # All conditions met, done - return true; - } - } - } - - # Not done yet, try sub-parents - if ( !isset( $this->name2id[$pname] ) ) { - # No sub-parent - continue; - } - $done = $this->check( $this->name2id[$pname], $conds, $path ); - if ( $done || count( $conds ) == 0 ) { - # Subparents have done it! - return true; - } - } - return false; - } - - /** - * Scans a "parent layer" of the articles/categories in $this->next - */ - function scan_next_layer() { - wfProfileIn( __METHOD__ ); - - # Find all parents of the article currently in $this->next - $layer = array(); - $res = $this->dbr->select( - /* FROM */ 'categorylinks', - /* SELECT */ '*', - /* WHERE */ array( 'cl_from' => $this->next ), - __METHOD__ . '-1' - ); - foreach ( $res as $o ) { - $k = $o->cl_to; - - # Update parent tree - if ( !isset( $this->parents[$o->cl_from] ) ) { - $this->parents[$o->cl_from] = array(); - } - $this->parents[$o->cl_from][$k] = $o; - - # Ignore those we already have - if ( in_array( $k, $this->deadend ) ) { - continue; - } - - if ( isset( $this->name2id[$k] ) ) { - continue; - } - - # Hey, new category! - $layer[$k] = $k; - } - - $this->next = array(); - - # Find the IDs of all category pages in $layer, if they exist - if ( count( $layer ) > 0 ) { - $res = $this->dbr->select( - /* FROM */ 'page', - /* SELECT */ array( 'page_id', 'page_title' ), - /* WHERE */ array( 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ), - __METHOD__ . '-2' - ); - foreach ( $res as $o ) { - $id = $o->page_id; - $name = $o->page_title; - $this->name2id[$name] = $id; - $this->next[] = $id; - unset( $layer[$name] ); - } - } - - # Mark dead ends - foreach ( $layer as $v ) { - $this->deadend[$v] = $v; - } - - wfProfileOut( __METHOD__ ); - } -} diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 14799c6464..5fc73776a9 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2699,11 +2699,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 */ diff --git a/includes/EditPage.php b/includes/EditPage.php index b83569d8fe..a14191a035 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -217,8 +217,13 @@ class EditPage { /** @var bool|stdClass */ protected $lastDelete; - /** @var bool */ - protected $mTokenOk = false; + /** @var bool + * This is public because SemanticForms uses it (bug 67522). + * However, please consider using this property publicly + * to be deprecated. + * @protected + */ + public $mTokenOk = false; /** @var bool */ protected $mTokenOkExceptSuffix = false; @@ -2425,9 +2430,7 @@ class EditPage { $wgOut->addHTML( $this->editFormTextBeforeContent ); - if ( $this->contentModel === CONTENT_MODEL_WIKITEXT && - $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) - { + if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) { $wgOut->addHTML( EditPage::getEditToolbar() ); } @@ -2612,9 +2615,18 @@ class EditPage { ); } elseif ( $wgUser->isAnon() ) { if ( $this->formtype != 'preview' ) { - $wgOut->wrapWikiMsg( "
\n$1
", 'anoneditwarning' ); + $wgOut->wrapWikiMsg( + "
\n$1\n
", + array( 'anoneditwarning', + // Log-in link + '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}', + // Sign-up link + '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' ) + ); } else { - $wgOut->wrapWikiMsg( "
\n$1
", 'anonpreviewwarning' ); + $wgOut->wrapWikiMsg( "
\n$1
", + 'anonpreviewwarning' + ); } } else { if ( $this->isCssJsSubpage ) { diff --git a/includes/Feed.php b/includes/Feed.php index 60e623b47d..03dd7f5c88 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -258,20 +258,11 @@ abstract class ChannelFeed extends FeedItem { } /** - * Output the initial XML headers with a stylesheet for legibility - * if someone finds it in a browser. + * Output the initial XML headers. */ protected function outXmlHeader() { - global $wgStylePath, $wgStyleVersion; - $this->httpHeaders(); echo '' . "\n"; - echo '\n"; } } diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index ddea620b81..cfe9a87dc1 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -3366,7 +3366,10 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, ); if ( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) { - $result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase ); + // Removing leading zeros works around broken base detection code in + // some PHP versions (see and + // ). + $result = gmp_strval( gmp_init( ltrim( $input, '0' ), $sourceBase ), $destBase ); } elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) { $decimal = '0'; foreach ( str_split( strtolower( $input ) ) as $char ) { diff --git a/includes/Linker.php b/includes/Linker.php index 05bace5dbf..f0b16ab981 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -909,10 +909,10 @@ class Linker { $thumb15 = $file->transform( $hp15 ); $thumb20 = $file->transform( $hp20 ); - if ( $thumb15 && $thumb15->getUrl() !== $thumb->getUrl() ) { + if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) { $thumb->responsiveUrls['1.5'] = $thumb15->getUrl(); } - if ( $thumb20 && $thumb20->getUrl() !== $thumb->getUrl() ) { + if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) { $thumb->responsiveUrls['2'] = $thumb20->getUrl(); } } diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index 281080cd00..9213c021ef 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -27,7 +27,6 @@ */ class MediaWiki { /** - * @todo Fold $output, etc, into this * @var IContextSource */ private $context; @@ -37,30 +36,6 @@ class MediaWiki { */ private $config; - /** - * @param null|WebRequest $x - * @return WebRequest - */ - public function request( WebRequest $x = null ) { - $old = $this->context->getRequest(); - if ( $x ) { - $this->context->setRequest( $x ); - } - return $old; - } - - /** - * @param null|OutputPage $x - * @return OutputPage - */ - public function output( OutputPage $x = null ) { - $old = $this->context->getOutput(); - if ( $x ) { - $this->context->setOutput( $x ); - } - return $old; - } - /** * @param IContextSource|null $context */ @@ -521,8 +496,8 @@ class MediaWiki { $this->context->setTitle( $title ); $wgTitle = $title; - $dispatcher = new AjaxDispatcher(); - $dispatcher->performAction(); + $dispatcher = new AjaxDispatcher( $this->config ); + $dispatcher->performAction( $this->context->getUser() ); wfProfileOut( __METHOD__ ); return; } @@ -682,7 +657,8 @@ class MediaWiki { $query = array( 'title' => 'Special:RunJobs', 'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 ); - $query['signature'] = SpecialRunJobs::getQuerySignature( $query ); + $query['signature'] = SpecialRunJobs::getQuerySignature( + $query, $this->config->get( 'SecretKey' ) ); $errno = $errstr = null; $info = wfParseUrl( $this->config->get( 'Server' ) ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 8d50e41562..af90ca6da4 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2797,7 +2797,9 @@ $templates ); } else { $links['html'] .= Html::inlineScript( - $resourceLoader->makeModuleResponse( $context, $grpModules ) + ResourceLoader::makeLoaderConditionalScript( + $resourceLoader->makeModuleResponse( $context, $grpModules ) + ) ); } $links['html'] .= "\n"; @@ -3336,25 +3338,23 @@ $templates } # Language variants - if ( !$config->get( 'DisableLangConversion' ) && $config->get( 'CanonicalLanguageLinks' ) ) { + if ( !$config->get( 'DisableLangConversion' ) ) { $lang = $this->getTitle()->getPageLanguage(); if ( $lang->hasVariants() ) { - - $urlvar = $lang->getURLVariant(); - - if ( !$urlvar ) { - $variants = $lang->getVariants(); - foreach ( $variants as $_v ) { - $tags["variant-$_v"] = Html::element( 'link', array( - 'rel' => 'alternate', - 'hreflang' => wfBCP47( $_v ), - 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) - ); - } - } else { - $canonicalUrl = $this->getTitle()->getLocalURL(); + $variants = $lang->getVariants(); + foreach ( $variants as $_v ) { + $tags["variant-$_v"] = Html::element( 'link', array( + 'rel' => 'alternate', + 'hreflang' => wfBCP47( $_v ), + 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) + ); } } + # x-default link per https://support.google.com/webmasters/answer/189077?hl=en + $tags["variant-x-default"] = Html::element( 'link', array( + 'rel' => 'alternate', + 'hreflang' => 'x-default', + 'href' => $this->getTitle()->getLocalURL() ) ); } # Copyright @@ -3504,6 +3504,8 @@ $templates if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) { # If wanted, and the interface is right-to-left, flip the CSS $style_css = CSSJanus::transform( $style_css, true, false ); + } else { + $style_css = CSSJanus::nullTransform( $style_css ); } $this->mInlineStyles .= Html::inlineStyle( $style_css ) . "\n"; } @@ -3554,6 +3556,8 @@ $templates $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) { $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); + } else { + $previewedCSS = CSSJanus::nullTransform( $previewedCSS ); } $otherTags .= Html::inlineStyle( $previewedCSS ) . "\n"; } else { diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php index 853e2cc412..4aa65d9403 100644 --- a/includes/ProtectionForm.php +++ b/includes/ProtectionForm.php @@ -57,16 +57,21 @@ class ProtectionForm { /** @var array Map of action to the expiry time of the existing protection */ protected $mExistingExpiry = array(); - function __construct( Page $article ) { - global $wgUser; + /** @var IContextSource */ + private $mContext; + + function __construct( Article $article ) { // Set instance variables. $this->mArticle = $article; $this->mTitle = $article->getTitle(); $this->mApplicableTypes = $this->mTitle->getRestrictionTypes(); + $this->mContext = $article->getContext(); // Check if the form should be disabled. // If it is, the form will be available in read-only to show levels. - $this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser ); + $this->mPermErrors = $this->mTitle->getUserPermissionsErrors( + 'protect', $this->mContext->getUser() + ); if ( wfReadOnly() ) { $this->mPermErrors[] = array( 'readonlytext', wfReadOnlyReason() ); } @@ -82,14 +87,15 @@ class ProtectionForm { * Loads the current state of protection into the object. */ function loadData() { - global $wgRequest, $wgUser; - - $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(), $wgUser ); + $levels = MWNamespace::getRestrictionLevels( + $this->mTitle->getNamespace(), $this->mContext->getUser() + ); $this->mCascade = $this->mTitle->areRestrictionsCascading(); - $this->mReason = $wgRequest->getText( 'mwProtect-reason' ); - $this->mReasonSelection = $wgRequest->getText( 'wpProtectReasonSelection' ); - $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade ); + $request = $this->mContext->getRequest(); + $this->mReason = $request->getText( 'mwProtect-reason' ); + $this->mReasonSelection = $request->getText( 'wpProtectReasonSelection' ); + $this->mCascade = $request->getBool( 'mwProtect-cascade', $this->mCascade ); foreach ( $this->mApplicableTypes as $action ) { // @todo FIXME: This form currently requires individual selections, @@ -106,8 +112,8 @@ class ProtectionForm { } $this->mExistingExpiry[$action] = $existingExpiry; - $requestExpiry = $wgRequest->getText( "mwProtect-expiry-$action" ); - $requestExpirySelection = $wgRequest->getVal( "wpProtectExpirySelection-$action" ); + $requestExpiry = $request->getText( "mwProtect-expiry-$action" ); + $requestExpirySelection = $request->getVal( "wpProtectExpirySelection-$action" ); if ( $requestExpiry ) { // Custom expiry takes precedence @@ -128,7 +134,7 @@ class ProtectionForm { $this->mExpirySelection[$action] = 'infinite'; } - $val = $wgRequest->getVal( "mwProtect-level-$action" ); + $val = $request->getVal( "mwProtect-level-$action" ); if ( isset( $val ) && in_array( $val, $levels ) ) { $this->mRestrictions[$action] = $val; } @@ -170,16 +176,14 @@ class ProtectionForm { * Main entry point for action=protect and action=unprotect */ function execute() { - global $wgRequest, $wgOut; - if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === array( '' ) ) { throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' ); } - if ( $wgRequest->wasPosted() ) { + if ( $this->mContext->getRequest()->wasPosted() ) { if ( $this->save() ) { $q = $this->mArticle->isRedirect() ? 'redirect=no' : ''; - $wgOut->redirect( $this->mTitle->getFullURL( $q ) ); + $this->mContext->getOutput()->redirect( $this->mTitle->getFullURL( $q ) ); } } else { $this->show(); @@ -192,28 +196,27 @@ class ProtectionForm { * @param string $err Error message or null if there's no error */ function show( $err = null ) { - global $wgOut; - - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->addBacklinkSubtitle( $this->mTitle ); + $out = $this->mContext->getOutput(); + $out->setRobotPolicy( 'noindex,nofollow' ); + $out->addBacklinkSubtitle( $this->mTitle ); if ( is_array( $err ) ) { - $wgOut->wrapWikiMsg( "

\n$1\n

\n", $err ); + $out->wrapWikiMsg( "

\n$1\n

\n", $err ); } elseif ( is_string( $err ) ) { - $wgOut->addHTML( "

{$err}

\n" ); + $out->addHTML( "

{$err}

\n" ); } if ( $this->mTitle->getRestrictionTypes() === array() ) { // No restriction types available for the current title // this might happen if an extension alters the available types - $wgOut->setPageTitle( wfMessage( + $out->setPageTitle( wfMessage( 'protect-norestrictiontypes-title', $this->mTitle->getPrefixedText() ) ); - $wgOut->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() ); + $out->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() ); // Show the log in case protection was possible once - $this->showLogExtract( $wgOut ); + $this->showLogExtract( $out ); // return as there isn't anything else we can do return; } @@ -227,7 +230,7 @@ class ProtectionForm { } /** @todo FIXME: i18n issue, should use formatted number. */ - $wgOut->wrapWikiMsg( + $out->wrapWikiMsg( "
\n$1\n" . $titles . "
", array( 'protect-cascadeon', count( $cascadeSources ) ) ); @@ -236,19 +239,19 @@ class ProtectionForm { # Show an appropriate message if the user isn't allowed or able to change # the protection settings at this time if ( $this->disabled ) { - $wgOut->setPageTitle( + $out->setPageTitle( wfMessage( 'protect-title-notallowed', $this->mTitle->getPrefixedText() ) ); - $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) ); + $out->addWikiText( $out->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) ); } else { - $wgOut->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) ); - $wgOut->addWikiMsg( 'protect-text', + $out->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) ); + $out->addWikiMsg( 'protect-text', wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ); } - $wgOut->addHTML( $this->buildForm() ); - $this->showLogExtract( $wgOut ); + $out->addHTML( $this->buildForm() ); + $this->showLogExtract( $out ); } /** @@ -257,16 +260,17 @@ class ProtectionForm { * @return bool Success */ function save() { - global $wgRequest, $wgUser, $wgOut; - # Permission check! if ( $this->disabled ) { $this->show(); return false; } - $token = $wgRequest->getVal( 'wpEditToken' ); - if ( !$wgUser->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) { + $request = $this->mContext->getRequest(); + $user = $this->mContext->getUser(); + $out = $this->mContext->getOutput(); + $token = $request->getVal( 'wpEditToken' ); + if ( !$user->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) { $this->show( array( 'sessionfailure' ) ); return false; } @@ -295,18 +299,18 @@ class ProtectionForm { } } - $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' ); + $this->mCascade = $request->getBool( 'mwProtect-cascade' ); $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, - $wgUser + $user ); if ( !$status->isOK() ) { - $this->show( $wgOut->parseInline( $status->getWikiText() ) ); + $this->show( $out->parseInline( $status->getWikiText() ) ); return false; } @@ -327,7 +331,7 @@ class ProtectionForm { return false; } - WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'mwProtectWatch' ), $this->mTitle, $wgUser ); + WatchAction::doWatchOrUnwatch( $request->getCheck( 'mwProtectWatch' ), $this->mTitle, $user ); return true; } @@ -338,12 +342,14 @@ class ProtectionForm { * @return string HTML form */ function buildForm() { - global $wgUser, $wgLang, $wgOut, $wgCascadingRestrictionLevels; - + $user = $this->mContext->getUser(); + $output = $this->mContext->getOutput(); + $lang = $this->mContext->getLanguage(); + $cascadingRestrictionLevels = $this->mContext->getConfig()->get( 'CascadingRestrictionLevels' ); $out = ''; if ( !$this->disabled ) { - $wgOut->addModules( 'mediawiki.legacy.protect' ); - $wgOut->addJsConfigVars( 'wgCascadeableLevels', $wgCascadingRestrictionLevels ); + $output->addModules( 'mediawiki.legacy.protect' ); + $output->addJsConfigVars( 'wgCascadeableLevels', $cascadingRestrictionLevels ); $out .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=protect' ), 'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) ); @@ -379,9 +385,9 @@ class ProtectionForm { $expiryFormOptions = ''; if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) { - $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action], true ); - $d = $wgLang->date( $this->mExistingExpiry[$action], true ); - $t = $wgLang->time( $this->mExistingExpiry[$action], true ); + $timestamp = $lang->timeanddate( $this->mExistingExpiry[$action], true ); + $d = $lang->date( $this->mExistingExpiry[$action], true ); + $t = $lang->time( $this->mExistingExpiry[$action], true ); $expiryFormOptions .= Xml::option( wfMessage( 'protect-existing-expiry', $timestamp, $d, $t )->text(), @@ -508,14 +514,14 @@ class ProtectionForm { " "; # Disallow watching is user is not logged in - if ( $wgUser->isLoggedIn() ) { + if ( $user->isLoggedIn() ) { $out .= " " . Xml::checkLabel( wfMessage( 'watchthis' )->text(), 'mwProtectWatch', 'mwProtectWatch', - $wgUser->isWatched( $this->mTitle ) || $wgUser->getOption( 'watchdefault' ) ) . + $user->isWatched( $this->mTitle ) || $user->getOption( 'watchdefault' ) ) . " "; } @@ -533,7 +539,7 @@ class ProtectionForm { } $out .= Xml::closeElement( 'fieldset' ); - if ( $wgUser->isAllowed( 'editinterface' ) ) { + if ( $user->isAllowed( 'editinterface' ) ) { $title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' ); $link = Linker::link( $title, @@ -547,10 +553,10 @@ class ProtectionForm { if ( !$this->disabled ) { $out .= Html::hidden( 'wpEditToken', - $wgUser->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) + $user->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ); $out .= Xml::closeElement( 'form' ); - $wgOut->addScript( $this->buildCleanupScript() ); + $output->addScript( $this->buildCleanupScript() ); } return $out; @@ -564,12 +570,10 @@ class ProtectionForm { * @return string HTML fragment */ function buildSelector( $action, $selected ) { - global $wgUser; - // If the form is disabled, display all relevant levels. Otherwise, // just show the ones this user can use. $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(), - $this->disabled ? null : $wgUser + $this->disabled ? null : $this->mContext->getUser() ); $id = 'mwProtect-level-' . $action; diff --git a/includes/Revision.php b/includes/Revision.php index cad9f2ce59..28a825d051 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -1658,7 +1658,7 @@ class Revision implements IDBAccessObject { * instead of just plain userrights * @return bool */ - public static function userCanBitfield( $bitfield, $field, User $user = null , + public static function userCanBitfield( $bitfield, $field, User $user = null, Title $title = null ) { if ( $bitfield & $field ) { // aspect is deleted @@ -1680,7 +1680,7 @@ class Revision implements IDBAccessObject { } else { $text = $title->getPrefixedText(); wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" ); - foreach( $permissions as $perm ) { + foreach ( $permissions as $perm ) { if ( $title->userCan( $perm, $user ) ) { return true; } diff --git a/includes/Setup.php b/includes/Setup.php index acceb59d04..cddb30bd4d 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -265,16 +265,16 @@ if ( $wgSkipSkin ) { // Register skins // Use a closure to avoid leaking into global state -call_user_func( function() use ( $wgValidSkinNames ) { +call_user_func( function () use ( $wgValidSkinNames ) { $factory = SkinFactory::getDefaultInstance(); foreach ( $wgValidSkinNames as $name => $skin ) { - $factory->register( $name, $skin, function() use ( $name, $skin ) { + $factory->register( $name, $skin, function () use ( $name, $skin ) { $class = "Skin$skin"; return new $class( $name ); } ); } // Register a hidden "fallback" skin - $factory->register( 'fallback', 'Fallback', function() { + $factory->register( 'fallback', 'Fallback', function () { return new SkinFallback; } ); } ); @@ -596,7 +596,7 @@ if ( !is_object( $wgAuth ) ) { $wgTitle = null; /** - * @deprecated 1.24 Use DeferredUpdates::addUpdate instead + * @deprecated since 1.24 Use DeferredUpdates::addUpdate instead * @var array */ $wgDeferredUpdateList = array(); diff --git a/includes/Title.php b/includes/Title.php index bcf9cbd851..7fdeb05511 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -2265,7 +2265,7 @@ class Title { } elseif ( $action == 'delete' ) { $tempErrors = $this->checkPageRestrictions( 'edit', $user, array(), $doExpensiveQueries, true ); - if( !$tempErrors ) { + if ( !$tempErrors ) { $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit', $user, $tempErrors, $doExpensiveQueries, true ); } diff --git a/includes/User.php b/includes/User.php index 665edb973c..6b42994ef7 100644 --- a/includes/User.php +++ b/includes/User.php @@ -829,7 +829,7 @@ class User implements IDBAccessObject { * @param int $ts Optional timestamp to convert, default 0 for the current time */ public function expirePassword( $ts = 0 ) { - $this->load(); + $this->loadPasswords(); $timestamp = wfTimestamp( TS_MW, $ts ); $this->mPasswordExpires = $timestamp; $this->saveSettings(); diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php index d9a1e80ec7..ab136b8954 100644 --- a/includes/WatchedItem.php +++ b/includes/WatchedItem.php @@ -274,7 +274,7 @@ class WatchedItem { * @param WatchedItem[] $items * @return bool */ - static public function batchAddWatch( array $items ) { + public static function batchAddWatch( array $items ) { $section = new ProfileSection( __METHOD__ ); if ( wfReadOnly() ) { @@ -309,7 +309,7 @@ class WatchedItem { } $dbw = wfGetDB( DB_MASTER ); - foreach( array_chunk( $rows, 100 ) as $toInsert ) { + foreach ( array_chunk( $rows, 100 ) as $toInsert ) { // Use INSERT IGNORE to avoid overwriting the notification timestamp // if there's already an entry for this page $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' ); diff --git a/includes/actions/Action.php b/includes/actions/Action.php index 7dd46161ac..ffdf5168f2 100644 --- a/includes/actions/Action.php +++ b/includes/actions/Action.php @@ -38,18 +38,21 @@ abstract class Action { /** * Page on which we're performing the action + * @since 1.17 * @var WikiPage|Article|ImagePage|CategoryPage|Page $page */ protected $page; /** * IContextSource if specified; otherwise we'll use the Context from the Page + * @since 1.17 * @var IContextSource $context */ protected $context; /** * The fields used to create the HTMLForm + * @since 1.17 * @var array $fields */ protected $fields; @@ -82,6 +85,7 @@ abstract class Action { /** * Get an appropriate Action subclass for the given action + * @since 1.17 * @param string $action * @param Page $page * @param IContextSource $context @@ -152,6 +156,7 @@ abstract class Action { /** * Check if a given action is recognised, even if it's disabled + * @since 1.17 * * @param string $name Name of an action * @return bool @@ -162,6 +167,7 @@ abstract class Action { /** * Get the IContextSource in use here + * @since 1.17 * @return IContextSource */ final public function getContext() { @@ -179,6 +185,7 @@ abstract class Action { /** * Get the WebRequest being used for this instance + * @since 1.17 * * @return WebRequest */ @@ -188,6 +195,7 @@ abstract class Action { /** * Get the OutputPage being used for this instance + * @since 1.17 * * @return OutputPage */ @@ -197,6 +205,7 @@ abstract class Action { /** * Shortcut to get the User being used for this instance + * @since 1.17 * * @return User */ @@ -206,6 +215,7 @@ abstract class Action { /** * Shortcut to get the Skin being used for this instance + * @since 1.17 * * @return Skin */ @@ -224,6 +234,8 @@ abstract class Action { /** * Shortcut to get the Title object from the page + * @since 1.17 + * * @return Title */ final public function getTitle() { @@ -262,6 +274,8 @@ abstract class Action { /** * Return the name of the action this object responds to + * @since 1.17 + * * @return string Lowercase name */ abstract public function getName(); @@ -269,6 +283,8 @@ abstract class Action { /** * Get the permission required to perform this action. Often, but not always, * the same as the action name + * @since 1.17 + * * @return string|null */ public function getRestriction() { @@ -279,6 +295,7 @@ abstract class Action { * Checks if the given user (identified by an object) can perform this action. Can be * overridden by sub-classes with more complicated permissions schemes. Failures here * must throw subclasses of ErrorPageError + * @since 1.17 * * @param User $user The user to check, or null to use the context user * @throws UserBlockedError|ReadOnlyError|PermissionsError @@ -307,6 +324,8 @@ abstract class Action { /** * Whether this action requires the wiki not to be locked + * @since 1.17 + * * @return bool */ public function requiresWrite() { @@ -315,6 +334,8 @@ abstract class Action { /** * Whether this action can still be executed by a blocked user + * @since 1.17 + * * @return bool */ public function requiresUnblock() { @@ -324,6 +345,7 @@ abstract class Action { /** * Set output headers for noindexing etc. This function will not be called through * the execute() entry point, so only put UI-related stuff in here. + * @since 1.17 */ protected function setHeaders() { $out = $this->getOutput(); @@ -344,6 +366,7 @@ abstract class Action { /** * Returns the description that goes below the \ tag + * @since 1.17 * * @return string */ @@ -355,6 +378,8 @@ abstract class Action { * The main action entry point. Do all output for display and send it to the context * output. Do not use globals $wgOut, $wgRequest, etc, in implementations; use * $this->getOutput(), etc. + * @since 1.17 + * * @throws ErrorPageError */ abstract public function show(); diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index fc87a4767f..6914b02974 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -109,9 +109,11 @@ abstract class ApiBase extends ContextSource { } } - /***************************************************************************** - * ABSTRACT METHODS * - *****************************************************************************/ + + /************************************************************************//** + * @name Methods to implement + * @{ + */ /** * Evaluates the parameters, performs the requested query, and sets up @@ -132,440 +134,232 @@ abstract class ApiBase extends ContextSource { abstract public function execute(); /** - * Returns a string that identifies the version of the extending class. - * Typically includes the class name, the svn revision, timestamp, and - * last author. Usually done with SVN's Id keyword - * @return string - * @deprecated since 1.21, version string is no longer supported + * Get the module manager, or null if this module has no sub-modules + * @since 1.21 + * @return ApiModuleManager */ - public function getVersion() { - wfDeprecated( __METHOD__, '1.21' ); + public function getModuleManager() { + return null; + } - return ''; + /** + * If the module may only be used with a certain format module, + * it should override this method to return an instance of that formatter. + * A value of null means the default format will be used. + * @return mixed Instance of a derived class of ApiFormatBase, or null + */ + public function getCustomPrinter() { + return null; } /** - * Get the name of the module being executed by this instance - * @return string + * Returns the description string for this module + * @return string|array */ - public function getModuleName() { - return $this->mModuleName; + protected function getDescription() { + return false; } /** - * Get the module manager, or null if this module has no sub-modules - * @since 1.21 - * @return ApiModuleManager + * Returns usage examples for this module. Return false if no examples are available. + * @return bool|string|array */ - public function getModuleManager() { - return null; + protected function getExamples() { + return false; } /** - * Get parameter prefix (usually two letters or an empty string). - * @return string + * @return bool|string|array Returns a false if the module has no help URL, + * else returns a (array of) string */ - public function getModulePrefix() { - return $this->mModulePrefix; + public function getHelpUrls() { + return false; } /** - * Get the name of the module as shown in the profiler log + * Returns an array of allowed parameters (parameter name) => (default + * value) or (parameter name) => (array with PARAM_* constants as keys) + * Don't call this function directly: use getFinalParams() to allow + * hooks to modify parameters as needed. * - * @param DatabaseBase|bool $db + * Some derived classes may choose to handle an integer $flags parameter + * in the overriding methods. Callers of this method can pass zero or + * more OR-ed flags like GET_VALUES_FOR_HELP. * - * @return string + * @return array|bool */ - public function getModuleProfileName( $db = false ) { - if ( $db ) { - return 'API:' . $this->mModuleName . '-DB'; - } - - return 'API:' . $this->mModuleName; + protected function getAllowedParams( /* $flags = 0 */ ) { + // int $flags is not declared because it causes "Strict standards" + // warning. Most derived classes do not implement it. + return false; } /** - * Get the main module - * @return ApiMain + * Returns an array of parameter descriptions. + * Don't call this function directly: use getFinalParamDescription() to + * allow hooks to modify descriptions as needed. + * @return array|bool False on no parameter descriptions */ - public function getMain() { - return $this->mMainModule; + protected function getParamDescription() { + return false; } /** - * Returns true if this module is the main module ($this === $this->mMainModule), - * false otherwise. + * Indicates if this module needs maxlag to be checked * @return bool */ - public function isMain() { - return $this === $this->mMainModule; + public function shouldCheckMaxlag() { + return true; } /** - * Get the result object - * @return ApiResult + * Indicates whether this module requires read rights + * @return bool */ - public function getResult() { - // Main module has getResult() method overridden - // Safety - avoid infinite loop: - if ( $this->isMain() ) { - ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' ); - } - - return $this->getMain()->getResult(); + public function isReadMode() { + return true; } /** - * Get the result data array (read-only) - * @return array + * Indicates whether this module requires write mode + * @return bool */ - public function getResultData() { - return $this->getResult()->getData(); + public function isWriteMode() { + return false; } /** - * Set warning section for this module. Users should monitor this - * section to notice any changes in API. Multiple calls to this - * function will result in the warning messages being separated by - * newlines - * @param string $warning Warning message + * Indicates whether this module must be called with a POST request + * @return bool */ - public function setWarning( $warning ) { - $result = $this->getResult(); - $data = $result->getData(); - $moduleName = $this->getModuleName(); - if ( isset( $data['warnings'][$moduleName] ) ) { - // Don't add duplicate warnings - $oldWarning = $data['warnings'][$moduleName]['*']; - $warnPos = strpos( $oldWarning, $warning ); - // If $warning was found in $oldWarning, check if it starts at 0 or after "\n" - if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) { - // Check if $warning is followed by "\n" or the end of the $oldWarning - $warnPos += strlen( $warning ); - if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) { - return; - } - } - // If there is a warning already, append it to the existing one - $warning = "$oldWarning\n$warning"; - } - $msg = array(); - ApiResult::setContent( $msg, $warning ); - $result->addValue( 'warnings', $moduleName, - $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK ); + public function mustBePosted() { + return $this->needsToken() !== false; } /** - * If the module may only be used with a certain format module, - * it should override this method to return an instance of that formatter. - * A value of null means the default format will be used. - * @return mixed Instance of a derived class of ApiFormatBase, or null + * Returns the token type this module requires in order to execute. + * + * Modules are strongly encouraged to use the core 'csrf' type unless they + * have specialized security needs. If the token type is not one of the + * core types, you must use the ApiQueryTokensRegisterTypes hook to + * register it. + * + * Returning a non-falsey value here will cause self::getFinalParams() to + * return a required string 'token' parameter and + * self::getFinalParamDescription() to ensure there is standardized + * documentation for it. Also, self::mustBePosted() must return true when + * tokens are used. + * + * In previous versions of MediaWiki, true was a valid return value. + * Returning true will generate errors indicating that the API module needs + * updating. + * + * @return string|false */ - public function getCustomPrinter() { - return null; + public function needsToken() { + return false; } /** - * Generates help message for this module, or false if there is no description - * @return string|bool + * Fetch the salt used in the Web UI corresponding to this module. + * + * Only override this if the Web UI uses a token with a non-constant salt. + * + * @since 1.24 + * @param array $params All supplied parameters for the module + * @return string|array|null */ - public function makeHelpMsg() { - static $lnPrfx = "\n "; - - $msg = $this->getFinalDescription(); - - if ( $msg !== false ) { - - if ( !is_array( $msg ) ) { - $msg = array( - $msg - ); - } - $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n"; - - $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() ); - - if ( $this->isReadMode() ) { - $msg .= "\nThis module requires read rights"; - } - if ( $this->isWriteMode() ) { - $msg .= "\nThis module requires write rights"; - } - if ( $this->mustBePosted() ) { - $msg .= "\nThis module only accepts POST requests"; - } - if ( $this->isReadMode() || $this->isWriteMode() || - $this->mustBePosted() - ) { - $msg .= "\n"; - } - - // Parameters - $paramsMsg = $this->makeHelpMsgParameters(); - if ( $paramsMsg !== false ) { - $msg .= "Parameters:\n$paramsMsg"; - } + protected function getWebUITokenSalt( array $params ) { + return null; + } - $examples = $this->getExamples(); - if ( $examples ) { - if ( !is_array( $examples ) ) { - $examples = array( - $examples - ); - } - $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n"; - foreach ( $examples as $k => $v ) { - if ( is_numeric( $k ) ) { - $msg .= " $v\n"; - } else { - if ( is_array( $v ) ) { - $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) ); - } else { - $msgExample = " $v"; - } - $msgExample .= ":"; - $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n"; - } - } - } - } + /**@}*/ - return $msg; - } + /************************************************************************//** + * @name Data access methods + * @{ + */ /** - * @param string $item + * Get the name of the module being executed by this instance * @return string */ - private function indentExampleText( $item ) { - return " " . $item; + public function getModuleName() { + return $this->mModuleName; } /** - * @param string $prefix Text to split output items - * @param string $title What is being output - * @param string|array $input + * Get parameter prefix (usually two letters or an empty string). * @return string */ - protected function makeHelpArrayToString( $prefix, $title, $input ) { - if ( $input === false ) { - return ''; - } - if ( !is_array( $input ) ) { - $input = array( $input ); - } - - if ( count( $input ) > 0 ) { - if ( $title ) { - $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n "; - } else { - $msg = ' '; - } - $msg .= implode( $prefix, $input ) . "\n"; - - return $msg; - } - - return ''; + public function getModulePrefix() { + return $this->mModulePrefix; } /** - * Generates the parameter descriptions for this module, to be displayed in the - * module's help. - * @return string|bool + * Get the main module + * @return ApiMain */ - public function makeHelpMsgParameters() { - $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP ); - if ( $params ) { - - $paramsDescription = $this->getFinalParamDescription(); - $msg = ''; - $paramPrefix = "\n" . str_repeat( ' ', 24 ); - $descWordwrap = "\n" . str_repeat( ' ', 28 ); - foreach ( $params as $paramName => $paramSettings ) { - $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : ''; - if ( is_array( $desc ) ) { - $desc = implode( $paramPrefix, $desc ); - } - - //handle shorthand - if ( !is_array( $paramSettings ) ) { - $paramSettings = array( - self::PARAM_DFLT => $paramSettings, - ); - } - - //handle missing type - if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) { - $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] ) - ? $paramSettings[ApiBase::PARAM_DFLT] - : null; - if ( is_bool( $dflt ) ) { - $paramSettings[ApiBase::PARAM_TYPE] = 'boolean'; - } elseif ( is_string( $dflt ) || is_null( $dflt ) ) { - $paramSettings[ApiBase::PARAM_TYPE] = 'string'; - } elseif ( is_int( $dflt ) ) { - $paramSettings[ApiBase::PARAM_TYPE] = 'integer'; - } - } - - if ( isset( $paramSettings[self::PARAM_DEPRECATED] ) - && $paramSettings[self::PARAM_DEPRECATED] - ) { - $desc = "DEPRECATED! $desc"; - } - - if ( isset( $paramSettings[self::PARAM_REQUIRED] ) - && $paramSettings[self::PARAM_REQUIRED] - ) { - $desc .= $paramPrefix . "This parameter is required"; - } - - $type = isset( $paramSettings[self::PARAM_TYPE] ) - ? $paramSettings[self::PARAM_TYPE] - : null; - if ( isset( $type ) ) { - $hintPipeSeparated = true; - $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) - ? $paramSettings[self::PARAM_ISMULTI] - : false; - if ( $multi ) { - $prompt = 'Values (separate with \'|\'): '; - } else { - $prompt = 'One value: '; - } - - if ( $type === 'submodule' ) { - $type = $this->getModuleManager()->getNames( $paramName ); - sort( $type ); - } - if ( is_array( $type ) ) { - $choices = array(); - $nothingPrompt = ''; - foreach ( $type as $t ) { - if ( $t === '' ) { - $nothingPrompt = 'Can be empty, or '; - } else { - $choices[] = $t; - } - } - $desc .= $paramPrefix . $nothingPrompt . $prompt; - $choicesstring = implode( ', ', $choices ); - $desc .= wordwrap( $choicesstring, 100, $descWordwrap ); - $hintPipeSeparated = false; - } else { - switch ( $type ) { - case 'namespace': - // Special handling because namespaces are - // type-limited, yet they are not given - $desc .= $paramPrefix . $prompt; - $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ), - 100, $descWordwrap ); - $hintPipeSeparated = false; - break; - case 'limit': - $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}"; - if ( isset( $paramSettings[self::PARAM_MAX2] ) ) { - $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)"; - } - $desc .= ' allowed'; - break; - case 'integer': - $s = $multi ? 's' : ''; - $hasMin = isset( $paramSettings[self::PARAM_MIN] ); - $hasMax = isset( $paramSettings[self::PARAM_MAX] ); - if ( $hasMin || $hasMax ) { - if ( !$hasMax ) { - $intRangeStr = "The value$s must be no less than " . - "{$paramSettings[self::PARAM_MIN]}"; - } elseif ( !$hasMin ) { - $intRangeStr = "The value$s must be no more than " . - "{$paramSettings[self::PARAM_MAX]}"; - } else { - $intRangeStr = "The value$s must be between " . - "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}"; - } - - $desc .= $paramPrefix . $intRangeStr; - } - break; - case 'upload': - $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data"; - break; - } - } - - if ( $multi ) { - if ( $hintPipeSeparated ) { - $desc .= $paramPrefix . "Separate values with '|'"; - } - - $isArray = is_array( $type ); - if ( !$isArray - || $isArray && count( $type ) > self::LIMIT_SML1 - ) { - $desc .= $paramPrefix . "Maximum number of values " . - self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)"; - } - } - } - - $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null; - if ( !is_null( $default ) && $default !== false ) { - $desc .= $paramPrefix . "Default: $default"; - } - - $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc ); - } - - return $msg; - } - - return false; + public function getMain() { + return $this->mMainModule; } /** - * Returns the description string for this module - * @return string|array + * Returns true if this module is the main module ($this === $this->mMainModule), + * false otherwise. + * @return bool */ - protected function getDescription() { - return false; + public function isMain() { + return $this === $this->mMainModule; } /** - * Returns usage examples for this module. Return false if no examples are available. - * @return bool|string|array + * Get the result object + * @return ApiResult */ - protected function getExamples() { - return false; + public function getResult() { + // Main module has getResult() method overridden + // Safety - avoid infinite loop: + if ( $this->isMain() ) { + ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' ); + } + + return $this->getMain()->getResult(); } /** - * Returns an array of allowed parameters (parameter name) => (default - * value) or (parameter name) => (array with PARAM_* constants as keys) - * Don't call this function directly: use getFinalParams() to allow - * hooks to modify parameters as needed. - * - * Some derived classes may choose to handle an integer $flags parameter - * in the overriding methods. Callers of this method can pass zero or - * more OR-ed flags like GET_VALUES_FOR_HELP. - * - * @return array|bool + * Get the result data array (read-only) + * @return array */ - protected function getAllowedParams( /* $flags = 0 */ ) { - // int $flags is not declared because it causes "Strict standards" - // warning. Most derived classes do not implement it. - return false; + public function getResultData() { + return $this->getResult()->getData(); + } + + /** + * Gets a default slave database connection object + * @return DatabaseBase + */ + protected function getDB() { + if ( !isset( $this->mSlaveDB ) ) { + $this->profileDBIn(); + $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' ); + $this->profileDBOut(); + } + + return $this->mSlaveDB; } /** - * Returns an array of parameter descriptions. - * Don't call this function directly: use getFinalParamDescription() to - * allow hooks to modify descriptions as needed. - * @return array|bool False on no parameter descriptions + * Get final module description, after hooks have had a chance to tweak it as + * needed. + * + * @return array|bool False on no parameters */ - protected function getParamDescription() { - return false; + public function getFinalDescription() { + $desc = $this->getDescription(); + wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) ); + + return $desc; } /** @@ -578,6 +372,14 @@ abstract class ApiBase extends ContextSource { */ public function getFinalParams( $flags = 0 ) { $params = $this->getAllowedParams( $flags ); + + if ( $this->needsToken() ) { + $params['token'] = array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ); + } + wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) ); return $params; @@ -591,56 +393,32 @@ abstract class ApiBase extends ContextSource { */ public function getFinalParamDescription() { $desc = $this->getParamDescription(); - wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) ); - return $desc; - } + $tokenType = $this->needsToken(); + if ( $tokenType ) { + if ( !isset( $desc['token'] ) ) { + $desc['token'] = array(); + } elseif ( !is_array( $desc['token'] ) ) { + // We ignore a plain-string token, because it's probably an + // extension that is supplying the string for BC. + $desc['token'] = array(); + } + array_unshift( $desc['token'], + "A '$tokenType' token retrieved from action=query&meta=tokens" + ); + } - /** - * Formerly used to fetch a list of possible properites in the result, - * somehow organized with respect to the prop parameter that causes them to - * be returned. The specific semantics of the return value was never - * specified. Since this was never possible to be accurately updated, it - * has been removed. - * - * @deprecated since 1.24 - * @return array|bool - */ - protected function getResultProperties() { - wfDeprecated( __METHOD__, '1.24' ); - return false; - } + wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) ); - /** - * @see self::getResultProperties() - * @deprecated since 1.24 - * @return array|bool - */ - public function getFinalResultProperties() { - wfDeprecated( __METHOD__, '1.24' ); - return array(); + return $desc; } - /** - * @see self::getResultProperties() - * @deprecated since 1.24 - */ - protected static function addTokenProperties( &$props, $tokenFunctions ) { - wfDeprecated( __METHOD__, '1.24' ); - } + /**@}*/ - /** - * Get final module description, after hooks have had a chance to tweak it as - * needed. - * - * @return array|bool False on no parameters + /************************************************************************//** + * @name Parameter handling + * @{ */ - public function getFinalDescription() { - $desc = $this->getDescription(); - wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) ); - - return $desc; - } /** * This method mangles parameter name based on the prefix supplied to the constructor. @@ -718,16 +496,6 @@ abstract class ApiBase extends ContextSource { } } - /** - * @see self::getPossibleErrors() - * @deprecated since 1.24 - * @return array - */ - public function getRequireOnlyOneParameterErrorMessages( $params ) { - wfDeprecated( __METHOD__, '1.24' ); - return array(); - } - /** * Die if more than one of a certain set of parameters is set and not false. * @@ -750,16 +518,6 @@ abstract class ApiBase extends ContextSource { } } - /** - * @see self::getPossibleErrors() - * @deprecated since 1.24 - * @return array - */ - public function getRequireMaxOneParameterErrorMessages( $params ) { - wfDeprecated( __METHOD__, '1.24' ); - return array(); - } - /** * Die if none of a certain set of parameters is set and not false. * @@ -784,13 +542,13 @@ abstract class ApiBase extends ContextSource { } /** - * @see self::getPossibleErrors() - * @deprecated since 1.24 - * @return array + * Callback function used in requireOnlyOneParameter to check whether required parameters are set + * + * @param object $x Parameter to check is not null/false + * @return bool */ - public function getRequireAtLeastOneParameterErrorMessages( $params ) { - wfDeprecated( __METHOD__, '1.24' ); - return array(); + private function parameterNotEmpty( $x ) { + return !is_null( $x ) && $x !== false; } /** @@ -833,26 +591,6 @@ abstract class ApiBase extends ContextSource { return $pageObj; } - /** - * @see self::getPossibleErrors() - * @deprecated since 1.24 - * @return array - */ - public function getTitleOrPageIdErrorMessage() { - wfDeprecated( __METHOD__, '1.24' ); - return array(); - } - - /** - * Callback function used in requireOnlyOneParameter to check whether required parameters are set - * - * @param object $x Parameter to check is not null/false - * @return bool - */ - private function parameterNotEmpty( $x ) { - return !is_null( $x ) && $x !== false; - } - /** * Return true if we're to watch the page, false if not, null if no change. * @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange' @@ -894,21 +632,6 @@ abstract class ApiBase extends ContextSource { } } - /** - * Set a watch (or unwatch) based the based on a watchlist parameter. - * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange' - * @param Title $titleObj The article's title to change - * @param string $userOption The user option to consider when $watch=preferences - */ - protected function setWatch( $watch, $titleObj, $userOption = null ) { - $value = $this->getWatchlistValue( $watch, $titleObj, $userOption ); - if ( $value === null ) { - return; - } - - WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() ); - } - /** * Using the settings determine the value for the given parameter * @@ -1204,7 +927,7 @@ abstract class ApiBase extends ContextSource { * @param int $botMax Maximum value for sysops/bots * @param bool $enforceLimits Whether to enforce (die) if value is outside limits */ - function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) { + protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) { if ( !is_null( $min ) && $value < $min ) { $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)"; @@ -1242,7 +965,7 @@ abstract class ApiBase extends ContextSource { * @param string $encParamName Parameter name * @return string Validated and normalized parameter */ - function validateTimestamp( $value, $encParamName ) { + protected function validateTimestamp( $value, $encParamName ) { $unixTimestamp = wfTimestamp( TS_UNIX, $value ); if ( $unixTimestamp === false ) { $this->dieUsage( @@ -1254,6 +977,44 @@ abstract class ApiBase extends ContextSource { return wfTimestamp( TS_MW, $unixTimestamp ); } + /** + * Validate the supplied token. + * + * @since 1.24 + * @param string $token Supplied token + * @param array $params All supplied parameters for the module + * @return bool + */ + public final function validateToken( $token, array $params ) { + $tokenType = $this->needsToken(); + $salts = ApiQueryTokens::getTokenTypeSalts(); + if ( !isset( $salts[$tokenType] ) ) { + throw new MWException( + "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " . + 'without registering it' + ); + } + + if ( $this->getUser()->matchEditToken( + $token, + $salts[$tokenType], + $this->getRequest() + ) ) { + return true; + } + + $webUiSalt = $this->getWebUITokenSalt( $params ); + if ( $webUiSalt !== null && $this->getUser()->matchEditToken( + $token, + $webUiSalt, + $this->getRequest() + ) ) { + return true; + } + + return false; + } + /** * Validate and normalize of parameters of type 'user' * @param string $value Parameter value @@ -1268,8 +1029,117 @@ abstract class ApiBase extends ContextSource { "baduser_{$encParamName}" ); } - - return $title->getText(); + + return $title->getText(); + } + + /**@}*/ + + /************************************************************************//** + * @name Utility methods + * @{ + */ + + /** + * Set a watch (or unwatch) based the based on a watchlist parameter. + * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange' + * @param Title $titleObj The article's title to change + * @param string $userOption The user option to consider when $watch=preferences + */ + protected function setWatch( $watch, $titleObj, $userOption = null ) { + $value = $this->getWatchlistValue( $watch, $titleObj, $userOption ); + if ( $value === null ) { + return; + } + + WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() ); + } + + /** + * Truncate an array to a certain length. + * @param array $arr Array to truncate + * @param int $limit Maximum length + * @return bool True if the array was truncated, false otherwise + */ + public static function truncateArray( &$arr, $limit ) { + $modified = false; + while ( count( $arr ) > $limit ) { + array_pop( $arr ); + $modified = true; + } + + return $modified; + } + + /** + * Gets the user for whom to get the watchlist + * + * @param array $params + * @return User + */ + public function getWatchlistUser( $params ) { + if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) { + $user = User::newFromName( $params['owner'], false ); + if ( !( $user && $user->getId() ) ) { + $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' ); + } + $token = $user->getOption( 'watchlisttoken' ); + if ( $token == '' || $token != $params['token'] ) { + $this->dieUsage( + 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', + 'bad_wltoken' + ); + } + } else { + if ( !$this->getUser()->isLoggedIn() ) { + $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' ); + } + if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) { + $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' ); + } + $user = $this->getUser(); + } + + return $user; + } + + /**@}*/ + + /************************************************************************//** + * @name Warning and error reporting + * @{ + */ + + /** + * Set warning section for this module. Users should monitor this + * section to notice any changes in API. Multiple calls to this + * function will result in the warning messages being separated by + * newlines + * @param string $warning Warning message + */ + public function setWarning( $warning ) { + $result = $this->getResult(); + $data = $result->getData(); + $moduleName = $this->getModuleName(); + if ( isset( $data['warnings'][$moduleName] ) ) { + // Don't add duplicate warnings + $oldWarning = $data['warnings'][$moduleName]['*']; + $warnPos = strpos( $oldWarning, $warning ); + // If $warning was found in $oldWarning, check if it starts at 0 or after "\n" + if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) { + // Check if $warning is followed by "\n" or the end of the $oldWarning + $warnPos += strlen( $warning ); + if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) { + return; + } + } + // If there is a warning already, append it to the existing one + $warning = "$oldWarning\n$warning"; + } + $msg = array(); + ApiResult::setContent( $msg, $warning ); + $result->addValue( 'warnings', $moduleName, + $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK ); } /** @@ -1286,22 +1156,6 @@ abstract class ApiBase extends ContextSource { $this->setWarning( $msg ); } - /** - * Truncate an array to a certain length. - * @param array $arr Array to truncate - * @param int $limit Maximum length - * @return bool True if the array was truncated, false otherwise - */ - public static function truncateArray( &$arr, $limit ) { - $modified = false; - while ( count( $arr ) > $limit ) { - array_pop( $arr ); - $modified = true; - } - - return $modified; - } - /** * Throw a UsageException, which will (if uncaught) call the main module's * error handler and die with an error message. @@ -1963,139 +1817,298 @@ abstract class ApiBase extends ContextSource { throw new MWException( "Internal error in $method: $message" ); } + /**@}*/ + + /************************************************************************//** + * @name Help message generation + * @{ + */ + /** - * Indicates if this module needs maxlag to be checked - * @return bool + * Generates help message for this module, or false if there is no description + * @return string|bool */ - public function shouldCheckMaxlag() { - return true; + public function makeHelpMsg() { + static $lnPrfx = "\n "; + + $msg = $this->getFinalDescription(); + + if ( $msg !== false ) { + + if ( !is_array( $msg ) ) { + $msg = array( + $msg + ); + } + $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n"; + + $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() ); + + if ( $this->isReadMode() ) { + $msg .= "\nThis module requires read rights"; + } + if ( $this->isWriteMode() ) { + $msg .= "\nThis module requires write rights"; + } + if ( $this->mustBePosted() ) { + $msg .= "\nThis module only accepts POST requests"; + } + if ( $this->isReadMode() || $this->isWriteMode() || + $this->mustBePosted() + ) { + $msg .= "\n"; + } + + // Parameters + $paramsMsg = $this->makeHelpMsgParameters(); + if ( $paramsMsg !== false ) { + $msg .= "Parameters:\n$paramsMsg"; + } + + $examples = $this->getExamples(); + if ( $examples ) { + if ( !is_array( $examples ) ) { + $examples = array( + $examples + ); + } + $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n"; + foreach ( $examples as $k => $v ) { + if ( is_numeric( $k ) ) { + $msg .= " $v\n"; + } else { + if ( is_array( $v ) ) { + $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) ); + } else { + $msgExample = " $v"; + } + $msgExample .= ":"; + $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n"; + } + } + } + } + + return $msg; } /** - * Indicates whether this module requires read rights - * @return bool + * @param string $item + * @return string */ - public function isReadMode() { - return true; + private function indentExampleText( $item ) { + return " " . $item; } /** - * Indicates whether this module requires write mode - * @return bool + * @param string $prefix Text to split output items + * @param string $title What is being output + * @param string|array $input + * @return string */ - public function isWriteMode() { - return false; + protected function makeHelpArrayToString( $prefix, $title, $input ) { + if ( $input === false ) { + return ''; + } + if ( !is_array( $input ) ) { + $input = array( $input ); + } + + if ( count( $input ) > 0 ) { + if ( $title ) { + $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n "; + } else { + $msg = ' '; + } + $msg .= implode( $prefix, $input ) . "\n"; + + return $msg; + } + + return ''; } /** - * Indicates whether this module must be called with a POST request - * @return bool + * Generates the parameter descriptions for this module, to be displayed in the + * module's help. + * @return string|bool */ - public function mustBePosted() { - return false; - } + public function makeHelpMsgParameters() { + $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP ); + if ( $params ) { + + $paramsDescription = $this->getFinalParamDescription(); + $msg = ''; + $paramPrefix = "\n" . str_repeat( ' ', 24 ); + $descWordwrap = "\n" . str_repeat( ' ', 28 ); + foreach ( $params as $paramName => $paramSettings ) { + $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : ''; + if ( is_array( $desc ) ) { + $desc = implode( $paramPrefix, $desc ); + } + + //handle shorthand + if ( !is_array( $paramSettings ) ) { + $paramSettings = array( + self::PARAM_DFLT => $paramSettings, + ); + } + + //handle missing type + if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) { + $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] ) + ? $paramSettings[ApiBase::PARAM_DFLT] + : null; + if ( is_bool( $dflt ) ) { + $paramSettings[ApiBase::PARAM_TYPE] = 'boolean'; + } elseif ( is_string( $dflt ) || is_null( $dflt ) ) { + $paramSettings[ApiBase::PARAM_TYPE] = 'string'; + } elseif ( is_int( $dflt ) ) { + $paramSettings[ApiBase::PARAM_TYPE] = 'integer'; + } + } + + if ( isset( $paramSettings[self::PARAM_DEPRECATED] ) + && $paramSettings[self::PARAM_DEPRECATED] + ) { + $desc = "DEPRECATED! $desc"; + } + + if ( isset( $paramSettings[self::PARAM_REQUIRED] ) + && $paramSettings[self::PARAM_REQUIRED] + ) { + $desc .= $paramPrefix . "This parameter is required"; + } + + $type = isset( $paramSettings[self::PARAM_TYPE] ) + ? $paramSettings[self::PARAM_TYPE] + : null; + if ( isset( $type ) ) { + $hintPipeSeparated = true; + $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) + ? $paramSettings[self::PARAM_ISMULTI] + : false; + if ( $multi ) { + $prompt = 'Values (separate with \'|\'): '; + } else { + $prompt = 'One value: '; + } + + if ( $type === 'submodule' ) { + $type = $this->getModuleManager()->getNames( $paramName ); + sort( $type ); + } + if ( is_array( $type ) ) { + $choices = array(); + $nothingPrompt = ''; + foreach ( $type as $t ) { + if ( $t === '' ) { + $nothingPrompt = 'Can be empty, or '; + } else { + $choices[] = $t; + } + } + $desc .= $paramPrefix . $nothingPrompt . $prompt; + $choicesstring = implode( ', ', $choices ); + $desc .= wordwrap( $choicesstring, 100, $descWordwrap ); + $hintPipeSeparated = false; + } else { + switch ( $type ) { + case 'namespace': + // Special handling because namespaces are + // type-limited, yet they are not given + $desc .= $paramPrefix . $prompt; + $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ), + 100, $descWordwrap ); + $hintPipeSeparated = false; + break; + case 'limit': + $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}"; + if ( isset( $paramSettings[self::PARAM_MAX2] ) ) { + $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)"; + } + $desc .= ' allowed'; + break; + case 'integer': + $s = $multi ? 's' : ''; + $hasMin = isset( $paramSettings[self::PARAM_MIN] ); + $hasMax = isset( $paramSettings[self::PARAM_MAX] ); + if ( $hasMin || $hasMax ) { + if ( !$hasMax ) { + $intRangeStr = "The value$s must be no less than " . + "{$paramSettings[self::PARAM_MIN]}"; + } elseif ( !$hasMin ) { + $intRangeStr = "The value$s must be no more than " . + "{$paramSettings[self::PARAM_MAX]}"; + } else { + $intRangeStr = "The value$s must be between " . + "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}"; + } + + $desc .= $paramPrefix . $intRangeStr; + } + break; + case 'upload': + $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data"; + break; + } + } - /** - * Returns whether this module requires a token to execute - * It is used to show possible errors in action=paraminfo - * see bug 25248 - * @return bool - */ - public function needsToken() { - return false; - } + if ( $multi ) { + if ( $hintPipeSeparated ) { + $desc .= $paramPrefix . "Separate values with '|'"; + } - /** - * Returns the token salt if there is one, - * '' if the module doesn't require a salt, - * else false if the module doesn't need a token - * You have also to override needsToken() - * Value is passed to User::getEditToken - * @return bool|string|array - */ - public function getTokenSalt() { - return false; - } + $isArray = is_array( $type ); + if ( !$isArray + || $isArray && count( $type ) > self::LIMIT_SML1 + ) { + $desc .= $paramPrefix . "Maximum number of values " . + self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)"; + } + } + } - /** - * Gets the user for whom to get the watchlist - * - * @param array $params - * @return User - */ - public function getWatchlistUser( $params ) { - if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) { - $user = User::newFromName( $params['owner'], false ); - if ( !( $user && $user->getId() ) ) { - $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' ); - } - $token = $user->getOption( 'watchlisttoken' ); - if ( $token == '' || $token != $params['token'] ) { - $this->dieUsage( - 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', - 'bad_wltoken' - ); - } - } else { - if ( !$this->getUser()->isLoggedIn() ) { - $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' ); - } - if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) { - $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' ); + $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null; + if ( !is_null( $default ) && $default !== false ) { + $desc .= $paramPrefix . "Default: $default"; + } + + $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc ); } - $user = $this->getUser(); - } - return $user; - } + return $msg; + } - /** - * @return bool|string|array Returns a false if the module has no help URL, - * else returns a (array of) string - */ - public function getHelpUrls() { return false; } - /** - * This formerly attempted to return a list of all possible errors returned - * by the module. However, this was impossible to maintain in many cases - * since errors could come from other areas of MediaWiki and in some cases - * from arbitrary extension hooks. Since a partial list claiming to be - * comprehensive is unlikely to be useful, it was removed. - * - * @deprecated since 1.24 - * @return array - */ - public function getPossibleErrors() { - wfDeprecated( __METHOD__, '1.24' ); - return array(); - } + /**@}*/ - /** - * @see self::getPossibleErrors() - * @deprecated since 1.24 - * @return array + /************************************************************************//** + * @name Profiling + * @{ */ - public function getFinalPossibleErrors() { - wfDeprecated( __METHOD__, '1.24' ); - return array(); - } /** - * @see self::getPossibleErrors() - * @deprecated since 1.24 - * @return array + * Profiling: total module execution time */ - public function parseErrors( $errors ) { - wfDeprecated( __METHOD__, '1.24' ); - return array(); - } + private $mTimeIn = 0, $mModuleTime = 0; /** - * Profiling: total module execution time + * Get the name of the module as shown in the profiler log + * + * @param DatabaseBase|bool $db + * + * @return string */ - private $mTimeIn = 0, $mModuleTime = 0; + public function getModuleProfileName( $db = false ) { + if ( $db ) { + return 'API:' . $this->mModuleName . '-DB'; + } + + return 'API:' . $this->mModuleName; + } /** * Start module profiling @@ -2206,35 +2219,6 @@ abstract class ApiBase extends ContextSource { return $this->mDBTime; } - /** - * Gets a default slave database connection object - * @return DatabaseBase - */ - protected function getDB() { - if ( !isset( $this->mSlaveDB ) ) { - $this->profileDBIn(); - $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' ); - $this->profileDBOut(); - } - - return $this->mSlaveDB; - } - - /** - * Debugging function that prints a value and an optional backtrace - * @param mixed $value Value to print - * @param string $name Description of the printed value - * @param bool $backtrace If true, print a backtrace - */ - public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) { - print "\n\n
Debugging value '$name':\n\n";
-		var_export( $value );
-		if ( $backtrace ) {
-			print "\n" . wfBacktrace();
-		}
-		print "\n
\n"; - } - /** * Write logging information for API features to a debug log, for usage * analysis. @@ -2249,4 +2233,139 @@ abstract class ApiBase extends ContextSource { ' "' . addslashes( $request->getHeader( 'User-agent' ) ) . '"'; wfDebugLog( 'api-feature-usage', $s, 'private' ); } + + /**@}*/ + + /************************************************************************//** + * @name Deprecated + * @{ + */ + + /** + * Formerly returned a string that identifies the version of the extending + * class. Typically included the class name, the svn revision, timestamp, + * and last author. Usually done with SVN's Id keyword + * + * @deprecated since 1.21, version string is no longer supported + * @return string + */ + public function getVersion() { + wfDeprecated( __METHOD__, '1.21' ); + return ''; + } + + /** + * Formerly used to fetch a list of possible properites in the result, + * somehow organized with respect to the prop parameter that causes them to + * be returned. The specific semantics of the return value was never + * specified. Since this was never possible to be accurately updated, it + * has been removed. + * + * @deprecated since 1.24 + * @return array|bool + */ + protected function getResultProperties() { + wfDeprecated( __METHOD__, '1.24' ); + return false; + } + + /** + * @see self::getResultProperties() + * @deprecated since 1.24 + * @return array|bool + */ + public function getFinalResultProperties() { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * @see self::getResultProperties() + * @deprecated since 1.24 + */ + protected static function addTokenProperties( &$props, $tokenFunctions ) { + wfDeprecated( __METHOD__, '1.24' ); + } + + /** + * @see self::getPossibleErrors() + * @deprecated since 1.24 + * @return array + */ + public function getRequireOnlyOneParameterErrorMessages( $params ) { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * @see self::getPossibleErrors() + * @deprecated since 1.24 + * @return array + */ + public function getRequireMaxOneParameterErrorMessages( $params ) { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * @see self::getPossibleErrors() + * @deprecated since 1.24 + * @return array + */ + public function getRequireAtLeastOneParameterErrorMessages( $params ) { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * @see self::getPossibleErrors() + * @deprecated since 1.24 + * @return array + */ + public function getTitleOrPageIdErrorMessage() { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * This formerly attempted to return a list of all possible errors returned + * by the module. However, this was impossible to maintain in many cases + * since errors could come from other areas of MediaWiki and in some cases + * from arbitrary extension hooks. Since a partial list claiming to be + * comprehensive is unlikely to be useful, it was removed. + * + * @deprecated since 1.24 + * @return array + */ + public function getPossibleErrors() { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * @see self::getPossibleErrors() + * @deprecated since 1.24 + * @return array + */ + public function getFinalPossibleErrors() { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /** + * @see self::getPossibleErrors() + * @deprecated since 1.24 + * @return array + */ + public function parseErrors( $errors ) { + wfDeprecated( __METHOD__, '1.24' ); + return array(); + } + + /**@}*/ } + +/** + * For really cool vim folding this needs to be at the end: + * vim: foldmarker=@{,@} foldmethod=marker + */ diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index 4fa50a43d6..07f62c668c 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -152,7 +152,6 @@ class ApiBlock extends ApiBase { ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true ), - 'token' => null, 'expiry' => 'never', 'reason' => '', 'anononly' => false, @@ -169,7 +168,6 @@ class ApiBlock extends ApiBase { public function getParamDescription() { return array( 'user' => 'Username, IP address or IP range you want to block', - 'token' => 'A block token previously obtained through prop=info', 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. ' . 'If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.', 'reason' => 'Reason for block', @@ -192,17 +190,13 @@ class ApiBlock extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { return array( - 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike', - 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=' + 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike&token=123ABC', + 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC' ); } diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php index aab0303275..abca824545 100644 --- a/includes/api/ApiDelete.php +++ b/includes/api/ApiDelete.php @@ -188,10 +188,6 @@ class ApiDelete extends ApiBase { 'pageid' => array( ApiBase::PARAM_TYPE => 'integer' ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'reason' => null, 'watch' => array( ApiBase::PARAM_DFLT => false, @@ -220,7 +216,6 @@ class ApiDelete extends ApiBase { return array( 'title' => "Title of the page you want to delete. Cannot be used together with {$p}pageid", 'pageid' => "Page ID of the page you want to delete. Cannot be used together with {$p}title", - 'token' => 'A delete token previously retrieved through prop=info', 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used', 'watch' => 'Add the page to your watchlist', @@ -236,11 +231,7 @@ class ApiDelete extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php index 9126ad3c6b..8a762714eb 100644 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@ -513,10 +513,6 @@ class ApiEditPage extends ApiBase { ApiBase::PARAM_TYPE => 'string', ), 'text' => null, - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'summary' => null, 'minor' => false, 'notminor' => false, @@ -575,8 +571,8 @@ class ApiEditPage extends ApiBase { 'sectiontitle' => 'The title for a new section', 'text' => 'Page content', 'token' => array( - 'Edit token. You can get one of these through prop=info.', - "The token should always be sent as the last parameter, or at " . + /* Standard description is automatically prepended */ + 'The token should always be sent as the last parameter, or at ' . "least, after the {$p}text parameter" ), 'summary' @@ -589,7 +585,8 @@ class ApiEditPage extends ApiBase { 'Used to detect edit conflicts; leave unset to ignore conflicts' ), 'starttimestamp' => array( - 'Timestamp when you obtained the edit token.', + 'Timestamp when you began the editing process, e.g. when the current page content ' . + 'was loaded for editing.', 'Used to detect edit conflicts; leave unset to ignore conflicts' ), 'recreate' => 'Override any errors about the article having been deleted in the meantime', @@ -616,11 +613,7 @@ class ApiEditPage extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php index 1a4d67d9ff..d35b848bc1 100644 --- a/includes/api/ApiEmailUser.php +++ b/includes/api/ApiEmailUser.php @@ -94,10 +94,6 @@ class ApiEmailUser extends ApiBase { ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'ccme' => false, ); } @@ -107,7 +103,6 @@ class ApiEmailUser extends ApiBase { 'target' => 'User to send email to', 'subject' => 'Subject header', 'text' => 'Mail body', - 'token' => 'A token previously acquired via prop=info', 'ccme' => 'Send a copy of this mail to me', ); } @@ -117,16 +112,12 @@ class ApiEmailUser extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { return array( - 'api.php?action=emailuser&target=WikiSysop&text=Content' + 'api.php?action=emailuser&target=WikiSysop&text=Content&token=123ABC' => 'Send an email to the User "WikiSysop" with the text "Content"', ); } diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php index 58e4ff9336..f518e172e3 100644 --- a/includes/api/ApiFileRevert.php +++ b/includes/api/ApiFileRevert.php @@ -132,17 +132,12 @@ class ApiFileRevert extends ApiBase { ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true, ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), ); } public function getParamDescription() { return array( 'filename' => 'Target filename without the File: prefix', - 'token' => 'Edit token. You can get one of these through prop=info', 'comment' => 'Upload comment', 'archivename' => 'Archive name of the revision to revert to', ); @@ -155,11 +150,7 @@ class ApiFileRevert extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 0e3b6123d9..9165ce8805 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -352,79 +352,3 @@ See the complete documentation, $this->setWarning( "format=$name has been deprecated. Please use format=json$fm instead." ); } } - -/** - * This printer is used to wrap an instance of the Feed class - * @ingroup API - */ -class ApiFormatFeedWrapper extends ApiFormatBase { - - public function __construct( ApiMain $main ) { - parent::__construct( $main, 'feed' ); - } - - /** - * Call this method to initialize output data. See execute() - * @param ApiResult $result - * @param object $feed An instance of one of the $wgFeedClasses classes - * @param array $feedItems Array of FeedItem objects - */ - public static function setResult( $result, $feed, $feedItems ) { - // Store output in the Result data. - // This way we can check during execution if any error has occurred - // Disable size checking for this because we can't continue - // cleanly; size checking would cause more problems than it'd - // solve - $result->addValue( null, '_feed', $feed, ApiResult::NO_SIZE_CHECK ); - $result->addValue( null, '_feeditems', $feedItems, ApiResult::NO_SIZE_CHECK ); - } - - /** - * Feed does its own headers - * - * @return null - */ - public function getMimeType() { - return null; - } - - /** - * Optimization - no need to sanitize data that will not be needed - * - * @return bool - */ - public function getNeedsRawData() { - return true; - } - - /** - * ChannelFeed doesn't give us a method to print errors in a friendly - * manner, so just punt errors to the default printer. - * @return bool - */ - public function canPrintErrors() { - return false; - } - - /** - * This class expects the result data to be in a custom format set by self::setResult() - * $result['_feed'] - an instance of one of the $wgFeedClasses classes - * $result['_feeditems'] - an array of FeedItem instances - */ - public function execute() { - $data = $this->getResultData(); - if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) { - $feed = $data['_feed']; - $items = $data['_feeditems']; - - $feed->outHeader(); - foreach ( $items as & $item ) { - $feed->outItem( $item ); - } - $feed->outFooter(); - } else { - // Error has occurred, print something useful - ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' ); - } - } -} diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php index 61ed18f464..5ec518b358 100644 --- a/includes/api/ApiFormatDbg.php +++ b/includes/api/ApiFormatDbg.php @@ -26,6 +26,7 @@ /** * API PHP's var_export() output formatter + * @deprecated since 1.24 * @ingroup API */ class ApiFormatDbg extends ApiFormatBase { diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php index 7d3224604a..d4c7cab4f3 100644 --- a/includes/api/ApiFormatDump.php +++ b/includes/api/ApiFormatDump.php @@ -26,6 +26,7 @@ /** * API PHP's var_dump() output formatter + * @deprecated since 1.24 * @ingroup API */ class ApiFormatDump extends ApiFormatBase { diff --git a/includes/api/ApiFormatFeedWrapper.php b/includes/api/ApiFormatFeedWrapper.php new file mode 100644 index 0000000000..92600067f1 --- /dev/null +++ b/includes/api/ApiFormatFeedWrapper.php @@ -0,0 +1,101 @@ +@gmail.com" + * + * 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 printer is used to wrap an instance of the Feed class + * @ingroup API + */ +class ApiFormatFeedWrapper extends ApiFormatBase { + + public function __construct( ApiMain $main ) { + parent::__construct( $main, 'feed' ); + } + + /** + * Call this method to initialize output data. See execute() + * @param ApiResult $result + * @param object $feed An instance of one of the $wgFeedClasses classes + * @param array $feedItems Array of FeedItem objects + */ + public static function setResult( $result, $feed, $feedItems ) { + // Store output in the Result data. + // This way we can check during execution if any error has occurred + // Disable size checking for this because we can't continue + // cleanly; size checking would cause more problems than it'd + // solve + $result->addValue( null, '_feed', $feed, ApiResult::NO_SIZE_CHECK ); + $result->addValue( null, '_feeditems', $feedItems, ApiResult::NO_SIZE_CHECK ); + } + + /** + * Feed does its own headers + * + * @return null + */ + public function getMimeType() { + return null; + } + + /** + * Optimization - no need to sanitize data that will not be needed + * + * @return bool + */ + public function getNeedsRawData() { + return true; + } + + /** + * ChannelFeed doesn't give us a method to print errors in a friendly + * manner, so just punt errors to the default printer. + * @return bool + */ + public function canPrintErrors() { + return false; + } + + /** + * This class expects the result data to be in a custom format set by self::setResult() + * $result['_feed'] - an instance of one of the $wgFeedClasses classes + * $result['_feeditems'] - an array of FeedItem instances + */ + public function execute() { + $data = $this->getResultData(); + if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) { + $feed = $data['_feed']; + $items = $data['_feeditems']; + + $feed->outHeader(); + foreach ( $items as & $item ) { + $feed->outItem( $item ); + } + $feed->outFooter(); + } else { + // Error has occurred, print something useful + ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' ); + } + } +} diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php index 3de2943d46..c451ed7756 100644 --- a/includes/api/ApiFormatTxt.php +++ b/includes/api/ApiFormatTxt.php @@ -26,6 +26,7 @@ /** * API Text output formatter + * @deprecated since 1.24 * @ingroup API */ class ApiFormatTxt extends ApiFormatBase { diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index a08c3abc12..ba90c2603a 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -26,6 +26,7 @@ /** * API WDDX output formatter + * @deprecated since 1.24 * @ingroup API */ class ApiFormatWddx extends ApiFormatBase { diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index 9f9b0577dd..3798f89409 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -26,6 +26,7 @@ /** * API YAML output formatter + * @deprecated since 1.24 * @ingroup API */ class ApiFormatYaml extends ApiFormatJson { diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php index 26def4410c..20396dd761 100644 --- a/includes/api/ApiImageRotate.php +++ b/includes/api/ApiImageRotate.php @@ -184,10 +184,6 @@ class ApiImageRotate extends ApiBase { ApiBase::PARAM_TYPE => array( '90', '180', '270' ), ApiBase::PARAM_REQUIRED => true ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'continue' => '', ); if ( $flags ) { @@ -202,7 +198,6 @@ class ApiImageRotate extends ApiBase { return $pageSet->getFinalParamDescription() + array( 'rotation' => 'Degrees to rotate image clockwise', - 'token' => 'Edit token. You can get one of these through action=tokens', 'continue' => 'When more results are available, use this to continue', ); } @@ -212,11 +207,7 @@ class ApiImageRotate extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php index 25ce89b9e5..b11348e5b1 100644 --- a/includes/api/ApiImport.php +++ b/includes/api/ApiImport.php @@ -99,10 +99,6 @@ class ApiImport extends ApiBase { public function getAllowedParams() { return array( - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'summary' => null, 'xml' => array( ApiBase::PARAM_TYPE => 'upload', @@ -122,7 +118,6 @@ class ApiImport extends ApiBase { public function getParamDescription() { return array( - 'token' => 'Import token obtained through prop=info', 'summary' => 'Import summary', 'xml' => 'Uploaded XML file', 'interwikisource' => 'For interwiki imports: wiki to import from', @@ -143,11 +138,7 @@ class ApiImport extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 3919c755ae..7f711b7cc6 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -736,6 +736,11 @@ class ApiMain extends ApiBase { } } + if ( $this->getParameter( 'curtimestamp' ) ) { + $result->addValue( null, 'curtimestamp', wfTimestamp( TS_ISO_8601, time() ), + ApiResult::NO_SIZE_CHECK ); + } + $params = $this->extractRequestParams(); $this->mAction = $params['action']; @@ -759,18 +764,35 @@ class ApiMain extends ApiBase { } $moduleParams = $module->extractRequestParams(); - // Die if token required, but not provided - $salt = $module->getTokenSalt(); - if ( $salt !== false ) { + // Check token, if necessary + if ( $module->needsToken() === true ) { + throw new MWException( + "Module '{$module->getModuleName()}' must be updated for the new token handling. " . + "See documentation for ApiBase::needsToken for details." + ); + } + if ( $module->needsToken() ) { + if ( !$module->mustBePosted() ) { + throw new MWException( + "Module '{$module->getModuleName()}' must require POST to use tokens." + ); + } + if ( !isset( $moduleParams['token'] ) ) { $this->dieUsageMsg( array( 'missingparam', 'token' ) ); } - if ( !$this->getUser()->matchEditToken( - $moduleParams['token'], - $salt, - $this->getContext()->getRequest() ) - ) { + if ( array_key_exists( + $module->encodeParamName( 'token' ), + $this->getRequest()->getQueryValues() + ) ) { + $this->dieUsage( + "The '{$module->encodeParamName( 'token' )}' parameter must be POSTed", + 'mustposttoken' + ); + } + + if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) { $this->dieUsageMsg( 'sessionfailure' ); } } @@ -1112,6 +1134,7 @@ class ApiMain extends ApiBase { ), 'requestid' => null, 'servedby' => false, + 'curtimestamp' => false, 'origin' => null, ); } @@ -1139,6 +1162,7 @@ class ApiMain extends ApiBase { 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', 'servedby' => 'Include the hostname that served the request in the ' . 'results. Unconditionally shown on error', + 'curtimestamp' => 'Include the current timestamp in the result.', 'origin' => array( 'When accessing the API using a cross-domain AJAX request (CORS), set this to the', 'originating domain. This must be included in any pre-flight request, and', diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php index 602a905311..04e931d2f3 100644 --- a/includes/api/ApiMove.php +++ b/includes/api/ApiMove.php @@ -195,10 +195,6 @@ class ApiMove extends ApiBase { ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'reason' => '', 'movetalk' => false, 'movesubpages' => false, @@ -231,7 +227,6 @@ class ApiMove extends ApiBase { 'from' => "Title of the page you want to move. Cannot be used together with {$p}fromid", 'fromid' => "Page ID of the page you want to move. Cannot be used together with {$p}from", 'to' => 'Title you want to rename the page to', - 'token' => 'A move token previously retrieved through prop=info', 'reason' => 'Reason for the move', 'movetalk' => 'Move the talk page, if it exists', 'movesubpages' => 'Move subpages, if applicable', @@ -249,11 +244,7 @@ class ApiMove extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php index 8ac9b9afb0..b01dc3e239 100644 --- a/includes/api/ApiOptions.php +++ b/includes/api/ApiOptions.php @@ -135,10 +135,6 @@ class ApiOptions extends ApiBase { $optionKinds[] = 'all'; return array( - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'reset' => false, 'resetkinds' => array( ApiBase::PARAM_TYPE => $optionKinds, @@ -159,7 +155,6 @@ class ApiOptions extends ApiBase { public function getParamDescription() { return array( - 'token' => 'An options token previously obtained through the action=tokens', 'reset' => 'Resets preferences to the site defaults', 'resetkinds' => 'List of types of options to reset when the "reset" option is set', 'change' => array( 'List of changes, formatted name=value (e.g. skin=vector), ' . @@ -183,11 +178,7 @@ class ApiOptions extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getHelpUrls() { diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index f81f3d9237..067b2f5968 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -198,6 +198,10 @@ class ApiParamInfo extends ApiBase { $a['required'] = ''; } + if ( $n === 'token' && $obj->needsToken() ) { + $a['tokentype'] = $obj->needsToken(); + } + if ( isset( $p[ApiBase::PARAM_DFLT] ) ) { $type = $p[ApiBase::PARAM_TYPE]; if ( $type === 'boolean' ) { diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php index 9690952968..8b66781a4d 100644 --- a/includes/api/ApiPatrol.php +++ b/includes/api/ApiPatrol.php @@ -77,10 +77,6 @@ class ApiPatrol extends ApiBase { public function getAllowedParams() { return array( - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'rcid' => array( ApiBase::PARAM_TYPE => 'integer' ), @@ -92,7 +88,6 @@ class ApiPatrol extends ApiBase { public function getParamDescription() { return array( - 'token' => 'Patrol token obtained from list=recentchanges', 'rcid' => 'Recentchanges ID to patrol', 'revid' => 'Revision ID to patrol', ); @@ -103,17 +98,13 @@ class ApiPatrol extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { return 'patrol'; } public function getExamples() { return array( - 'api.php?action=patrol&token=123abc&rcid=230672766', - 'api.php?action=patrol&token=123abc&revid=230672766' + 'api.php?action=patrol&token=123ABC&rcid=230672766', + 'api.php?action=patrol&token=123ABC&revid=230672766' ); } diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index 844d1ccee7..a3d12b7fc0 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -148,10 +148,6 @@ class ApiProtect extends ApiBase { 'pageid' => array( ApiBase::PARAM_TYPE => 'integer', ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'protections' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_REQUIRED => true, @@ -185,7 +181,6 @@ class ApiProtect extends ApiBase { return array( 'title' => "Title of the page you want to (un)protect. Cannot be used together with {$p}pageid", 'pageid' => "ID of the page you want to (un)protect. Cannot be used together with {$p}title", - 'token' => 'A protect token previously retrieved through prop=info', 'protections' => 'List of protection levels, formatted action=group (e.g. edit=sysop)', 'expiry' => array( 'Expiry timestamps. If only one timestamp is ' . @@ -208,11 +203,7 @@ class ApiProtect extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 9ffcf0e5d3..3d6372c5e0 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -108,6 +108,7 @@ class ApiQuery extends ApiBase { 'siteinfo' => 'ApiQuerySiteinfo', 'userinfo' => 'ApiQueryUserInfo', 'filerepoinfo' => 'ApiQueryFileRepoInfo', + 'tokens' => 'ApiQueryTokens', ); /** diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 6680316390..6b08fc5c87 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -47,6 +47,11 @@ abstract class ApiQueryBase extends ApiBase { $this->resetQueryParams(); } + /************************************************************************//** + * @name Methods to implement + * @{ + */ + /** * Get the cache mode for the data generated by this module. Override * this in the module subclass. For possible return values and other @@ -62,6 +67,68 @@ abstract class ApiQueryBase extends ApiBase { return 'private'; } + /** + * Override this method to request extra fields from the pageSet + * using $pageSet->requestField('fieldName') + * @param ApiPageSet $pageSet + */ + public function requestExtraData( $pageSet ) { + } + + /**@}*/ + + /************************************************************************//** + * @name Data access + * @{ + */ + + /** + * Get the main Query module + * @return ApiQuery + */ + public function getQuery() { + return $this->mQueryModule; + } + + /** + * Get the Query database connection (read-only) + * @return DatabaseBase + */ + protected function getDB() { + if ( is_null( $this->mDb ) ) { + $this->mDb = $this->getQuery()->getDB(); + } + + return $this->mDb; + } + + /** + * Selects the query database connection with the given name. + * See ApiQuery::getNamedDB() for more information + * @param string $name Name to assign to the database connection + * @param int $db One of the DB_* constants + * @param array $groups Query groups + * @return DatabaseBase + */ + public function selectNamedDB( $name, $db, $groups ) { + $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups ); + } + + /** + * Get the PageSet object to work on + * @return ApiPageSet + */ + protected function getPageSet() { + return $this->getQuery()->getPageSet(); + } + + /**@}*/ + + /************************************************************************//** + * @name Querying + * @{ + */ + /** * Blank the internal arrays with query parameters */ @@ -305,29 +372,64 @@ abstract class ApiQueryBase extends ApiBase { } /** - * Estimate the row count for the SELECT query that would be run if we - * called select() right now, and check if it's acceptable. - * @return bool True if acceptable, false otherwise + * @param string $query + * @param string $protocol + * @return null|string */ - protected function checkRowCount() { - $db = $this->getDB(); - $this->profileDBIn(); - $rowcount = $db->estimateRowCount( - $this->tables, - $this->fields, - $this->where, - __METHOD__, - $this->options - ); - $this->profileDBOut(); + public function prepareUrlQuerySearchString( $query = null, $protocol = null ) { + $db = $this->getDb(); + if ( !is_null( $query ) || $query != '' ) { + if ( is_null( $protocol ) ) { + $protocol = 'http://'; + } - if ( $rowcount > $this->getConfig()->get( 'APIMaxDBRows' ) ) { - return false; + $likeQuery = LinkFilter::makeLikeArray( $query, $protocol ); + if ( !$likeQuery ) { + $this->dieUsage( 'Invalid query', 'bad_query' ); + } + + $likeQuery = LinkFilter::keepOneWildcard( $likeQuery ); + + return 'el_index ' . $db->buildLike( $likeQuery ); + } elseif ( !is_null( $protocol ) ) { + return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ); } - return true; + return null; } + /** + * Filters hidden users (where the user doesn't have the right to view them) + * Also adds relevant block information + * + * @param bool $showBlockInfo + * @return void + */ + public function showHiddenUsersAddBlockInfo( $showBlockInfo ) { + $this->addTables( 'ipblocks' ); + $this->addJoinConds( array( + 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ), + ) ); + + $this->addFields( 'ipb_deleted' ); + + if ( $showBlockInfo ) { + $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry' ) ); + } + + // Don't show hidden names + if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { + $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' ); + } + } + + /**@}*/ + + /************************************************************************//** + * @name Utility methods + * @{ + */ + /** * Add information (title and namespace) about a Title object to a * result array @@ -340,22 +442,6 @@ abstract class ApiQueryBase extends ApiBase { $arr[$prefix . 'title'] = $title->getPrefixedText(); } - /** - * Override this method to request extra fields from the pageSet - * using $pageSet->requestField('fieldName') - * @param ApiPageSet $pageSet - */ - public function requestExtraData( $pageSet ) { - } - - /** - * Get the main Query module - * @return ApiQuery - */ - public function getQuery() { - return $this->mQueryModule; - } - /** * Add a sub-element under the page element with the given page ID * @param int $pageId Page ID @@ -405,89 +491,21 @@ abstract class ApiQueryBase extends ApiBase { } /** - * Get the Query database connection (read-only) - * @return DatabaseBase - */ - protected function getDB() { - if ( is_null( $this->mDb ) ) { - $this->mDb = $this->getQuery()->getDB(); - } - - return $this->mDb; - } - - /** - * Selects the query database connection with the given name. - * See ApiQuery::getNamedDB() for more information - * @param string $name Name to assign to the database connection - * @param int $db One of the DB_* constants - * @param array $groups Query groups - * @return DatabaseBase - */ - public function selectNamedDB( $name, $db, $groups ) { - $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups ); - } - - /** - * Get the PageSet object to work on - * @return ApiPageSet - */ - protected function getPageSet() { - return $this->getQuery()->getPageSet(); - } - - /** - * Convert a title to a DB key - * @param string $title Page title with spaces - * @return string Page title with underscores - */ - public function titleToKey( $title ) { - // Don't throw an error if we got an empty string - if ( trim( $title ) == '' ) { - return ''; - } - $t = Title::newFromText( $title ); - if ( !$t ) { - $this->dieUsageMsg( array( 'invalidtitle', $title ) ); - } - - return $t->getPrefixedDBkey(); - } - - /** - * The inverse of titleToKey() - * @param string $key Page title with underscores - * @return string Page title with spaces - */ - public function keyToTitle( $key ) { - // Don't throw an error if we got an empty string - if ( trim( $key ) == '' ) { - return ''; - } - $t = Title::newFromDBkey( $key ); - // This really shouldn't happen but we gotta check anyway - if ( !$t ) { - $this->dieUsageMsg( array( 'invalidtitle', $key ) ); - } - - return $t->getPrefixedText(); - } - - /** - * An alternative to titleToKey() that doesn't trim trailing spaces, and - * does not mangle the input if starts with something that looks like a - * namespace. It is advisable to pass the namespace parameter in order to - * handle per-namespace capitalization settings. - * @param string $titlePart Title part with spaces - * @param int $defaultNamespace Namespace to assume - * @return string Title part with underscores + * Convert an input title or title prefix into a dbkey. + * + * $namespace should always be specified in order to handle per-namespace + * capitalization settings. + * + * @param string $titlePart Title part + * @param int $defaultNamespace Namespace of the title + * @return string DBkey (no namespace prefix) */ - public function titlePartToKey( $titlePart, $defaultNamespace = NS_MAIN ) { - $t = Title::makeTitleSafe( $defaultNamespace, $titlePart . 'x' ); + public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) { + $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' ); if ( !$t ) { $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) ); } - if ( $defaultNamespace != $t->getNamespace() || $t->isExternal() ) { + if ( $namespace != $t->getNamespace() || $t->isExternal() ) { // This can happen in two cases. First, if you call titlePartToKey with a title part // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very // difficult to handle such a case. Such cases cannot exist and are therefore treated @@ -499,15 +517,6 @@ abstract class ApiQueryBase extends ApiBase { return substr( $t->getDbKey(), 0, -1 ); } - /** - * An alternative to keyToTitle() that doesn't trim trailing spaces - * @param string $keyPart Key part with spaces - * @return string Key part with underscores - */ - public function keyPartToTitle( $keyPart ) { - return substr( $this->keyToTitle( $keyPart . 'x' ), 0, -1 ); - } - /** * Gets the personalised direction parameter description * @@ -523,58 +532,6 @@ abstract class ApiQueryBase extends ApiBase { ); } - /** - * @param string $query - * @param string $protocol - * @return null|string - */ - public function prepareUrlQuerySearchString( $query = null, $protocol = null ) { - $db = $this->getDb(); - if ( !is_null( $query ) || $query != '' ) { - if ( is_null( $protocol ) ) { - $protocol = 'http://'; - } - - $likeQuery = LinkFilter::makeLikeArray( $query, $protocol ); - if ( !$likeQuery ) { - $this->dieUsage( 'Invalid query', 'bad_query' ); - } - - $likeQuery = LinkFilter::keepOneWildcard( $likeQuery ); - - return 'el_index ' . $db->buildLike( $likeQuery ); - } elseif ( !is_null( $protocol ) ) { - return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ); - } - - return null; - } - - /** - * Filters hidden users (where the user doesn't have the right to view them) - * Also adds relevant block information - * - * @param bool $showBlockInfo - * @return void - */ - public function showHiddenUsersAddBlockInfo( $showBlockInfo ) { - $this->addTables( 'ipblocks' ); - $this->addJoinConds( array( - 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ), - ) ); - - $this->addFields( 'ipb_deleted' ); - - if ( $showBlockInfo ) { - $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry' ) ); - } - - // Don't show hidden names - if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { - $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' ); - } - } - /** * @param string $hash * @return bool @@ -604,6 +561,94 @@ abstract class ApiQueryBase extends ApiBase { 'viewsuppressed' ); } + + /**@}*/ + + /************************************************************************//** + * @name Deprecated + * @{ + */ + + /** + * Estimate the row count for the SELECT query that would be run if we + * called select() right now, and check if it's acceptable. + * @deprecated since 1.24 + * @return bool True if acceptable, false otherwise + */ + protected function checkRowCount() { + wfDeprecated( __METHOD__, '1.24' ); + $db = $this->getDB(); + $this->profileDBIn(); + $rowcount = $db->estimateRowCount( + $this->tables, + $this->fields, + $this->where, + __METHOD__, + $this->options + ); + $this->profileDBOut(); + + if ( $rowcount > $this->getConfig()->get( 'APIMaxDBRows' ) ) { + return false; + } + + return true; + } + + /** + * Convert a title to a DB key + * @deprecated since 1.24, past uses of this were always incorrect and should + * have used self::titlePartToKey() instead + * @param string $title Page title with spaces + * @return string Page title with underscores + */ + public function titleToKey( $title ) { + wfDeprecated( __METHOD__, '1.24' ); + // Don't throw an error if we got an empty string + if ( trim( $title ) == '' ) { + return ''; + } + $t = Title::newFromText( $title ); + if ( !$t ) { + $this->dieUsageMsg( array( 'invalidtitle', $title ) ); + } + + return $t->getPrefixedDBkey(); + } + + /** + * The inverse of titleToKey() + * @deprecated since 1.24, unused and probably never needed + * @param string $key Page title with underscores + * @return string Page title with spaces + */ + public function keyToTitle( $key ) { + wfDeprecated( __METHOD__, '1.24' ); + // Don't throw an error if we got an empty string + if ( trim( $key ) == '' ) { + return ''; + } + $t = Title::newFromDBkey( $key ); + // This really shouldn't happen but we gotta check anyway + if ( !$t ) { + $this->dieUsageMsg( array( 'invalidtitle', $key ) ); + } + + return $t->getPrefixedText(); + } + + /** + * Inverse of titlePartToKey() + * @deprecated since 1.24, unused and probably never needed + * @param string $keyPart DBkey, with prefix + * @return string Key part with underscores + */ + public function keyPartToTitle( $keyPart ) { + wfDeprecated( __METHOD__, '1.24' ); + return substr( $this->keyToTitle( $keyPart . 'x' ), 0, -1 ); + } + + /**@}*/ } /** @@ -641,7 +686,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { } /** - * Overrides base class to prepend 'g' to every generator parameter + * Overrides ApiBase to prepend 'g' to every generator parameter * @param string $paramName Parameter name * @return string Prefixed parameter name */ diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 6b1185b882..9042696bee 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -61,6 +61,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $fld_token = isset( $prop['token'] ); $fld_tags = isset( $prop['tags'] ); + if ( isset( $prop['token'] ) ) { + $p = $this->getModulePrefix(); + $this->setWarning( + "{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead." + ); + } + // If we're in JSON callback mode, no tokens can be obtained if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) { $fld_token = false; @@ -493,7 +500,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase { ' len - Adds the length (bytes) of the revision', ' sha1 - Adds the SHA-1 (base 16) of the revision', ' content - Adds the content of the revision', - ' token - Gives the edit token', + ' token - DEPRECATED! Gives the edit token', ' tags - Tags for the revision', ), 'namespace' => 'Only list pages in this namespace (3)', diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index be1de9385b..3ac9c8ac1d 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -79,6 +79,7 @@ class ApiQueryInfo extends ApiQueryBase { * Get an array mapping token names to their handler functions. * The prototype for a token function is func($pageid, $title) * it should return a token or false (permission denied) + * @deprecated since 1.24 * @return array Array(tokenname => function) */ protected function getTokenFunctions() { @@ -110,10 +111,16 @@ class ApiQueryInfo extends ApiQueryBase { static protected $cachedTokens = array(); + /** + * @deprecated since 1.24 + */ public static function resetTokenCache() { ApiQueryInfo::$cachedTokens = array(); } + /** + * @deprecated since 1.24 + */ public static function getEditToken( $pageid, $title ) { // We could check for $title->userCan('edit') here, // but that's too expensive for this purpose @@ -131,6 +138,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['edit']; } + /** + * @deprecated since 1.24 + */ public static function getDeleteToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isAllowed( 'delete' ) ) { @@ -145,6 +155,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['delete']; } + /** + * @deprecated since 1.24 + */ public static function getProtectToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isAllowed( 'protect' ) ) { @@ -159,6 +172,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['protect']; } + /** + * @deprecated since 1.24 + */ public static function getMoveToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isAllowed( 'move' ) ) { @@ -173,6 +189,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['move']; } + /** + * @deprecated since 1.24 + */ public static function getBlockToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isAllowed( 'block' ) ) { @@ -187,11 +206,17 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['block']; } + /** + * @deprecated since 1.24 + */ public static function getUnblockToken( $pageid, $title ) { // Currently, this is exactly the same as the block token return self::getBlockToken( $pageid, $title ); } + /** + * @deprecated since 1.24 + */ public static function getEmailToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) { @@ -206,6 +231,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['email']; } + /** + * @deprecated since 1.24 + */ public static function getImportToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) { @@ -220,6 +248,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['import']; } + /** + * @deprecated since 1.24 + */ public static function getWatchToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isLoggedIn() ) { @@ -234,6 +265,9 @@ class ApiQueryInfo extends ApiQueryBase { return ApiQueryInfo::$cachedTokens['watch']; } + /** + * @deprecated since 1.24 + */ public static function getOptionsToken( $pageid, $title ) { global $wgUser; if ( !$wgUser->isLoggedIn() ) { @@ -784,6 +818,7 @@ class ApiQueryInfo extends ApiQueryBase { // need to be added to getCacheMode() ) ), 'token' => array( + ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_DFLT => null, ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ) diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 8ce6b55cba..6f0c5d3477 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -47,6 +47,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { * Get an array mapping token names to their handler functions. * The prototype for a token function is func($pageid, $title, $rc) * it should return a token or false (permission denied) + * @deprecated since 1.24 * @return array Array(tokenname => function) */ protected function getTokenFunctions() { @@ -69,6 +70,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { } /** + * @deprecated since 1.24 * @param int $pageid * @param Title $title * @param RecentChange|null $rc @@ -657,6 +659,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { ) ), 'token' => array( + ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_ISMULTI => true ), diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 627de55623..da4ec19541 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -48,6 +48,7 @@ class ApiQueryRevisions extends ApiQueryBase { private $tokenFunctions; + /** @deprecated since 1.24 */ protected function getTokenFunctions() { // tokenname => function // function prototype is func($pageid, $title, $rev) @@ -72,6 +73,7 @@ class ApiQueryRevisions extends ApiQueryBase { } /** + * @deprecated since 1.24 * @param int $pageid * @param Title $title * @param Revision $rev @@ -748,6 +750,7 @@ class ApiQueryRevisions extends ApiQueryBase { 'parse' => false, 'section' => null, 'token' => array( + ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_ISMULTI => true ), diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index f17ac29b81..522c7c0805 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -138,6 +138,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['phpversion'] = PHP_VERSION; $data['phpsapi'] = PHP_SAPI; + if ( defined( 'HHVM_VERSION' ) ) { + $data['hhvmversion'] = HHVM_VERSION; + } $data['dbtype'] = $config->get( 'DBtype' ); $data['dbversion'] = $this->getDB()->getServerVersion(); @@ -840,7 +843,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { ' fileextensions - Returns list of file extensions allowed to be uploaded', ' rightsinfo - Returns wiki rights (license) information if available', ' restrictions - Returns information on available restriction (protection) types', - ' languages - Returns a list of languages MediaWiki supports' . + ' languages - Returns a list of languages MediaWiki supports ' . "(optionally localised by using {$p}inlanguagecode)", ' skins - Returns a list of all enabled skins', ' extensiontags - Returns a list of parser extension tags', diff --git a/includes/api/ApiQueryTokens.php b/includes/api/ApiQueryTokens.php new file mode 100644 index 0000000000..ba9c937712 --- /dev/null +++ b/includes/api/ApiQueryTokens.php @@ -0,0 +1,104 @@ +extractRequestParams(); + $res = array(); + + if ( $this->getMain()->getRequest()->getVal( 'callback' ) !== null ) { + $this->setWarning( 'Tokens may not be obtained when using a callback' ); + return; + } + + $salts = self::getTokenTypeSalts(); + foreach ( $params['type'] as $type ) { + $salt = $salts[$type]; + $val = $this->getUser()->getEditToken( $salt, $this->getRequest() ); + $res[$type . 'token'] = $val; + } + + $this->getResult()->addValue( 'query', $this->getModuleName(), $res ); + } + + public static function getTokenTypeSalts() { + static $salts = null; + if ( !$salts ) { + wfProfileIn( __METHOD__ ); + $salts = array( + 'csrf' => '', + 'watch' => 'watch', + 'patrol' => 'patrol', + 'rollback' => 'rollback', + 'userrights' => 'userrights', + ); + wfRunHooks( 'ApiQueryTokensRegisterTypes', array( &$salts ) ); + ksort( $salts ); + wfProfileOut( __METHOD__ ); + } + + return $salts; + } + + public function getAllowedParams() { + return array( + 'type' => array( + ApiBase::PARAM_DFLT => 'csrf', + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_TYPE => array_keys( self::getTokenTypeSalts() ), + ), + ); + } + + public function getParamDescription() { + return array( + 'type' => 'Type of token(s) to request' + ); + } + + public function getDescription() { + return 'Gets tokens for data-modifying actions.'; + } + + protected function getExamples() { + return array( + 'api.php?action=query&meta=tokens' => 'Retrieve a csrf token (the default)', + 'api.php?action=query&meta=tokens&type=watch|patrol' => 'Retrieve a watch token and a patrol token' + ); + } + + public function getCacheMode( $params ) { + return 'private'; + } +} diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 6d70241049..8b7831cb8e 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -104,6 +104,12 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['options'] = $user->getOptions(); } + if ( isset( $this->prop['preferencestoken'] ) ) { + $p = $this->getModulePrefix(); + $this->setWarning( + "{$p}prop=preferencestoken has been deprecated. Please use action=query&meta=tokens instead." + ); + } if ( isset( $this->prop['preferencestoken'] ) && is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) && $user->isAllowed( 'editmyoptions' ) @@ -252,7 +258,7 @@ class ApiQueryUserInfo extends ApiQueryBase { ' rights - Lists all the rights the current user has', ' changeablegroups - Lists the groups the current user can add to and remove from', ' options - Lists all preferences the current user has set', - ' preferencestoken - Get a token to change current user\'s preferences', + ' preferencestoken - DEPRECATED! Get a token to change current user\'s preferences', ' editcount - Adds the current user\'s edit count', ' ratelimits - Lists all rate limits applying to the current user', ' realname - Adds the user\'s real name', diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index 84326a2950..b62d6a83e0 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -58,6 +58,7 @@ class ApiQueryUsers extends ApiQueryBase { * Get an array mapping token names to their handler functions. * The prototype for a token function is func($user) * it should return a token or false (permission denied) + * @deprecated since 1.24 * @return array Array of tokenname => function */ protected function getTokenFunctions() { @@ -80,6 +81,7 @@ class ApiQueryUsers extends ApiQueryBase { } /** + * @deprecated since 1.24 * @param User $user * @return string */ @@ -317,6 +319,7 @@ class ApiQueryUsers extends ApiQueryBase { ApiBase::PARAM_ISMULTI => true ), 'token' => array( + ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_ISMULTI => true ), diff --git a/includes/api/ApiRevisionDelete.php b/includes/api/ApiRevisionDelete.php index 07a1a452b1..cbc3070422 100644 --- a/includes/api/ApiRevisionDelete.php +++ b/includes/api/ApiRevisionDelete.php @@ -195,10 +195,6 @@ class ApiRevisionDelete extends ApiBase { ApiBase::PARAM_TYPE => array( 'yes', 'no', 'nochange' ), ApiBase::PARAM_DFLT => 'nochange', ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'reason' => null, ); } @@ -211,7 +207,6 @@ class ApiRevisionDelete extends ApiBase { 'hide' => 'What to hide for each revision', 'show' => 'What to unhide for each revision', 'suppress' => 'Whether to suppress data from administrators as well as others', - 'token' => 'A delete token previously retrieved through action=tokens', 'reason' => 'Reason for the deletion/undeletion', ); } @@ -221,11 +216,7 @@ class ApiRevisionDelete extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php index af6f9ffeef..f4d3c5414d 100644 --- a/includes/api/ApiRollback.php +++ b/includes/api/ApiRollback.php @@ -40,9 +40,19 @@ class ApiRollback extends ApiBase { private $mUser = null; public function execute() { + $user = $this->getUser(); $params = $this->extractRequestParams(); - // User and title already validated in call to getTokenSalt from Main + // WikiPage::doRollback needs a Web UI token, so get one of those if we + // validated based on an API rollback token. + $token = $params['token']; + if ( $user->matchEditToken( $token, 'rollback', $this->getRequest() ) ) { + $token = $this->getUser()->getEditToken( + $this->getWebUITokenSalt( $params ), + $this->getRequest() + ); + } + $titleObj = $this->getRbTitle( $params ); $pageObj = WikiPage::factory( $titleObj ); $summary = $params['summary']; @@ -50,10 +60,10 @@ class ApiRollback extends ApiBase { $retval = $pageObj->doRollback( $this->getRbUser( $params ), $summary, - $params['token'], + $token, $params['markbot'], $details, - $this->getUser() + $user ); if ( $retval ) { @@ -99,10 +109,6 @@ class ApiRollback extends ApiBase { ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'summary' => '', 'markbot' => false, 'watchlist' => array( @@ -123,10 +129,11 @@ class ApiRollback extends ApiBase { return array( 'title' => "Title of the page you want to roll back. Cannot be used together with {$p}pageid", 'pageid' => "Page ID of the page you want to roll back. Cannot be used together with {$p}title", - 'user' => 'Name of the user whose edits are to be rolled back. If ' . - 'set incorrectly, you\'ll get a badtoken error.', - 'token' => 'A rollback token previously retrieved through ' . - "{$this->getModulePrefix()}prop=revisions", + 'user' => 'Name of the user whose edits are to be rolled back.', + 'token' => array( + /* Standard description automatically prepended */ + 'For compatibility, the token used in the web UI is also accepted.' + ), 'summary' => 'Custom edit summary. If empty, default summary will be used', 'markbot' => 'Mark the reverted edits and the revert as bot edits', 'watchlist' => 'Unconditionally add or remove the page from your watchlist, ' . @@ -142,12 +149,10 @@ class ApiRollback extends ApiBase { } public function needsToken() { - return true; + return 'rollback'; } - public function getTokenSalt() { - $params = $this->extractRequestParams(); - + protected function getWebUITokenSalt( array $params ) { return array( $this->getRbTitle( $params )->getPrefixedText(), $this->getRbUser( $params ) diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php index 0433bc9c83..5d527fc79b 100644 --- a/includes/api/ApiSetNotificationTimestamp.php +++ b/includes/api/ApiSetNotificationTimestamp.php @@ -202,11 +202,7 @@ class ApiSetNotificationTimestamp extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getAllowedParams( $flags = 0 ) { @@ -214,7 +210,6 @@ class ApiSetNotificationTimestamp extends ApiBase { 'entirewatchlist' => array( ApiBase::PARAM_TYPE => 'boolean' ), - 'token' => null, 'timestamp' => array( ApiBase::PARAM_TYPE => 'timestamp' ), @@ -239,7 +234,6 @@ class ApiSetNotificationTimestamp extends ApiBase { 'timestamp' => 'Timestamp to which to set the notification timestamp', 'torevid' => 'Revision to set the notification timestamp to (one page only)', 'newerthanrevid' => 'Revision to set the notification timestamp newer than (one page only)', - 'token' => 'A token previously acquired via prop=info', 'continue' => 'When more results are available, use this to continue', ); } diff --git a/includes/api/ApiTokens.php b/includes/api/ApiTokens.php index 8e2ecfbe94..9287fe6e7f 100644 --- a/includes/api/ApiTokens.php +++ b/includes/api/ApiTokens.php @@ -25,11 +25,16 @@ */ /** + * @deprecated since 1.24 * @ingroup API */ class ApiTokens extends ApiBase { public function execute() { + $this->setWarning( + "action=tokens has been deprecated. Please use action=query&meta=tokens instead." + ); + $params = $this->extractRequestParams(); $res = array(); @@ -88,7 +93,10 @@ class ApiTokens extends ApiBase { } public function getDescription() { - return 'Gets tokens for data-modifying actions.'; + return array( + 'This module is deprecated in favor of action=query&meta=tokens.', + 'Gets tokens for data-modifying actions.' + ); } protected function getExamples() { diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php index acb7ed2653..2854a82529 100644 --- a/includes/api/ApiUnblock.php +++ b/includes/api/ApiUnblock.php @@ -89,7 +89,6 @@ class ApiUnblock extends ApiBase { ApiBase::PARAM_TYPE => 'integer', ), 'user' => null, - 'token' => null, 'reason' => '', ); } @@ -102,7 +101,6 @@ class ApiUnblock extends ApiBase { "Cannot be used together with {$p}user", 'user' => "Username, IP address or IP range you want to unblock. " . "Cannot be used together with {$p}id", - 'token' => "An unblock token previously obtained through prop=info", 'reason' => 'Reason for unblock', ); } @@ -112,11 +110,7 @@ class ApiUnblock extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php index df245cdeb3..07aad9f57d 100644 --- a/includes/api/ApiUndelete.php +++ b/includes/api/ApiUndelete.php @@ -96,10 +96,6 @@ class ApiUndelete extends ApiBase { ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'reason' => '', 'timestamps' => array( ApiBase::PARAM_TYPE => 'timestamp', @@ -124,10 +120,6 @@ class ApiUndelete extends ApiBase { public function getParamDescription() { return array( 'title' => 'Title of the page you want to restore', - 'token' => array( - 'An undelete token previously retrieved through list=deletedrevs, or ', - 'a delete token retrieved through action=tokens.' - ), 'reason' => 'Reason for restoring', 'timestamps' => array( 'Timestamps of the revisions to restore.', @@ -151,11 +143,7 @@ class ApiUndelete extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php index 368e7ce6a4..aa8fe21db0 100644 --- a/includes/api/ApiUpload.php +++ b/includes/api/ApiUpload.php @@ -688,10 +688,6 @@ class ApiUpload extends ApiBase { ApiBase::PARAM_DFLT => '' ), 'text' => null, - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'watch' => array( ApiBase::PARAM_DFLT => false, ApiBase::PARAM_DEPRECATED => true, @@ -735,7 +731,6 @@ class ApiUpload extends ApiBase { public function getParamDescription() { $params = array( 'filename' => 'Target filename', - 'token' => 'Edit token. You can get one of these through prop=info', 'comment' => 'Upload comment. Also used as the initial page text for new ' . 'files if "text" is not specified', 'text' => 'Initial page text for new files', @@ -771,24 +766,20 @@ class ApiUpload extends ApiBase { ' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter', ' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter', 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when', - 'sending the "file". Also you must get and send an edit token before doing any upload stuff.' + 'sending the "file".', ); } public function needsToken() { - return true; - } - - public function getTokenSalt() { - return ''; + return 'csrf'; } public function getExamples() { return array( 'api.php?action=upload&filename=Wiki.png' . - '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png' + '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC' => 'Upload from a URL', - 'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1' + 'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC' => 'Complete an upload that failed due to warnings', ); } diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php index 0bed859356..c3ceb3457b 100644 --- a/includes/api/ApiUserrights.php +++ b/includes/api/ApiUserrights.php @@ -35,7 +35,7 @@ class ApiUserrights extends ApiBase { public function execute() { $params = $this->extractRequestParams(); - $user = $this->getUrUser(); + $user = $this->getUrUser( $params ); $form = new UserrightsPage; $form->setContext( $this->getContext() ); @@ -53,14 +53,14 @@ class ApiUserrights extends ApiBase { } /** + * @param array $params * @return User */ - private function getUrUser() { + private function getUrUser( array $params ) { if ( $this->mUser !== null ) { return $this->mUser; } - $params = $this->extractRequestParams(); $this->requireOnlyOneParameter( $params, 'user', 'userid' ); $user = isset( $params['user'] ) ? $params['user'] : '#' . $params['userid']; @@ -101,10 +101,6 @@ class ApiUserrights extends ApiBase { ApiBase::PARAM_TYPE => User::getAllGroups(), ApiBase::PARAM_ISMULTI => true ), - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'reason' => array( ApiBase::PARAM_DFLT => '' ) @@ -117,7 +113,10 @@ class ApiUserrights extends ApiBase { 'userid' => 'User id', 'add' => 'Add the user to these groups', 'remove' => 'Remove the user from these groups', - 'token' => 'A userrights token previously retrieved through list=users', + 'token' => array( + /* Standard description automatically prepended */ + 'For compatibility, the token used in the web UI is also accepted.' + ), 'reason' => 'Reason for the change', ); } @@ -127,11 +126,11 @@ class ApiUserrights extends ApiBase { } public function needsToken() { - return true; + return 'userrights'; } - public function getTokenSalt() { - return $this->getUrUser()->getName(); + protected function getWebUITokenSalt( array $params ) { + return $this->getUrUser( $params )->getName(); } public function getExamples() { diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index 80602606c8..e6a660b36f 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -166,10 +166,6 @@ class ApiWatch extends ApiBase { } public function needsToken() { - return true; - } - - public function getTokenSalt() { return 'watch'; } @@ -181,10 +177,6 @@ class ApiWatch extends ApiBase { ), 'unwatch' => false, 'uselang' => null, - 'token' => array( - ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true - ), 'continue' => '', ); if ( $flags ) { @@ -201,7 +193,6 @@ class ApiWatch extends ApiBase { 'title' => 'The page to (un)watch. use titles instead', 'unwatch' => 'If set the page will be unwatched rather than watched', 'uselang' => 'Language to show the message in', - 'token' => 'A token previously acquired via prop=info', 'continue' => 'When more results are available, use this to continue', ); } diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php index 17c5b6eb1b..b779a029c1 100644 --- a/includes/changes/OldChangesList.php +++ b/includes/changes/OldChangesList.php @@ -21,6 +21,7 @@ */ class OldChangesList extends ChangesList { + /** * Format a line using the old system (aka without any javascript). * @@ -33,10 +34,6 @@ class OldChangesList extends ChangesList { public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) { wfProfileIn( __METHOD__ ); - # Should patrol-related stuff be shown? - $unpatrolled = $this->showAsUnpatrolled( $rc ); - - $s = ''; $classes = array(); // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468) if ( $linenumber ) { @@ -52,20 +49,52 @@ class OldChangesList extends ChangesList { $classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; + $html = $this->formatChangeLine( $rc, $watched ); + + if ( $this->watchlist ) { + $classes[] = Sanitizer::escapeClass( 'watchlist-' . + $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); + } + + if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$html, $rc, &$classes ) ) ) { + wfProfileOut( __METHOD__ ); + + return false; + } + + wfProfileOut( __METHOD__ ); + + $dateheader = ''; // $html now contains only
  • ...
  • , for hooks' convenience. + $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); + + return "$dateheader
  • " . $html . "
  • \n"; + } + + /** + * @param RecentChange $rc + * @param boolean $watched + * + * @return string + */ + private function formatChangeLine( RecentChange $rc, $watched ) { + $html = ''; + if ( $rc->mAttribs['rc_log_type'] ) { $logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] ); - $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] ); + $this->insertLog( $html, $logtitle, $rc->mAttribs['rc_log_type'] ); // Log entries (old format) or log targets, and special pages } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); + list( $name, $htmlubpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); if ( $name == 'Log' ) { - $this->insertLog( $s, $rc->getTitle(), $subpage ); + $this->insertLog( $html, $rc->getTitle(), $htmlubpage ); } // Regular entries } else { - $this->insertDiffHist( $s, $rc, $unpatrolled ); + $unpatrolled = $this->showAsUnpatrolled( $rc ); + + $this->insertDiffHist( $html, $rc, $unpatrolled ); # M, N, b and ! (minor, new, bot and unpatrolled) - $s .= $this->recentChangesFlags( + $html .= $this->recentChangesFlags( array( 'newpage' => $rc->mAttribs['rc_type'] == RC_NEW, 'minor' => $rc->mAttribs['rc_minor'], @@ -74,56 +103,40 @@ class OldChangesList extends ChangesList { ), '' ); - $this->insertArticleLink( $s, $rc, $unpatrolled, $watched ); + $this->insertArticleLink( $html, $rc, $unpatrolled, $watched ); } # Edit/log timestamp - $this->insertTimestamp( $s, $rc ); + $this->insertTimestamp( $html, $rc ); # Bytes added or removed if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) { $cd = $this->formatCharacterDifference( $rc ); if ( $cd !== '' ) { - $s .= $cd . ' . . '; + $html .= $cd . ' . . '; } } if ( $rc->mAttribs['rc_type'] == RC_LOG ) { - $s .= $this->insertLogEntry( $rc ); + $html .= $this->insertLogEntry( $rc ); } else { # User tool links - $this->insertUserRelatedLinks( $s, $rc ); + $this->insertUserRelatedLinks( $html, $rc ); # LTR/RTL direction mark - $s .= $this->getLanguage()->getDirMark(); - $s .= $this->insertComment( $rc ); + $html .= $this->getLanguage()->getDirMark(); + $html .= $this->insertComment( $rc ); } # Tags - $this->insertTags( $s, $rc, $classes ); + $this->insertTags( $html, $rc, $classes ); # Rollback - $this->insertRollback( $s, $rc ); + $this->insertRollback( $html, $rc ); # For subclasses - $this->insertExtra( $s, $rc, $classes ); + $this->insertExtra( $html, $rc, $classes ); # How many users watch this page if ( $rc->numberofWatchingusers > 0 ) { - $s .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers ); - } - - if ( $this->watchlist ) { - $classes[] = Sanitizer::escapeClass( 'watchlist-' . - $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); + $html .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers ); } - if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) { - wfProfileOut( __METHOD__ ); - - return false; - } - - wfProfileOut( __METHOD__ ); - - $dateheader = ''; // $s now contains only
  • ...
  • , for hooks' convenience. - $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); - - return "$dateheader
  • " . $s . "
  • \n"; + return $html; } } diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php index 2673084ebe..72414585d0 100644 --- a/includes/content/CssContent.php +++ b/includes/content/CssContent.php @@ -58,7 +58,7 @@ class CssContent extends TextContent { $text = $this->getNativeData(); $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); - return new CssContent( $pst ); + return new static( $pst ); } /** diff --git a/includes/content/CssContentHandler.php b/includes/content/CssContentHandler.php index fd326f01b1..1ab4ee2a8f 100644 --- a/includes/content/CssContentHandler.php +++ b/includes/content/CssContentHandler.php @@ -36,27 +36,8 @@ class CssContentHandler extends TextContentHandler { parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) ); } - /** - * @param string $text - * @param string $format - * - * @return CssContent - * - * @see ContentHandler::unserializeContent() - */ - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new CssContent( $text ); - } - - /** - * @return CssContent A new CssContent object with empty text. - * - * @see ContentHandler::makeEmptyContent() - */ - public function makeEmptyContent() { - return new CssContent( '' ); + protected function getContentClass() { + return 'CssContent'; } /** diff --git a/includes/content/JSONContentHandler.php b/includes/content/JSONContentHandler.php index 6b7752781e..33f203661c 100644 --- a/includes/content/JSONContentHandler.php +++ b/includes/content/JSONContentHandler.php @@ -16,6 +16,8 @@ class JSONContentHandler extends TextContentHandler { /** * The class name of objects that should be created * + * @deprecated override getContentClass instead + * * @var string */ protected $contentClass = 'JSONContent'; @@ -25,25 +27,13 @@ class JSONContentHandler extends TextContentHandler { } /** - * Unserializes a JSONContent object. - * - * @param string $text Serialized form of the content - * @param null|string $format The format used for serialization - * - * @return JSONContent - */ - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - return new $this->contentClass( $text ); - } - - /** - * Creates an empty JSONContent object. + * Temporary back-compat until extensions + * are updated to override this * - * @return JSONContent + * @return string */ - public function makeEmptyContent() { - return new $this->contentClass( '' ); + protected function getContentClass() { + return $this->contentClass; } /** diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php index 442b705282..0991f07683 100644 --- a/includes/content/JavaScriptContent.php +++ b/includes/content/JavaScriptContent.php @@ -57,7 +57,7 @@ class JavaScriptContent extends TextContent { $text = $this->getNativeData(); $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); - return new JavaScriptContent( $pst ); + return new static( $pst ); } /** diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php index 122003fe53..8d62e2a3ad 100644 --- a/includes/content/JavaScriptContentHandler.php +++ b/includes/content/JavaScriptContentHandler.php @@ -36,27 +36,8 @@ class JavaScriptContentHandler extends TextContentHandler { parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) ); } - /** - * @param string $text - * @param string $format - * - * @return JavaScriptContent - * - * @see ContentHandler::unserializeContent() - */ - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new JavaScriptContent( $text ); - } - - /** - * @return JavaScriptContent A new JavaScriptContent object with empty text. - * - * @see ContentHandler::makeEmptyContent() - */ - public function makeEmptyContent() { - return new JavaScriptContent( '' ); + protected function getContentClass() { + return 'JavaScriptContent'; } /** diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php index abaac53f3a..edbd075ca9 100644 --- a/includes/content/MessageContent.php +++ b/includes/content/MessageContent.php @@ -106,7 +106,7 @@ class MessageContent extends AbstractContent { } /** - * @param int $maxLength Maximum length of the summary text, defaults to 250. + * @param int $maxlength Maximum length of the summary text, defaults to 250. * * @return string The summary text. * diff --git a/includes/content/TextContentHandler.php b/includes/content/TextContentHandler.php index 0c9ee37577..ffe1acbd20 100644 --- a/includes/content/TextContentHandler.php +++ b/includes/content/TextContentHandler.php @@ -92,6 +92,19 @@ class TextContentHandler extends ContentHandler { return $mergedContent; } + /** + * Returns the name of the associated Content class, to + * be used when creating new objects. Override expected + * by subclasses. + * + * @since 1.24 + * + * @return string + */ + protected function getContentClass() { + return 'TextContent'; + } + /** * Unserializes a Content object of the type supported by this ContentHandler. * @@ -105,7 +118,8 @@ class TextContentHandler extends ContentHandler { public function unserializeContent( $text, $format = null ) { $this->checkFormat( $format ); - return new TextContent( $text ); + $class = $this->getContentClass(); + return new $class( $text ); } /** @@ -116,7 +130,8 @@ class TextContentHandler extends ContentHandler { * @return Content A new TextContent object with empty text. */ public function makeEmptyContent() { - return new TextContent( '' ); + $class = $this->getContentClass(); + return new $class( '' ); } } diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php index 237029b0ba..d23f925d80 100644 --- a/includes/content/WikitextContent.php +++ b/includes/content/WikitextContent.php @@ -52,7 +52,7 @@ class WikitextContent extends TextContent { if ( $sect === false ) { return false; } else { - return new WikitextContent( $sect ); + return new static( $sect ); } } @@ -104,7 +104,7 @@ class WikitextContent extends TextContent { $text = $wgParser->replaceSection( $oldtext, $sectionId, $text ); } - $newContent = new WikitextContent( $text ); + $newContent = new static( $text ); wfProfileOut( __METHOD__ ); @@ -125,7 +125,7 @@ class WikitextContent extends TextContent { $text .= "\n\n"; $text .= $this->getNativeData(); - return new WikitextContent( $text ); + return new static( $text ); } /** @@ -145,7 +145,7 @@ class WikitextContent extends TextContent { $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); rtrim( $pst ); - return ( $text === $pst ) ? $this : new WikitextContent( $pst ); + return ( $text === $pst ) ? $this : new static( $pst ); } /** @@ -164,7 +164,7 @@ class WikitextContent extends TextContent { $text = $this->getNativeData(); $plt = $wgParser->getPreloadText( $text, $title, $popts, $params ); - return new WikitextContent( $plt ); + return new static( $plt ); } /** @@ -246,7 +246,7 @@ class WikitextContent extends TextContent { '[[' . $target->getFullText() . ']]', $this->getNativeData(), 1 ); - return new WikitextContent( $newText ); + return new static( $newText ); } /** diff --git a/includes/content/WikitextContentHandler.php b/includes/content/WikitextContentHandler.php index 5ae3e25d6e..c1db1de63a 100644 --- a/includes/content/WikitextContentHandler.php +++ b/includes/content/WikitextContentHandler.php @@ -34,19 +34,8 @@ class WikitextContentHandler extends TextContentHandler { parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) ); } - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new WikitextContent( $text ); - } - - /** - * @return Content A new WikitextContent object with empty text. - * - * @see ContentHandler::makeEmptyContent - */ - public function makeEmptyContent() { - return new WikitextContent( '' ); + protected function getContentClass() { + return 'WikitextContent'; } /** @@ -79,7 +68,8 @@ class WikitextContentHandler extends TextContentHandler { $redirectText .= "\n" . $text; } - return new WikitextContent( $redirectText ); + $class = $this->getContentClass(); + return new $class( $redirectText ); } /** diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php index 091e8da911..ede10fe93c 100644 --- a/includes/context/RequestContext.php +++ b/includes/context/RequestContext.php @@ -425,6 +425,7 @@ class RequestContext implements IContextSource { * Get the RequestContext object associated with the main request * and gives a warning to the log, to find places, where a context maybe is missing. * + * @param string $func * @return RequestContext * @since 1.24 */ diff --git a/includes/db/Database.php b/includes/db/Database.php index a46ee1c068..9584e466f1 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -679,8 +679,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { * - DBO_DEBUG: output some debug info (same as debug()) * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) * - DBO_TRX: automatically start transactions - * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode - * and removes it in command line mode * - DBO_PERSISTENT: use persistant database connection * @return bool */ diff --git a/includes/debug/MWDebug.php b/includes/debug/MWDebug.php index 01e0ba0bb6..fb11f368ea 100644 --- a/includes/debug/MWDebug.php +++ b/includes/debug/MWDebug.php @@ -538,7 +538,8 @@ class MWDebug { return array( 'mwVersion' => $wgVersion, - 'phpVersion' => PHP_VERSION, + 'phpEngine' => wfIsHHVM() ? 'HHVM' : 'PHP', + 'phpVersion' => wfIsHHVM() ? HHVM_VERSION : PHP_VERSION, 'gitRevision' => GitInfo::headSHA1(), 'gitBranch' => GitInfo::currentBranch(), 'gitViewUrl' => GitInfo::headViewUrl(), diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index ee918f3785..50e08ca1d2 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -310,11 +310,11 @@ class DifferenceEngine extends ContextSource { 'undoafter' => $this->mOldid, 'undo' => $this->mNewid ) ), - 'title' => Linker::titleAttrib( 'undo' ) + 'title' => Linker::titleAttrib( 'undo' ), ), $this->msg( 'editundo' )->text() ); - $revisionTools[] = $undoLink; + $revisionTools['mw-diff-undo'] = $undoLink; } } @@ -387,8 +387,14 @@ class DifferenceEngine extends ContextSource { wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) ); $formattedRevisionTools = array(); // Put each one in parentheses (poor man's button) - foreach ( $revisionTools as $tool ) { - $formattedRevisionTools[] = $this->msg( 'parentheses' )->rawParams( $tool )->escaped(); + foreach ( $revisionTools as $key => $tool ) { + $toolClass = is_string( $key ) ? $key : 'mw-diff-tool'; + $element = Html::rawElement( + 'span', + array( 'class' => $toolClass ), + $this->msg( 'parentheses' )->rawParams( $tool )->escaped() + ); + $formattedRevisionTools[] = $element; } $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . ' ' . implode( ' ', $formattedRevisionTools ); @@ -1052,8 +1058,13 @@ class DifferenceEngine extends ContextSource { $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold'; $msg = $this->msg( $key )->escaped(); - $header .= ' ' . $this->msg( 'parentheses' )->rawParams( - Linker::linkKnown( $title, $msg, array(), $editQuery ) )->plain(); + $editLink = $this->msg( 'parentheses' )->rawParams( + Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->plain(); + $header .= ' ' . Html::rawElement( + 'span', + array( 'class' => 'mw-diff-edit' ), + $editLink + ); if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $header = Html::rawElement( 'span', diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php index 4fab33b9ec..ba2d4ac895 100644 --- a/includes/filerepo/file/File.php +++ b/includes/filerepo/file/File.php @@ -714,7 +714,7 @@ abstract class File { */ function canRender() { if ( !isset( $this->canRender ) ) { - $this->canRender = $this->getHandler() && $this->handler->canRender( $this ); + $this->canRender = $this->getHandler() && $this->handler->canRender( $this ) && $this->exists(); } return $this->canRender; diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php index 0adcc73c62..710058fba6 100644 --- a/includes/filerepo/file/OldLocalFile.php +++ b/includes/filerepo/file/OldLocalFile.php @@ -404,4 +404,18 @@ class OldLocalFile extends LocalFile { return true; } + + /** + * If archive name is an empty string, then file does not "exist" + * + * This is the case for a couple files on Wikimedia servers where + * the old version is "lost". + */ + public function exists() { + $archiveName = $this->getArchiveName(); + if ( $archiveName === '' || !is_string( $archiveName ) ) { + return false; + } + return parent::exists(); + } } diff --git a/includes/htmlform/HTMLAutoCompleteSelectField.php b/includes/htmlform/HTMLAutoCompleteSelectField.php new file mode 100644 index 0000000000..49053628de --- /dev/null +++ b/includes/htmlform/HTMLAutoCompleteSelectField.php @@ -0,0 +1,165 @@ + false, + ); + + parent::__construct( $params ); + + if ( array_key_exists( 'autocomplete-messages', $this->mParams ) ) { + foreach ( $this->mParams['autocomplete-messages'] as $key => $value ) { + $key = $this->msg( $key )->plain(); + $this->autocomplete[$key] = strval( $value ); + } + } elseif ( array_key_exists( 'autocomplete', $this->mParams ) ) { + foreach ( $this->mParams['autocomplete'] as $key => $value ) { + $this->autocomplete[$key] = strval( $value ); + } + } + if ( !is_array( $this->autocomplete ) || !$this->autocomplete ) { + throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' ); + } + + $this->getOptions(); + if ( $this->mOptions && !in_array( 'other', $this->mOptions, true ) ) { + if ( isset( $params['other-message'] ) ) { + $msg = wfMessage( $params['other-message'] )->text(); + } elseif ( isset( $params['other'] ) ) { + $msg = $params['other']; + } else { + $msg = wfMessage( 'htmlform-selectorother-other' )->text(); + } + $this->mOptions[$msg] = 'other'; + } + } + + function loadDataFromRequest( $request ) { + if ( $request->getCheck( $this->mName ) ) { + $val = $request->getText( $this->mName . '-select', 'other' ); + + if ( $val === 'other' ) { + $val = $request->getText( $this->mName ); + if ( isset( $this->autocomplete[$val] ) ) { + $val = $this->autocomplete[$val]; + } + } + + return $val; + } else { + return $this->getDefault(); + } + } + + function validate( $value, $alldata ) { + $p = parent::validate( $value, $alldata ); + + if ( $p !== true ) { + return $p; + } + + $validOptions = HTMLFormField::flattenOptions( $this->getOptions() ); + + if ( in_array( strval( $value ), $validOptions, true ) ) { + return true; + } elseif ( in_array( strval( $value ), $this->autocomplete, true ) ) { + return true; + } elseif ( $this->mParams['require-match'] ) { + return $this->msg( 'htmlform-select-badoption' )->parse(); + } + + return true; + } + + function getAttributes( array $list ) { + $attribs = array( + 'type' => 'text', + 'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ), + ) + parent::getAttributes( $list ); + + if ( $this->getOptions() ) { + $attribs['data-hide-if'] = FormatJson::encode( + array( '!==', $this->mName . '-select', 'other' ) + ); + } + + return $attribs; + } + + function getInputHTML( $value ) { + $oldClass = $this->mClass; + $this->mClass = (array)$this->mClass; + + $valInSelect = false; + $ret = ''; + + if ( $this->getOptions() ) { + if ( $value !== false ) { + $value = strval( $value ); + $valInSelect = in_array( + $value, HTMLFormField::flattenOptions( $this->getOptions() ), true + ); + } + + $selected = $valInSelect ? $value : 'other'; + $select = new XmlSelect( $this->mName . '-select', $this->mID . '-select', $selected ); + $select->addOptions( $this->getOptions() ); + $select->setAttribute( 'class', 'mw-htmlform-select-or-other' ); + + if ( !empty( $this->mParams['disabled'] ) ) { + $select->setAttribute( 'disabled', 'disabled' ); + } + + if ( isset( $this->mParams['tabindex'] ) ) { + $select->setAttribute( 'tabindex', $this->mParams['tabindex'] ); + } + + $ret = $select->getHTML() . "
    \n"; + + $this->mClass[] = 'mw-htmlform-hide-if'; + } + + if ( $valInSelect ) { + $value = ''; + } else { + $key = array_search( strval( $value ), $this->autocomplete, true ); + if ( $key !== false ) { + $value = $key; + } + } + + $this->mClass[] = 'mw-htmlform-autocomplete'; + $ret .= parent::getInputHTML( $valInSelect ? '' : $value ); + $this->mClass = $oldClass; + + return $ret; + } + +} diff --git a/includes/htmlform/HTMLCheckMatrix.php b/includes/htmlform/HTMLCheckMatrix.php index 825552696f..6c538fdd15 100644 --- a/includes/htmlform/HTMLCheckMatrix.php +++ b/includes/htmlform/HTMLCheckMatrix.php @@ -80,8 +80,6 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { * @return string */ function getInputHTML( $value ) { - global $wgUseMediaWikiUIEverywhere; - $html = ''; $tableContents = ''; $rows = $this->mParams['rows']; @@ -129,7 +127,7 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { $thisAttribs['disabled'] = 1; } $chkBox = Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs ); - if ( $wgUseMediaWikiUIEverywhere ) { + if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { $chkBox = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) . $chkBox . Html::element( 'label', array( 'for' => $thisId ) ) . diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 65018908f0..fc2222ad8c 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -120,6 +120,7 @@ class HTMLForm extends ContextSource { 'edittools' => 'HTMLEditTools', 'checkmatrix' => 'HTMLCheckMatrix', 'cloner' => 'HTMLFormFieldCloner', + 'autocompleteselect' => 'HTMLAutoCompleteSelectField', // HTMLTextField will output the correct type="" attribute automagically. // There are about four zillion other HTML5 input types, like range, but // we don't use those at the moment, so no point in adding all of them. @@ -300,9 +301,8 @@ class HTMLForm extends ContextSource { * @return string */ public function getDisplayFormat() { - global $wgHTMLFormAllowTableFormat; $format = $this->displayFormat; - if ( !$wgHTMLFormAllowTableFormat && $format === 'table' ) { + if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) { $format = 'div'; } return $format; @@ -845,8 +845,6 @@ class HTMLForm extends ContextSource { * @return string HTML. */ function getHiddenFields() { - global $wgArticlePath; - $html = ''; if ( $this->getMethod() == 'post' ) { $html .= Html::hidden( @@ -857,7 +855,8 @@ class HTMLForm extends ContextSource { $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; } - if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() == 'get' ) { + $articlePath = $this->getConfig()->get( 'ArticlePath' ); + if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() == 'get' ) { $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; } @@ -874,8 +873,8 @@ class HTMLForm extends ContextSource { * @return string HTML. */ function getButtons() { - global $wgUseMediaWikiUIEverywhere; $buttons = ''; + $useMediaWikiUIEverywhere = $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ); if ( $this->mShowSubmit ) { $attribs = array(); @@ -894,7 +893,7 @@ class HTMLForm extends ContextSource { $attribs['class'] = array( 'mw-htmlform-submit' ); - if ( $this->isVForm() || $wgUseMediaWikiUIEverywhere ) { + if ( $this->isVForm() || $useMediaWikiUIEverywhere ) { array_push( $attribs['class'], 'mw-ui-button', 'mw-ui-constructive' ); } @@ -937,8 +936,8 @@ class HTMLForm extends ContextSource { $attrs['id'] = $button['id']; } - if ( $wgUseMediaWikiUIEverywhere ) { - if ( isset( $attrs['class' ] ) ) { + if ( $useMediaWikiUIEverywhere ) { + if ( isset( $attrs['class'] ) ) { $attrs['class'] .= ' mw-ui-button'; } else { $attrs['class'] = 'mw-ui-button'; @@ -1438,20 +1437,19 @@ class HTMLForm extends ContextSource { * @return string */ public function getAction() { - global $wgScript, $wgArticlePath; - // If an action is alredy provided, return it if ( $this->mAction !== false ) { return $this->mAction; } - // Check whether we are in GET mode and $wgArticlePath contains a "?" + $articlePath = $this->getConfig()->get( 'ArticlePath' ); + // Check whether we are in GET mode and the ArticlePath contains a "?" // meaning that getLocalURL() would return something like "index.php?title=...". // As browser remove the query string before submitting GET forms, - // it means that the title would be lost. In such case use $wgScript instead + // it means that the title would be lost. In such case use wfScript() instead // and put title in an hidden field (see getHiddenFields()). - if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() === 'get' ) { - return $wgScript; + if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) { + return wfScript(); } return $this->getTitle()->getLocalURL(); diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php index aca3119ca8..987925c747 100644 --- a/includes/installer/Installer.php +++ b/includes/installer/Installer.php @@ -434,7 +434,11 @@ abstract class Installer { public function doEnvironmentChecks() { // Php version has already been checked by entry scripts // Show message here for information purposes - $this->showMessage( 'config-env-php', PHP_VERSION ); + if ( wfIsHHVM() ) { + $this->showMessage( 'config-env-hhvm', HHVM_VERSION ); + } else { + $this->showMessage( 'config-env-php', PHP_VERSION ); + } $good = true; // Must go here because an old version of PCRE can prevent other checks from completing diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php index f797ef9a45..3094d5571b 100644 --- a/includes/installer/WebInstallerOutput.php +++ b/includes/installer/WebInstallerOutput.php @@ -124,53 +124,55 @@ class WebInstallerOutput { * @return string */ public function getCSS() { - // Horrible, horrible hack: the installer is currently hardcoded to use the Vector skin, so load - // it here. Include instead of require, as this will work without it, it will just look bad. - // We need the 'global' statement for $wgResourceModules because the Vector skin adds the - // definitions for its RL modules there that we use implicitly below. - // @codingStandardsIgnoreStart - global $wgResourceModules; // This is NOT UNUSED! - // @codingStandardsIgnoreEnd global $wgStyleDirectory; - include_once "$wgStyleDirectory/Vector/Vector.php"; $moduleNames = array( // See SkinTemplate::setupSkinUserCss 'mediawiki.legacy.shared', // See Vector::setupSkinUserCss 'mediawiki.skinning.interface', - 'skins.vector.styles', - - 'mediawiki.legacy.config', ); - $css = ''; + if ( file_exists( "$wgStyleDirectory/Vector/Vector.php" ) ) { + // Force loading Vector skin if available as a fallback skin + // for whatever ResourceLoader wants to have as the default. + + // Include instead of require, as this will work without it, it will just look bad. + // We need the 'global' statement for $wgResourceModules because the Vector skin adds the + // definitions for its RL modules there that we use implicitly below. + + // @codingStandardsIgnoreStart + global $wgResourceModules; // This is NOT UNUSED! + // @codingStandardsIgnoreEnd + + include_once "$wgStyleDirectory/Vector/Vector.php"; + + $moduleNames[] = 'skins.vector.styles'; + } + + $moduleNames[] = 'mediawiki.legacy.config'; $resourceLoader = new ResourceLoader(); $rlContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( array( 'debug' => 'true', 'lang' => $this->getLanguageCode(), 'only' => 'styles', - 'skin' => 'vector', ) ) ); + + $styles = array(); foreach ( $moduleNames as $moduleName ) { /** @var ResourceLoaderFileModule $module */ $module = $resourceLoader->getModule( $moduleName ); - // One of the modules will be missing if Vector is unavailable - if ( !$module ) { - continue; - } // Based on: ResourceLoaderFileModule::getStyles (without the DB query) - $styles = ResourceLoader::makeCombinedStyles( $module->readStyleFiles( - $module->getStyleFiles( $rlContext ), - $module->getFlip( $rlContext ) - ) ); - - $css .= implode( "\n", $styles ); + $styles = array_merge( $styles, ResourceLoader::makeCombinedStyles( + $module->readStyleFiles( + $module->getStyleFiles( $rlContext ), + $module->getFlip( $rlContext ) + ) ) ); } - return $css; + return implode( "\n", $styles ); } /** @@ -271,8 +273,8 @@ class WebInstallerOutput { $this->getDir() ) ) . "\n"; ?>
    -
    -
    +
    +

    outputTitle(); ?>

    Chyba: Vložení aktualizačních klíčů do tabulek selhalo s následující chybou: $1", "config-install-sysop": "Zakládá se uživatelský účet správce", "config-install-subscribe-fail": "Nelze se přihlásit k odběru mediawiki-announce: $1", "config-install-subscribe-notpossible": "Není nainstalován cURL a není dostupné allow_url_fopen.", diff --git a/includes/installer/i18n/de.json b/includes/installer/i18n/de.json index 532d7c1d63..284a356669 100644 --- a/includes/installer/i18n/de.json +++ b/includes/installer/i18n/de.json @@ -58,6 +58,7 @@ "config-env-good": "Die Installationsumgebung wurde geprüft.\nMediaWiki kann installiert werden.", "config-env-bad": "Die Installationsumgebung wurde geprüft.\nMediaWiki kann nicht installiert werden.", "config-env-php": "Die Skriptsprache „PHP“ ($1) ist installiert.", + "config-env-hhvm": "HHVM $1 ist installiert.", "config-unicode-using-utf8": "Zur Unicode-Normalisierung wird Brion Vibbers utf8_normalize.so eingesetzt.", "config-unicode-using-intl": "Zur Unicode-Normalisierung wird die [http://pecl.php.net/intl PECL-Erweiterung intl] eingesetzt.", "config-unicode-pure-php-warning": "'''Warnung:''' Die [http://pecl.php.net/intl PECL-Erweiterung intl] ist für die Unicode-Normalisierung nicht verfügbar, so dass stattdessen die langsame pure-PHP-Implementierung genutzt wird.\nSofern eine Website mit großer Benutzeranzahl betrieben wird, sollten weitere Informationen auf der Webseite [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-Normalisierung (en)] gelesen werden.", @@ -320,6 +321,8 @@ "config-install-stats": "Statistiken werden initialisiert", "config-install-keys": "Geheimschlüssel werden erstellt", "config-insecure-keys": "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1, {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert {{PLURAL:$2|wurde, ist|wurden, sind}} nicht sehr sicher. {{PLURAL:$2|Er sollte|Sie sollten}} manuell geändert werden.", + "config-install-updates": "Unnötige Aktualisierungen nicht ausführen", + "config-install-updates-failed": "Fehler: Das Einfügen von Aktualisierungsschlüssel in die Tabellen ist mit dem folgenden Fehler fehlgeschlagen: $1", "config-install-sysop": "Administratorkonto wird erstellt", "config-install-subscribe-fail": "Abonnieren von „mediawiki-announce“ ist gescheitert: $1", "config-install-subscribe-notpossible": "cURL ist nicht installiert und allow_url_fopen ist nicht verfügbar.", diff --git a/includes/installer/i18n/en.json b/includes/installer/i18n/en.json index ca328cfee5..1e1c2da7eb 100644 --- a/includes/installer/i18n/en.json +++ b/includes/installer/i18n/en.json @@ -44,6 +44,7 @@ "config-env-good": "The environment has been checked.\nYou can install MediaWiki.", "config-env-bad": "The environment has been checked.\nYou cannot install MediaWiki.", "config-env-php": "PHP $1 is installed.", + "config-env-hhvm": "HHVM $1 is installed.", "config-unicode-using-utf8": "Using Brion Vibber's utf8_normalize.so for Unicode normalization.", "config-unicode-using-intl": "Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalization.", "config-unicode-pure-php-warning": "Warning: The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.\nIf you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].", diff --git a/includes/installer/i18n/es.json b/includes/installer/i18n/es.json index 135a32ba0c..755c2a69fb 100644 --- a/includes/installer/i18n/es.json +++ b/includes/installer/i18n/es.json @@ -24,7 +24,8 @@ "McDutchie", "Miguel2706", "Macofe", - "AVIADOR" + "AVIADOR", + "FuzzyDice" ] }, "config-desc": "El instalador de MediaWiki", @@ -41,9 +42,9 @@ "config-session-expired": "Tus datos de sesión parecen haber expirado.\nLas sesiones están configuradas por una duración de $1.\nPuedes incrementar esto configurando session.gc_maxlifetime en php.ini.\nReiniciar el proceso de instalación.", "config-no-session": "Se han perdido los datos de sesión.\nVerifica tu php.ini y comprueba que session.save_path está establecido en un directorio apropiado.", "config-your-language": "Tu idioma:", - "config-your-language-help": "Seleccionar un idioma a usar durante el proceso de instalación.", + "config-your-language-help": "Selecciona un idioma para usar durante el proceso de instalación.", "config-wiki-language": "Idioma del wiki:", - "config-wiki-language-help": "Seleccionar el idioma en el que el wiki será escrito predominantemente.", + "config-wiki-language-help": "Selecciona el idioma en el que se escribirá predominantemente el wiki.", "config-back": "← Atrás", "config-continue": "Continuar →", "config-page-language": "Idioma", @@ -69,6 +70,7 @@ "config-env-good": "El entorno ha sido comprobado.\nPuedes instalar MediaWiki.", "config-env-bad": "El entorno ha sido comprobado.\nNo puedes instalar MediaWiki.", "config-env-php": "PHP $1 está instalado.", + "config-env-hhvm": "HHVM $1 está instalado.", "config-unicode-using-utf8": "Usando utf8_normalize.so de Brion Vibber para la normalización Unicode.", "config-unicode-using-intl": "Usando la [http://pecl.php.net/intl extensión intl PECL] para la normalización Unicode.", "config-unicode-pure-php-warning": "'''Advertencia''': La [http://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Utilizando la implementación más lenta en PHP.\nSi tu web tiene mucho tráfico, te recomendamos leer acerca de la [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].", @@ -96,48 +98,48 @@ "config-no-cache": "'''Advertencia:''' No pudo encontrarse [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nEl caché de objetos no está habilitado.", "config-mod-security": "''' Advertencia ''': Su servidor web tiene [http://modsecurity.org/ mod_security] habilitado. Si la configuración es incorrecta, puede causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrarios.\nConsulte la [http://modsecurity.org/documentation/ documentación de mod_security] o contacte con el soporte de su servidor (''host'') si encuentra errores aleatorios.", "config-diff3-bad": "GNU diff3 no se encuentra.", - "config-git": "Se encontró el ''software'' de control de versiones Git: $1.", + "config-git": "Se encontró el software de control de versiones Git: $1.", "config-git-bad": "No se encontró el software de control de versiones Git.", "config-imagemagick": "ImageMagick encontrado: $1.\nLa miniaturización de imágenes se habilitará si habilitas las cargas.", "config-gd": "Se ha encontrado una biblioteca de gráficos GD integrada.\nLa miniaturización de imágenes se habilitará si habilitas las subidas.", "config-no-scaling": "No se ha encontrado ninguma biblioteca GD o ImageMagik.\nSe inhabilitará la miniaturización de imágenes.", - "config-no-uri": "'''Error:''' No se pudo determinar el URI actual.\nSe interrumpió la instalación.", + "config-no-uri": "Error: no se pudo determinar el URI actual.\nSe interrumpió la instalación.", "config-no-cli-uri": "Aviso: No se especificó --scriptpath; se usa el valor predeterminado: $1.", "config-using-server": "Utilizando el nombre de servidor \"$1\".", - "config-using-uri": "Utilizando la dirección URL del servidor \"$1$2\".", + "config-using-uri": "Utilizando la URL del servidor \"$1$2\".", "config-uploads-not-safe": "'''Atención:''' Su directorio por defecto para las cargas, $1, es vulnerable a la ejecución de scripts arbitrarios.\nAunque MediaWiki comprueba todos los archivos cargados por si hubiese amenazas de seguridad, es altamente recomendable [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security cerrar esta vulnerabilidad de seguridad] antes de activar las cargas.", "config-no-cli-uploads-check": "'''Atención:''' Su directorio predeterminado para cargas ($1) no está comprobado para la vulnerabilidad\n de ejecución arbitraria de comandos script durante la instalación de CLI.", "config-brokenlibxml": "El sistema tiene una combinación de versiones de PHP y de libxml2 que es poco confiable y puede provocar corrupción oculta en los datos de MediaWiki y otras aplicaciones web.\nActualizar a PHP 5.2.9 o posterior y a libxml2 2.7.3 o posterior ([//bugs.php.net/bug.php?id=45996 bug reportado con PHP]).\nInstalación abortada.", "config-suhosin-max-value-length": "Suhosin está instalado y limita el parámetro length GET a $1 bytes.\nEl componente ResourceLoader (gestor de recursos) de MediaWiki trabajará en este límite, pero eso perjudicará el rendimiento.\nSi es posible, deberías establecer suhosin.get.max_value_length en el valor 1024 o superior en php.ini y establecer $wgResourceLoaderMaxQueryLength en el mismo valor en php.ini.", - "config-db-type": "Tipo de base de datos", + "config-db-type": "Tipo de base de datos:", "config-db-host": "Servidor de la base de datos:", "config-db-host-help": "Si su servidor de base de datos está en otro servidor, escriba el nombre del host o su dirección IP aquí.\nSi está utilizando alojamiento web compartido, su proveedor de alojamiento debería darle el nombre correcto del servidor de alojamiento (host) en su documentación.\nSi va a instalarlo en un servidor Windows y utiliza MySQL, el uso de \"localhost\" como nombre del servidor puede no funcionar. Si no es así, intente poner \"127.0.0.1\" como dirección IP local.\nSi utiliza PostgreSQL, deje este campo en blanco para conectarse a través de un socket de Unix.", "config-db-host-oracle": "TNS de la base de datos:", "config-db-host-oracle-help": "Introduzca un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nombre de conexión local] válido; un archivo tnsnames.ora debe ser visible para esta instalación.
    Si está utilizando bibliotecas de cliente 10g o más recientes también puede utilizar el método de asignación de nombres [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].", - "config-db-wiki-settings": "Identifique este wiki", - "config-db-name": "Nombre de base de datos:", + "config-db-wiki-settings": "Identifica este wiki", + "config-db-name": "Nombre de la base de datos:", "config-db-name-help": "Elija un nombre que identifique su wiki.\nNo debe contener espacios.\n\nSi está utilizando alojamiento web compartido, su proveedor de alojamiento le dará un nombre específico de base de datos para que lo utilice, o bien le permitirá crear bases de datos a través de un panel de control.", - "config-db-name-oracle": "Esquema de base de datos:", + "config-db-name-oracle": "Esquema de la base de datos:", "config-db-account-oracle-warn": "Hay tres escenarios compatibles para la instalación de Oracle como base de datos back-end:\n\nSi desea crear una cuenta de base de datos como parte del proceso de instalación, por favor suministre una cuenta con función SYSDBA como cuenta de base de datos para la instalación y especifique las credenciales deseadas de la cuenta de acceso al web, de lo contrario puede crear manualmente la cuenta de acceso al web y suministrar sólo esa cuenta (si tiene los permisos necesarios para crear los objetos de esquema) o suministrar dos cuentas diferentes, una con privilegios de creación y otra con acceso restringido a la web\n\nLa secuencia de comandos (script) para crear una cuenta con los privilegios necesarios puede encontrarse en el directorio \"maintenance/oracle/\" de esta instalación. Tenga en cuenta que utilizando una cuenta restringida desactivará todas las capacidades de mantenimiento con la cuenta predeterminada.", "config-db-install-account": "Cuenta de usuario para instalación", - "config-db-username": "Nombre de usuario de base de datos:", - "config-db-password": "contraseña de base de datos:", + "config-db-username": "Nombre de usuario de la base de datos:", + "config-db-password": "Contraseña de la base de datos:", "config-db-password-empty": "Introduzca una contraseña para el nuevo usuario de base de datos: $1.\nAunque es posible crear usuarios sin contraseña, esto no es seguro.", "config-db-username-empty": "Debe introducir un valor para \"{{int:config-db-username}}\"", "config-db-install-username": "Introduzca el nombre de usuario que se utilizará para conectarse a la base de datos durante el proceso de instalación.\nEste no es el nombre de usuario de la cuenta de MediaWiki; Este es el nombre de usuario para la base de datos.", "config-db-install-password": "Introduzca la contraseña que se utilizará para conectarse a la base de datos durante el proceso de instalación.\nEsta no es la contraseña para la cuenta de MediaWiki; esta es la contraseña para la base de datos.", "config-db-install-help": "Ingresar el nombre de usuario y la contraseña que será usada para conectar a la base de datos durante el proceso de instalación.", "config-db-account-lock": "Usar el mismo nombre de usuario y contraseña durante operación normal", - "config-db-wiki-account": "Usar cuenta para operación normal", + "config-db-wiki-account": "Cuenta de usuario para operación normal", "config-db-wiki-help": "Introduce el nombre de usuario y la contraseña que serán usados para acceder a la base de datos durante la operación normal del wiki.\nSi esta cuenta no existe y la cuenta de instalación tiene suficientes privilegios, se creará esta cuenta de usuario con los privilegios mínimos necesarios para la operación normal del wiki.", - "config-db-prefix": "Prefijo de tablas de base de datos:", + "config-db-prefix": "Prefijo de tablas de la base de datos:", "config-db-prefix-help": "Si necesita compartir una base de datos entre múltiples wikis, o entre MediaWiki y otra aplicación web, puede optar por agregar un prefijo a todos los nombres de tabla para evitar conflictos.\nNo utilice espacios.\n\nNormalmente se deja este campo vacío.", "config-db-charset": "Conjunto de caracteres de la base de datos", "config-charset-mysql5-binary": "MySQL 4.1/5.0 binario", "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8", "config-charset-mysql4": "MySQL 4.0 retrocompatible UTF-8", "config-charset-help": "'''Atención:''' Si emplea '''backwards-compatible UTF-8''' en MySQL 4.1+ y posteriormente hace copia de seguridad de la base de datos con mysqldump , puede destruir todos los caracteres no-ASCII, ¡dañando irreversiblemente sus copias de seguridad!\n\nEn '''modo binario''', MediaWiki almacena texto UTF-8 en la base de datos en campos binarios.\nEsto es más eficiente que el modo UTF-8 de MySQL, y le permite utilizar la gama completa de caracteres Unicode.\nEn ''' modo UTF-8'' ', MySQL sabrá el juego de caracteres de sus datos y puede presentarlos y convertirlos apropiadamente,\npero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].", - "config-mysql-old": "Se necesita MySQL $1 o una versión más reciente. Tienes la versión $2.", + "config-mysql-old": "Se necesita MySQL $1 o posterior. Tienes $2.", "config-db-port": "Puerto de la base de datos:", "config-db-schema": "Esquema para MediaWiki", "config-db-schema-help": "Estos esquemas usualmente estarán bien.\nAltéralos sólo si tienes la seguridad de que necesitas hacerlo.", @@ -188,9 +190,9 @@ "config-upgrade-done": "Actualización completa.\n\nUsted puede ahora [ $1 empezar a usar su wiki].\n\nSi desea regenerar su archivo LocalSettings.php de archivo, haga clic en el botón de abajo.\nEsto '''no se recomienda''' a menos que esté teniendo problemas con su wiki.", "config-upgrade-done-no-regenerate": "Actualización completa.\n\nUsted puede ahora [$1 empezar a usar su wiki].", "config-regenerate": "Regenerar LocalSettings.php →", - "config-show-table-status": "SHOW TABLE STATUS ha fallado!", + "config-show-table-status": "¡Falló la consulta SHOW TABLE STATUS!", "config-unknown-collation": "'''Advertencia:''' La base de datos está utilizando una intercalación no reconocida.", - "config-db-web-account": "Cuenta de base de datos para acceso Web", + "config-db-web-account": "Cuenta de la base de datos para acceso web", "config-db-web-help": "Elige el usuario y contraseña que el servidor Web usará para conectarse al servidor de la base de datos durante el fincionamiento normal del wiki.", "config-db-web-account-same": "Utilizar la misma cuenta que en la instalación", "config-db-web-create": "Crear la cuenta si no existe", @@ -205,7 +207,7 @@ "config-mysql-binary": "Binario", "config-mysql-utf8": "UTF-8", "config-mysql-charset-help": "En '''modo binario''', MediaWiki almacena texto UTF-8 para la base de datos en campos binarios.\nEsto es más eficiente que el modo UTF-8 de MySQL y le permite utilizar la gama completa de caracteres Unicode.\n\nEn '''modo UTF-8''', MySQL sabrá qué conjunto de caracteres emplean sus datos y puede presentarlos y convertirlos adecuadamente, pero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].", - "config-mssql-auth": "Tipo de autentificación:", + "config-mssql-auth": "Tipo de autenticación:", "config-mssql-install-auth": "Seleccione el tipo de autenticación que se utilizará para conectarse a la base de datos durante el proceso de instalación.\nSi selecciona \"{{int:config-mssql-windowsauth}}\", las credenciales de cualquier usuario de el servidor web que se está ejecutando van a ser utilizadas.", "config-mssql-web-auth": "Seleccione el tipo de autenticación que utilizará el servidor web para conectarse al servidor de base de datos, durante el funcionamiento normal de la wiki.\nSi selecciona \"{{int:config-mssql-windowsauth}}\", las credenciales del usuario que sea cual sea el servidor Web se ejecuta como será utilizado.", "config-mssql-sqlauth": "Autenticación de SQL Server", @@ -215,7 +217,7 @@ "config-site-name-blank": "Ingresar un nombre de sitio.", "config-project-namespace": "Espacio de nombre de proyecto:", "config-ns-generic": "Proyecto", - "config-ns-site-name": "Igual como el nombre del wiki: $1", + "config-ns-site-name": "Igual al nombre del wiki: $1", "config-ns-other": "Otro (especificar)", "config-ns-other-default": "MiWiki", "config-project-namespace-help": "Siguiendo el ejemplo de Wikipedia, muchos wikis mantienen sus páginas de políticas separadas de sus páginas de contenido, en un \"'''espacio de nombres del proyecto'''\".\n\nTodos los títulos de página en este espacio de nombres comienzan con un determinado prefijo, que usted puede especificar aquí.\nTradicionalmente, este prefijo se deriva del nombre del wiki, pero no puede contener caracteres de puntuación como \"#\" o \":\".", @@ -224,7 +226,7 @@ "config-admin-box": "Cuenta de administrador", "config-admin-name": "Tu nombre de usuario:", "config-admin-password": "Contraseña:", - "config-admin-password-confirm": "Repita la contraseña:", + "config-admin-password-confirm": "Repite la contraseña:", "config-admin-help": "Escribe aquí el nombre de usuario que desees, como por ejemplo \"Pedro Bloggs\".\nEste es el nombre que usarás para entrar al wiki.", "config-admin-name-blank": "Introduce un nombre de usuario de administrador.", "config-admin-name-invalid": "El nombre de usuario especificado \"$1\" no es válido.\nEspecifique un nombre de usuario diferente.", @@ -247,20 +249,20 @@ "config-profile-fishbowl": "Sólo editores autorizados", "config-profile-private": "Wiki privado", "config-profile-help": "Los wikis funcionan mejor cuando dejas que los edite tanta gente como sea posible.\nEn MediaWiki, es fácil revisar los cambios recientes y revertir los daños realizados por usuarios malintencionados o novatos.\nSin embargo, muchos han encontrado que MediaWiki es útil para una amplia variedad de funciones, y a veces no es fácil convencer a todos de los beneficios de la forma wiki.\nPor lo tanto tienes la elección.\n\nEl modelo '''{{int:config-profile-wiki}}''' permite que cualquiera pueda editar, sin siquiera iniciar sesión.\nUn wiki con '''{{int:config-profile-no-anon}}''' ofrece rendición de cuentas adicional, pero puede disuadir a colaboradores.\n\nEl modelo '''{{int:config-profile-fishbowl}}''' permite editar a los usuarios autorizados, pero el público puede ver las páginas, incluyendo el historial.\nUn '''{{int:config-profile-private}}''' sólo permite ver páginas a los usuarios autorizados, el mismo grupo al que le está permitido editar.\n\nConfiguraciones más complejas de derechos de usuario están disponibles después de la instalación, consulte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights esta entrada en el manual].", - "config-license": "Copyright and licencia:", + "config-license": "Derechos de autor y licencia:", "config-license-none": "Pie sin licencia", - "config-license-cc-by-sa": "Creative Commons Reconocimiento Compartir Igual", - "config-license-cc-by": "Creative Commons Reconocimiento", - "config-license-cc-by-nc-sa": "Creative Commons Reconocimiento Compartir Igual no comercial", + "config-license-cc-by-sa": "Creative Commons Atribución-CompartirIgual", + "config-license-cc-by": "Creative Commons Atribución", + "config-license-cc-by-nc-sa": "Creative Commons Atribución-NoComercial-CompartirIgual", "config-license-cc-0": "Creative Commons Zero (dominio público)", "config-license-gfdl": "Licencia de documentación libre de GNU 1.3 o posterior", - "config-license-pd": "Dominio Público", + "config-license-pd": "Dominio público", "config-license-cc-choose": "Selecciona una licencia personalizada de Creative Commons", "config-license-help": "Muchos wikis públicos ponen todas las contribuciones bajo una [http://freedomdefined.org/Definition licencia libre].\nEsto ayuda a crear un sentido de propiedad comunitaria y alienta la contribución a largo plazo.\nEsto no es generalmente necesario para un wiki privado o corporativo.\n\nSi deseas poder utilizar texto de Wikipedia, y deseas que Wikipedia pueda aceptar el texto copiado de tu wiki, debes elegir {{int:config-license-cc-by-sa}}.\n\nWikipedia utilizaba anteriormente la licencia de documentación libre de GNU (GFDL).\nLa GFDL es una licencia válida, pero es difícil de entender.\nTambién es difícil reutilizar el contenido licenciado bajo la GFDL.", "config-email-settings": "Configuración de correo electrónico", "config-enable-email": "Activar el envío de correos electrónicos", "config-enable-email-help": "Si quieres que el correo electrónico funcione, la [http://www.php.net/manual/en/mail.configuration.php configuración PHP de correo electrónico] debe ser la correcta.\nSi no quieres la funcionalidad de correo electrónico, puedes desactivarla aquí.", - "config-email-user": "Habilitar correo electrónico de usuario a usuario", + "config-email-user": "Habilitar correo electrónico entre usuarios", "config-email-user-help": "Permitir que todos los usuarios intercambien correos electrónicos si lo han activado en sus preferencias.", "config-email-usertalk": "Activar notificaciones de páginas de discusión de usuarios", "config-email-usertalk-help": "Permitir a los usuarios recibir notificaciones de cambios en la página de discusión de usuario, si lo han activado en sus preferencias.", @@ -319,7 +321,7 @@ "config-install-user": "Creando el usuario de la base de datos", "config-install-user-alreadyexists": "El usuario \"$1\" ya existe", "config-install-user-create-failed": "La creación del usuario \"$1\" falló: $2", - "config-install-user-grant-failed": "La concesión de permisos para el usuario \"$1\" ha fallado: $2", + "config-install-user-grant-failed": "La concesión de permisos al usuario \"$1\" falló: $2", "config-install-user-missing": "El usuario especificado \"$1\" no existe.", "config-install-user-missing-create": "El usuario especificado \"$1\" no existe.\nPor favor, haga clic en la casilla \"Crear cuenta\" que aparece a continuación si desea crearlo.", "config-install-tables": "Creando tablas", @@ -329,9 +331,9 @@ "config-install-interwiki-list": "No se pudo encontrar el archivo interwiki.list.", "config-install-interwiki-exists": "'''Advertencia''': La tabla de interwikis parece ya contener entradas.\nSe omitirá la lista predeterminada.", "config-install-stats": "Iniciando las estadísticas", - "config-install-keys": "Generación de claves secretas", + "config-install-keys": "Generando claves secretas", "config-insecure-keys": "''' Atención:'' ' {{PLURAL:$2|Una clave de seguridad generada|Las claves de seguridad generadas}} ($1) durante la instalación no {{PLURAL:$2|es totalmente segura|son totalmente seguras}}. Considere {{PLURAL:$2| cambiarla|cambiarlas}} manualmente.", - "config-install-sysop": "Creando cuenta de usuario del administrador", + "config-install-sysop": "Creando la cuenta de usuario del administrador", "config-install-subscribe-fail": "No se ha podido suscribir a mediawiki-announce: $1", "config-install-subscribe-notpossible": "cURL no está instalado y allow_url_fopen no está disponible.", "config-install-mainpage": "Creando página principal con contenido predeterminado", @@ -339,10 +341,10 @@ "config-install-mainpage-failed": "No se pudo insertar la página principal: $1", "config-install-done": "¡Felicidades!\nHas instalado MediaWiki correctamente.\n\nEl instalador ha generado un archivo LocalSettings.php.\nEste contiene toda su configuración.\n\nDeberás descargarlo y ponerlo en la base de la instalación de wiki (el mismo directorio que index.php). La descarga debería haber comenzado automáticamente.\n\nSi no comenzó la descarga, o si se ha cancelado, puedes reiniciar la descarga haciendo clic en el siguiente enlace:\n\n$3\n\nNota: Si no haces esto ahora, este archivo de configuración generado no estará disponible más tarde si sales de la instalación sin descargarlo.\n\nCuando lo hayas hecho, podrás [$2 entrar en tu wiki].", "config-download-localsettings": "Descargar archivo LocalSettings.php", - "config-help": "Ayuda", - "config-help-tooltip": "Haz clic para ampliar", + "config-help": "ayuda", + "config-help-tooltip": "haz clic para ampliar", "config-nofile": "El archivo \"$1\" no se pudo encontrar. ¿Se ha eliminado?", "config-extension-link": "¿Sabías que tu wiki admite [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensiones]?\n\nPuedes navegar por las [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category categorías] o visitar el [//www.mediawiki.org/wiki/Extension_Matrix centro de extensiones] para ver una lista completa.", - "mainpagetext": "'''MediaWiki ha sido instalado con éxito.'''", + "mainpagetext": "MediaWiki se ha instalado con éxito.", "mainpagedocfooter": "Consulta la [//meta.wikimedia.org/wiki/Help:Contents/es guía del usuario] para obtener información sobre el uso del software wiki.\n\n== Primeros pasos ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de ajustes de configuración]\n* [//www.mediawiki.org/wiki/Manual:FAQ/es Preguntas frecuentes sobre MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de publicación de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traducir MediaWiki en tu idioma]" } diff --git a/includes/installer/i18n/et.json b/includes/installer/i18n/et.json index 13b61ed0b5..8fae026e6a 100644 --- a/includes/installer/i18n/et.json +++ b/includes/installer/i18n/et.json @@ -2,7 +2,8 @@ "@metadata": { "authors": [ "Avjoska", - "Pikne" + "Pikne", + "Boxmein" ] }, "config-information": "Teave", @@ -26,6 +27,7 @@ "config-page-upgradedoc": "Uuendamine", "config-page-existingwiki": "Olemasolev viki", "config-restart": "Jah, tee taaskäivitus", + "config-env-hhvm": "HHVM $1 on installitud.", "config-db-name": "Andmebaasi nimi:", "config-db-username": "Andmebaasi kasutajanimi:", "config-db-password": "Andmebaasi parool:", diff --git a/includes/installer/i18n/fa.json b/includes/installer/i18n/fa.json index d619cff2f6..02ffd0d1ca 100644 --- a/includes/installer/i18n/fa.json +++ b/includes/installer/i18n/fa.json @@ -6,7 +6,8 @@ "Ebraminio", "Omidh", "Pouyana", - "Reza1615" + "Reza1615", + "Alirezaaa" ] }, "config-desc": "نصب کنندهٔ ویکی‌مدیا", @@ -19,7 +20,7 @@ "config-upgrade-key-missing": "نصب موجود مدیاویکی شناسایی شده‌است.\nبرای بروزرسانی این نصب، لطفاً خط زیر را در آخر کد \nقرار دادن به نصب ارتقاء داده شده، به خط زیر لطفاً در پایین خود را LocalSettings.php قرار دهید:\n\n$1", "config-localsettings-incomplete": "وجود LocalSettings.php به نظر ناقص می‌رسد.\nمتغیر $1 تنظیم نشده‌است.\nبرای اینکه این متغیر تنظیم شود لطفاً LocalSettings.php را تغییر دهید، و \"{{int:Config-continue}}\" را کلیک کنید.", "config-localsettings-connection-error": "هنگام اتصال به پایگاه اطلاعاتی که ازتنظیمات مشخص شده درLocalSettings.php استفاده می‌کند، خطایی رخ داد. لطفاً این تنظیمات را نصب کنید و دوباره تلاش کنید.\n$1", - "config-session-error": "خطا در شروع جلسه $1", + "config-session-error": "خطا در شروع جلسه: $1", "config-session-expired": "به نظر می‌رسد اطلاعات جلسهٔ شما منقضی شده‌است.\nجلسات برای مادام العمر $1 پیکربندی شده‌اند.\nشما می‌توانید این پیکربندی را با تنظیم session.gc_maxlifetime در php.ini افزایش دهید.\nروند نصب را دوباره شروع کنید.", "config-no-session": "اطلاعات دورهٔ شما از دست رفته‌ است!\nphp.ini خود را بررسی کنید و مطمئن شوید session.save_path برای یک فهرست مناسب تنظیم شده‌است.", "config-your-language": "زبان شما:", @@ -31,26 +32,27 @@ "config-page-language": "زبان", "config-page-welcome": "به مدیاویکی خوش آمدید!", "config-page-dbconnect": "اتصال به پایگاه داده", - "config-page-upgrade": "نصب موجود را ارتقاء دهید.", - "config-page-dbsettings": "تنظیمات پایگاه اطلاعاتی", + "config-page-upgrade": "ارتقای نصب موجود", + "config-page-dbsettings": "تنظیمات پایگاه داده", "config-page-name": "نام", "config-page-options": "گزینه‌ها", "config-page-install": "نصب", "config-page-complete": "کامل!", - "config-page-restart": "نصب را دوباره شروع کنید", + "config-page-restart": "راه‌اندازی دوباره نصب", "config-page-readme": "مرا بخوان", "config-page-releasenotes": "یادداشت‌های انتشار", "config-page-copying": "تکثیر", - "config-page-upgradedoc": "ارتقا", + "config-page-upgradedoc": "ارتقاء", "config-page-existingwiki": "ویکی موجود", "config-help-restart": "آیا می‌خواهید همهٔ اطلاعات ذخیره شده‌ای که وارد کرده‌اید را پاک کنید و دوباره روند نصب را شروع کنید؟", - "config-restart": "بله ، آن دوباره راه اندازی کن", + "config-restart": "بله، دوباره راه‌اندازی کن", "config-welcome": "===بررسی‌های محیطی===\nبرای فهمیدن اینکه این محیط برای نصب مدیاویکی مناسب است، اکنون بررسی‌های اساسی انجام خواهد‌شد.\nاگر به دنبال پشتیبانی در چگونگی تکمیل نصب هستید،به یاد داشته باشید این اطلاعات را بگنجانید.", "config-copyright": "===حق چاپ و شرایط===\n$1\nاین برنامه،نرم‌افزاری آزاد است;شما می‌توانید این برنامه را دوباره توزیع کنید و/یا تحت شرایط مجوز عمومی کلی جی‌ان‌یو که توسط بنیاد نرم‌افزار آزاد منتشر شده،اصلاح کنید;یا نسخهٔ 2 مجوز، یا (به انتخاب خود) هر نسخهٔ پس از این.\nاین برنامه به امید اینکه مفید واقع‌ شود توزیع شده‌است،اما '''بدون هیچ ضمانتی'''; حتی بدون اشارهٔ ضمانتی از '''قابلیت عرضه''' یا ''' صلاحیت برای یک هدف خاص'''.\nبرای جزئیات بیشتر مجوز عمومی کلی جی‌ان‌یو را مشاهده کنید.\nشما باید یک چاپ ازمجوز عمومی کلی همراه این برنامه دریافت کنید; اگر دریافت نکردید،به بنیاد نرم‌افزار آزاد بنویسید،Inc.،خیابان فرانکلین۵۱،طبقه پنجم،بوستون، MA۰۲۱۱۰-۱۳۰،آمریکا،یا [http://www.gnu.org/copyleft/gpl.html read it online].", "config-sidebar": "* [//www.mediawiki.org صفحهٔ اصلی مدیاویکی]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents راهنمای کاربر]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents راهنمای مدیر]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسش‌های رایج]\n----\n* مرا بخوان\n* یادداشت‌های انتشار\n* نسخه برداری\n* ارتقا", "config-env-good": "محیط بررسی شده‌است.\nشما می‌توانید مدیاویکی را نصب کنید.", "config-env-bad": "محیط بررسی شده‌است.\nشما نمی‌توانید مدیاویکی را نصب کنید.", "config-env-php": "پی‌اچ‌پی $1 نصب شده‌است.", + "config-env-hhvm": "HHVM $1 نصب شده‌است.", "config-unicode-using-utf8": "برای یونیکد عادی از Brion Vibber's utf8_normalize.so استفاده کنید.", "config-unicode-using-intl": "برای یونیکد عادی از [http://pecl.php.net/intl intl PECL extension] استفاده کنید.", "config-unicode-pure-php-warning": "'''هشدار:''' [http://pecl.php.net/intl intl PECL extension] برای کنترل یونیکد عادی در دسترس نیست،اجرای کاملاً آهسته به تعویق می‌افتد.\nاگر شما یک سایت پر‌ ترافیک را اجرا می‌کنید، باید کمی [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization] را بخوانید.", @@ -78,8 +80,8 @@ "config-no-cache": "'''هشدار:''' [http://www.php.net/apc APC],[http://xcache.lighttpd.net/ XCache] یا [http://www.iis.net/download/WinCacheForPhp WinCache] را نتوانست پیدا کند.\nذخیره شی فعال نیست.", "config-mod-security": "'''هشدار:''' وب سرور شما [http://modsecurity.org/ mod_security] فعال است.اگر اشتباه پیکربندی شده‌‌ باشد،می تواند باعث ایجاد مشکلاتی برای مدیاویکی یا دیگر نرم‌افزاری شود که به کاربران اجازه می‌دهد پیام دلخواه ارسال کنند.\nبه [http://modsecurity.org/documentation/ mod_security documentation] مراجعه کنید یا اگر با خطاهای اتفاقی مواجه شدید با پشتیبانی میزبان خود در تماس باشید.", "config-diff3-bad": "جی‌ان‌یو دیف۳ پیدا نشد.", - "config-git": "نسخهٔ کنترل نرم‌افزار جیت پیدا شد:$1.", - "config-git-bad": "نسخه نرم‌افزار کنترل جیت پیدا نشد.", + "config-git": "کنترل نسخهٔ نرم‌افزار گیت پیدا شد: $1.", + "config-git-bad": "کنترل نسخهٔ نرم‌افزار گیت پیدا نشد.", "config-imagemagick": "ایمیج‌مجیک پیدا شد: $1.\nاگر ارسال‌ها را فعال کنید،تصویر کوچک فعال خواهد‌شد.", "config-gd": "گرافیک‌های جی‌دی ساخته‌‌ شده در کتابخانه پیدا شد.\nاگر ارسال‌ها را فعال کنید تصویر کوچک فعال خواهد‌شد.", "config-no-scaling": "کتابخانهٔ جی‌دی یا ایمیج‌مجیک نتوانست پیدا شود.\nتصویر کوچک غیر‌فعال خواهد‌شد.", @@ -104,7 +106,7 @@ "config-db-install-account": "حساب کاربری برای نصب", "config-db-username": "نام کاربری پایگاه اطلاعات:", "config-db-password": "گذرواژه پایگاه داده‌ها:", - "config-db-password-empty": "لطفاً یک رمز عبور برای کاربر جدید پایگاه اطلا‌عاتی وارد کنید: $1\nدر صورتی که ممکن است کاربران بدون رمز عبور به وجود آیند،امن نیست.", + "config-db-password-empty": "لطفاً یک رمز عبور برای کاربر تازه پایگاه اطلا‌عاتی وارد کنید: $1\nدر صورتی که ممکن است کاربران بدون رمز عبور به وجود آیند،امن نیست.", "config-db-username-empty": "شما باید یک مقدار برای \"نام کاربری {{int:config-db-username}}\" وارد کنید", "config-db-install-username": "نام کاربری را وارد کنید که برای اتصال به پایگاه اطلاعاتی در طول روند نصب استفاده خواهد‌شد.\nاین نام کاربری حساب مدیاویکی نیست; نام کاربری برای پایگاه اطلاعاتی شما است.", "config-db-install-password": "رمز عبوری را وارد کنید که برای اتصال به پایگاه اطلاعاتی در طول روند نصب استفاده خواهد‌شد.\nاین رمز عبور برای حساب مدیاویکی نیست;رمز عبور برای پایگاه اطلاعاتی شما است.", @@ -310,6 +312,8 @@ "config-install-stats": "شروع آمار", "config-install-keys": "تولید کلیدهای مخفی", "config-insecure-keys": "'''هشدار:''' {{PLURAL:$2|کلید امن|کلیدهای امن}} ($1) در طی نصب کاملاً ایمن {{PLURAL:$2|نیست|نیستند}}. تغییر دستی {{PLURAL:$2|آن|آنها}} را در نظر بگیرید.", + "config-install-updates": "جلوگیری از به روز رسانی‌های غیر ضروری در حال اجرا", + "config-install-updates-failed": "خطا: قراردادن کلیدهای به روز رسانی به داخل جداول با خطای روبرو مواجه شد: $1", "config-install-sysop": "ایجاد حساب کاربری مدیر", "config-install-subscribe-fail": "قادر تصدیق اعلام مدیاویکی نیست:$1", "config-install-subscribe-notpossible": "سی‌یوآر‌ال نصب نشده‌است و allow_url_fopen در دسترس نیست.", diff --git a/includes/installer/i18n/fr.json b/includes/installer/i18n/fr.json index 57d7444d4a..9fd6726e9a 100644 --- a/includes/installer/i18n/fr.json +++ b/includes/installer/i18n/fr.json @@ -66,6 +66,7 @@ "config-env-good": "L’environnement a été vérifié.\nVous pouvez installer MediaWiki.", "config-env-bad": "L’environnement a été vérifié.\nVous ne pouvez pas installer MediaWiki.", "config-env-php": "PHP $1 est installé.", + "config-env-hhvm": "HHVM $1 est installé.", "config-unicode-using-utf8": "Utilisation de utf8_normalize.so par Brion Vibber pour la normalisation Unicode.", "config-unicode-using-intl": "Utilisation de [http://pecl.php.net/intl l'extension PECL intl] pour la normalisation Unicode.", "config-unicode-pure-php-warning": "Attention : L'[http://pecl.php.net/intl extension PECL intl] n'est pas disponible pour la normalisation d’Unicode, retour à la version lente implémentée en PHP.\nSi votre site web sera très fréquenté, vous devriez lire ceci : [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).", @@ -328,6 +329,8 @@ "config-install-stats": "Initialisation des statistiques", "config-install-keys": "Génération de la clé secrète", "config-insecure-keys": "'''Avertissement''' : {{PLURAL:$2|Une clé de sécurité générée ($1) pendant l'installation n'est pas complètement sécuritaire. Envisagez de la modifier manuellement.|Des clés de sécurité générées ($1) pendant l'installation ne sont pas complètement sécuritaires. Envisagez de les modifier manuellement.}}", + "config-install-updates": "Empêcher l’exécution des mises à jour inutiles", + "config-install-updates-failed": "Erreur : L’insertion de clés modifiées dans les tables a échoué avec l’erreur suivante : $1", "config-install-sysop": "Création du compte administrateur", "config-install-subscribe-fail": "Impossible de s'abonner à mediawiki-announce : $1", "config-install-subscribe-notpossible": "cURL n’est pas installé et allow_url_fopen n’est pas disponible.", diff --git a/includes/installer/i18n/he.json b/includes/installer/i18n/he.json index 57e3c94c78..dd14e6335c 100644 --- a/includes/installer/i18n/he.json +++ b/includes/installer/i18n/he.json @@ -51,6 +51,7 @@ "config-env-good": "הסביבה שלכם נבדקה.\nאפשר להתקין מדיה־ויקי.", "config-env-bad": "הסביבה שלכם נבדקה.\nאי־אפשר להתקין מדיה־ויקי.", "config-env-php": "מותקנת PHP $1.", + "config-env-hhvm": "מותקנת HHVM $1.", "config-unicode-using-utf8": "משתמש ב־utf8_normalize.so של בריון ויבר לנרמול יוניקוד.", "config-unicode-using-intl": "משתמש ב[http://pecl.php.net/intl הרחבת intl PECL] לנרמול יוניקוד.", "config-unicode-pure-php-warning": "'''אזהרה''': [http://pecl.php.net/intl הרחבת intl PECL] אינה זמינה לטיפול בנרמול יוניקוד. משתמש ביישום PHP טהור ואטי יותר.\nאם זהו אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].", @@ -310,6 +311,8 @@ "config-install-stats": "אתחול סטטיסטיקות", "config-install-keys": "יצירת מפתחות סודיים", "config-insecure-keys": "'''אזהרה''': {{PLURAL:$2|מפתח|מפתחות}} אבטחה ($1) {{PLURAL:$2|שנוצר|שנוצרו}} במהלך ההתקנה {{PLURAL:$2|אינו בטוח|אינם בטוחים}} מספיק. מומלץ לשקול לשנות {{PLURAL:$2|אותו|אותם}} ידנית.", + "config-install-updates": "למנוע הרצת עדכונים מיותרים", + "config-install-updates-failed": "שגיאה: הוספת מפתחות עדכון לטבלאות נכשל עם השגיאה הבאה: $1", "config-install-sysop": "יצירת חשבון מפעיל", "config-install-subscribe-fail": "הרישום ל־mediawiki-announce לא הצליח: $1", "config-install-subscribe-notpossible": "cURL אינה מותקנת ו־allow_url_fopen אינה זמינה.", diff --git a/includes/installer/i18n/it.json b/includes/installer/i18n/it.json index 908d282e0d..faaf3355f6 100644 --- a/includes/installer/i18n/it.json +++ b/includes/installer/i18n/it.json @@ -55,6 +55,7 @@ "config-env-good": "L'ambiente è stato controllato.\nÈ possibile installare MediaWiki.", "config-env-bad": "L'ambiente è stato controllato.\nNon è possibile installare MediaWiki.", "config-env-php": "PHP $1 è installato.", + "config-env-hhvm": "HHVM $1 è installato.", "config-unicode-using-utf8": "Usa Brion Vibber's utf8_normalize.so per la normalizzazione Unicode.", "config-unicode-using-intl": "Usa [http://pecl.php.net/intl l'estensione PECL intl] per la normalizzazione Unicode.", "config-unicode-pure-php-warning": "'''Attenzione:''' [http://pecl.php.net/intl l'estensione PECL intl] non è disponibile per gestire la normalizzazione Unicode, così si usa la lenta implementazione in puro PHP.\nSe esegui un sito ad alto traffico, dovresti leggere alcune considerazioni sulla [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizzazione Unicode].", @@ -191,7 +192,7 @@ "config-admin-name-invalid": "Il nome utente specificato \"$1\" non è valido.\nSpecificare un nome utente diverso.", "config-admin-password-blank": "Inserisci una password per l'account di amministratore.", "config-admin-password-mismatch": "Le password inserite non coincidono tra loro.", - "config-admin-email": "Indirizzo e-mail:", + "config-admin-email": "Indirizzo email:", "config-admin-email-help": "Inserisci qui un indirizzo email per poter ricevere email dagli altri utenti del wiki, reimpostare la tua password, ed essere informato delle modifiche apportate alle pagine tuoi osservati speciali. Se non ti interessa, puoi lasciare vuoto questo campo.", "config-admin-error-user": "Errore interno durante la creazione di un amministratore con il nome \"$1\".", "config-admin-error-password": "Errore interno durante l'impostazione di una password per amministratore \"$1\":
    $2
    ", diff --git a/includes/installer/i18n/ja.json b/includes/installer/i18n/ja.json index 39ebf877cf..21fcb9d7ff 100644 --- a/includes/installer/i18n/ja.json +++ b/includes/installer/i18n/ja.json @@ -58,6 +58,7 @@ "config-env-good": "環境を確認しました。\nMediaWiki をインストールできます。", "config-env-bad": "環境を確認しました。\nMediaWiki のインストールはできません。", "config-env-php": "PHP $1がインストールされています。", + "config-env-hhvm": "HHVM $1 がインストールされています。", "config-unicode-using-utf8": "Unicode正規化に、Brion Vibberのutf8_normalize.soを使用。", "config-unicode-using-intl": "Unicode正規化に[http://pecl.php.net/intl intl PECL 拡張機能]を使用。", "config-unicode-pure-php-warning": "警告: Unicode 正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]を利用できないため、処理が遅いピュア PHP の実装を代わりに使用しています。\n高トラフィックのサイトを運営する場合は、[//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode 正規化]をお読みください。", diff --git a/includes/installer/i18n/lb.json b/includes/installer/i18n/lb.json index 994144200f..4d73ca0252 100644 --- a/includes/installer/i18n/lb.json +++ b/includes/installer/i18n/lb.json @@ -44,6 +44,7 @@ "config-env-good": "Den Environement gouf nogekuckt.\nDir kënnt MediaWiki installéieren.", "config-env-bad": "Den Environnement gouf iwwerpréift.\nDir kënnt MediWiki net installéieren.", "config-env-php": "PHP $1 ass installéiert.", + "config-env-hhvm": "HHVM $1 ass installéiert.", "config-unicode-using-utf8": "Fir d'Unicode-Normalisatioun gëtt dem Brion Vibber säin utf8_normalize.so benotzt.", "config-no-db": "Et konnt kee passenden Datebank-Driver fonnt ginn! Dir musst een Datebank-Driver fir PHP installéieren.\nDës Datebank-Type ginn ënnerstëtzt: $1.\n\nWann Dir PHP selwer compiléiert hutt, da rekonfiguréiert en mat dem ageschalten Datebank-Client, zum Beispill an deem Dir ./configure --with-mysql benotzt.\nWann Dir PHP vun engem Debian oder Ubuntu Package aus installéiert hutt, da musst Dir och den php5-mysql Modul installéieren.", "config-outdated-sqlite": "'''Warnung:''' SQLite $1 ass installéiert. Allerdengs brauch MediaWiki SQLite $2 oder méi nei. SQLite ass dofir net disponibel.", @@ -100,6 +101,7 @@ "config-missing-db-name": "Dir musst e Wäert fir \"{{int:config-db-name}}\" aginn", "config-missing-db-host": "Dir musst e Wäert fir \"{{int:config-db-host}}\" aginn.", "config-missing-db-server-oracle": "Dir musst e Wäert fir \"{{int:config-db-host-oracle}}\" aginn", + "config-connection-error": "$1.\n\nKuckt den Numm vum Server, de Benotzernumm an d'Passwuert no a probéiert et nach eng Kéier.", "config-db-sys-user-exists-oracle": "De Benotzerkont \"$1\" gëtt et schonn. SYSDBA kann nëmme benotzt gi fir en neie Benotzerkont opzemaachen.", "config-postgres-old": "PostgreSQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.", "config-mssql-old": "Microsoft SQL Server $1 oder eng méi rezent Versioun gëtt gebraucht. Dir hutt d'Versioun $2.", diff --git a/includes/installer/i18n/mk.json b/includes/installer/i18n/mk.json index 74359b2476..8784521e16 100644 --- a/includes/installer/i18n/mk.json +++ b/includes/installer/i18n/mk.json @@ -47,6 +47,7 @@ "config-env-good": "Околината е проверена.\nМожете да го воспоставите МедијаВики.", "config-env-bad": "Околината е проверена.\nНе можете да го воспоставите МедијаВики.", "config-env-php": "PHP $1 е воспоставен.", + "config-env-hhvm": "HHVM $1 е воспоставен.", "config-unicode-using-utf8": "Со utf8_normalize.so за уникодна нормализација од Брајон Вибер (Brion Vibber).", "config-unicode-using-intl": "Со додатокот [http://pecl.php.net/intl intl PECL] за уникодна нормализација.", "config-unicode-pure-php-warning": "'''Предупредување''': Додатокот [http://pecl.php.net/intl intl PECL] не е достапен за врши уникодна нормализација, враќајќи се на бавна примена на чист PHP.\n\nАко имате високопрометно мрежно место, тогаш ќе треба да прочитате повеќе за [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations уникодната нормализација].", @@ -309,6 +310,8 @@ "config-install-stats": "Ги подготвувам статистиките", "config-install-keys": "Создавање на тајни клучеви", "config-insecure-keys": "'''Предупредување:''' {{PLURAL:$2|Безбедносниот клуч $1 создаден во текот на воспоставката не е сосем безбеден|Безбедносните клучеви $1 создадени во текот на воспоставката не се сосем безбедни}}. Ви препорачуваме да {{PLURAL:$2|го|ги}} смените рачно.", + "config-install-updates": "Спречи вршење на непотребни поднови", + "config-install-updates-failed": "Грешка: Вметнувањето на подновни клучеви во табелите не успеа, со следнава грешка: $1", "config-install-sysop": "Создавање на администраторска корисничка сметка", "config-install-subscribe-fail": "Не можам да ве претплатам на известувањето mediawiki-announce: $1", "config-install-subscribe-notpossible": "cURL не е воспоставен, а allow_url_fopen не е достапно.", diff --git a/includes/installer/i18n/ms.json b/includes/installer/i18n/ms.json index 057410b65e..f9bcb9134b 100644 --- a/includes/installer/i18n/ms.json +++ b/includes/installer/i18n/ms.json @@ -135,9 +135,15 @@ "config-install-step-done": "siap", "config-install-step-failed": "gagal", "config-install-user-alreadyexists": "Pengguna \"$1\" sudah wujud", + "config-install-tables": "Mencipta jadual", "config-install-tables-exist": "Amaran: Nampaknya sudah terdapat jadual MediaWiki. Penciptaan dilangkau.", + "config-install-interwiki": "Mengisi jadual antara wiki lalai", + "config-install-interwiki-list": "Fail interwiki.list tidak dapat dibaca.", "config-install-interwiki-exists": "Amaran: Jadual antara wiki nampaknya sudah ada entri. Senarai asali dilangkau.", + "config-install-keys": "Menjana kunci-kunci rahsia", "config-insecure-keys": "Amaran: {{PLURAL:$2|Kunci keselamatan|Kunci-kunci keselamatan}} ($1) yang dihasilkan sewaktu pemasangan itu {{PLURAL:$2|adalah}} tidak selamat sepenuhnya. Oleh itu, {{PLURAL:$2|ia}} wajar ditukar secara manual.", + "config-install-sysop": "Membuka akaun pengguna pentadbir", + "config-install-mainpage": "Mewujudkan laman utama dengan kandungan lalai", "config-help": "bantuan", "mainpagetext": "'''MediaWiki telah berjaya dipasang.'''", "mainpagedocfooter": "Sila rujuk [//meta.wikimedia.org/wiki/Help:Contents Panduan Penggunaan] untuk maklumat mengenai penggunaan perisian wiki ini.\n\n== Permulaan ==\n\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Senarai tetapan konfigurasi]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Soalan Lazim MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai surat keluaran MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Terjemahkan MediaWiki ke dalam bahasa anda]" diff --git a/includes/installer/i18n/mt.json b/includes/installer/i18n/mt.json index 0a84d8734b..262a83782f 100644 --- a/includes/installer/i18n/mt.json +++ b/includes/installer/i18n/mt.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": [ - "Chrisportelli" + "Chrisportelli", + "Leli Forte" ] }, "config-title": "Installazzjoni ta' MediaWiki $1", @@ -30,6 +31,7 @@ "config-page-existingwiki": "Wiki eżistenti", "config-restart": "Iva, erÄ¡a' ibda", "config-env-php": "PHP $1 huwa installat.", + "config-env-hhvm": "HHVM $1 hu installat.", "config-db-wiki-settings": "Identifika din il-wiki", "config-db-name": "Isem tad-databażi:", "config-db-install-account": "Kont tal-utent għall-installazzjoni", @@ -46,13 +48,13 @@ "config-site-name": "Isem tal-wiki:", "config-site-name-help": "Dan se jidher fil-barra tat-titlu tal-browżer u f'diversi postijiet oħra.", "config-site-name-blank": "Daħħal isem tas-sit.", - "config-project-namespace": "Spazju tal-isem tal-proÄ¡ett:", + "config-project-namespace": "Spazju tal-ismijiet tal-proÄ¡ett:", "config-ns-generic": "ProÄ¡ett", "config-ns-site-name": "L-istess bħall-isem tal-wiki: $1", "config-ns-other": "Oħrajn (speċifika)", "config-ns-other-default": "MyWiki", - "config-ns-invalid": "L-ispazju speċifikat \"$1\" huwa ħażin.\nSpeċifika spazju tal-isem tal-proÄ¡ett differenti.", - "config-ns-conflict": "L-ispazju speċifikat \"$1\" joħloq kunflitt ma' spazju tal-isem ieħor tal-MediaWiki.\nSpeċifika spazju tal-isem tal-proÄ¡ett differenti.", + "config-ns-invalid": "L-ispazju speċifikat \"$1\" huwa ħażin.\nSpeċifika spazju tal-ismijiet ta' proÄ¡ett differenti.", + "config-ns-conflict": "L-ispazju speċifikat \"$1\" joħloq kunflitt ma' spazju tal-ismijiet tal-MediaWiki predeterminat.\nSpeċifika spazju tal-ismijiet ta' proÄ¡ett differenti.", "config-admin-box": "Kont tal-amministratur", "config-admin-name": "Ismek:", "config-admin-password": "Password:", diff --git a/includes/installer/i18n/nb.json b/includes/installer/i18n/nb.json index 93ddfa4c62..f3cf645495 100644 --- a/includes/installer/i18n/nb.json +++ b/includes/installer/i18n/nb.json @@ -50,6 +50,7 @@ "config-env-good": "Miljøet har blitt sjekket.\nDu kan installere MediaWiki.", "config-env-bad": "Miljøet har blitt sjekket.\nDu kan installere MediaWiki.", "config-env-php": "PHP $1 er innstallert.", + "config-env-hhvm": "HHVM $1 er installert.", "config-unicode-using-utf8": "Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.", "config-unicode-using-intl": "Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.", "config-unicode-pure-php-warning": "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for Ã¥ hÃ¥ndtere Unicode-normaliseringen, faller tilbake til en langsommere ren-PHP-implementasjon.\nOm du kjører et nettsted med høy trafikk bør du lese litt om [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-normalisering].", @@ -58,6 +59,7 @@ "config-outdated-sqlite": "'''Advarsel''': Du har SQLite $1, som er en eldre versjon enn minimumskravet SQLite $2. SQLite vil ikke være tilgjengelig.", "config-no-fts3": "'''Advarsel''': SQLite er kompilert uten [//sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig pÃ¥ dette bakstykket.", "config-register-globals-error": "Feil: PHPs [http://php.net/register_globals register_globals]-valg er aktivt.\nDet mÃ¥ deaktiveres for Ã¥ kunne fortsette med installeringen.\nSe [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] for Ã¥ fÃ¥ hjelp til Ã¥ gjøre dette.", + "config-magic-quotes-gpc": "Fatalt: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] er aktiv!\nDette valget kan ødelegge inndata pÃ¥ en uforutsigelig mÃ¥te.\nDu kan ikke installere eller bruke MediaWiki uten at denne valgmuligheten er slÃ¥tt av.", "config-magic-quotes-runtime": "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] er aktiv!'''\nDette alternativet ødelegger inndata pÃ¥ en uforutsigbar mÃ¥te.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.", "config-magic-quotes-sybase": "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''\nDette alternativet ødelegger inndata pÃ¥ en uforutsigbar mÃ¥te.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.", "config-mbstring": "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''\nDette alternativet fører til feil og kan ødelegge data pÃ¥ en uforutsigbar mÃ¥te.\nDu kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.", @@ -68,6 +70,7 @@ "config-memory-raised": "PHPs memory_limit er $1, økt til $2.", "config-memory-bad": "'''Advarsel:''' PHPs memory_limit er $1.\nDette er sannsynligvis for lavt.\nInstallasjonen kan mislykkes!", "config-ctype": "'''Fatal feil''': PHP mÃ¥ kompileres med støtte for [http://www.php.net/manual/en/ctype.installation.php Ctype-utvidelsen].", + "config-iconv": "Kritisk: PHP mÃ¥ kompileres med støtte for [http://www.php.net/manual/en/iconv.installation.php iconv-utvidelsen].", "config-json": "'''Alvorlig:''' PHP ble kompilert uten JSON-støtte.\nDu mÃ¥ installere enten PHP JSON-utvidelsen eller [http://pecl.php.net/package/jsonc PECL jsonc]-utvidelsen før du installere MediaWiki.\n* PHP-utvidelsen inngÃ¥r i Red Hat Enterprise Linux (CentOS) 5 and 6, men mÃ¥ aktiveres i /etc/php.ini eller /etc/php.d/json.ini.\n* Noen Linux-distribusjoner sluppet etter mai 2013 har ikke med PHP-utvidelsen, men har i stedet med PECL-utvidelsen php5-json eller php-pecl-jsonc.", "config-xcache": "[http://xcache.lighttpd.net/ XCache] er innstallert", "config-apc": "[http://www.php.net/apc APC] er innstallert", diff --git a/includes/installer/i18n/oc.json b/includes/installer/i18n/oc.json index e4bcb6e221..372058f8d8 100644 --- a/includes/installer/i18n/oc.json +++ b/includes/installer/i18n/oc.json @@ -37,7 +37,7 @@ "config-env-good": "L’environament es estat verificat.\nPodètz installar MediaWiki.", "config-env-bad": "L’environament es estat verificat.\nPodètz pas installar MediaWiki.", "config-env-php": "PHP $1 es installat.", - "config-env-php-toolow": "PHP $1 es installat.\nPasmens, MediaWiki requerís PHP $2 o superior.", + "config-env-hhvm": "HHVM $1 es installat.", "config-unicode-using-utf8": "Utilizacion de utf8_normalize.so per Brion Vibber per la normalizacion Unicode.", "config-unicode-using-intl": "Utilizacion de [http://pecl.php.net/intl l'extension PECL intl] per la normalizacion Unicode.", "config-memory-raised": "Lo paramètre memory_limit de PHP èra a $1, portat a $2.", @@ -153,20 +153,29 @@ "config-cache-options": "Paramètres per la mesa en escondedor dels objèctes :", "config-memcached-servers": "servidors per Memcached :", "config-extensions": "Extensions", + "config-skins": "Abilhatges", + "config-skins-use-as-default": "Utilizar aqueste abilhatge per defaut", "config-install-step-done": "fach", "config-install-step-failed": "fracàs", "config-install-extensions": "Inclusion de las extensions", "config-install-database": "Creacion de la banca de donadas", "config-install-schema": "Creacion d'esquèma", + "config-install-pg-schema-not-exist": "L'esquèma PostgreSQL existís pas", "config-install-pg-commit": "Validacion de las modificacions", "config-install-pg-plpgsql": "Verificacion del lengatge PL/pgSQL", "config-install-user": "Creacion d'un utilizaire de la banca de donadas", + "config-install-user-alreadyexists": "L'utilizaire « $1 » existís ja.", + "config-install-user-create-failed": "Fracàs al moment de la creacion de l'utilizaire « $1 » : $2", + "config-install-user-missing": "L'utilizaire «$1» existís pas.", "config-install-tables": "Creacion de las taulas", "config-install-stats": "Inicializacion de las estatisticas", + "config-install-keys": "Generacion de la clau secreta", + "config-install-updates": "Empachar l’execucion de las mesas a jorn inutilas", "config-install-sysop": "Creacion del compte administrator", "config-install-mainpage-failed": "Impossible d’inserir la pagina principala : $1", "config-download-localsettings": "Telecargar LocalSettings.php", "config-help": "ajuda", + "config-help-tooltip": "clicar per agrandir", "mainpagetext": "'''MediaWiki es estat installat amb succès.'''", "mainpagedocfooter": "Consultatz lo [//meta.wikimedia.org/wiki/Help:Contents/fr Guida de l'utilizaire] per mai d'entresenhas sus l'utilizacion d'aqueste logicial de wiki.\n\n== Per començar ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista dels paramètres de configuracion]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/oc FAQ MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las distribucions de MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Adaptatz MediaWiki dins vòstra lenga]" } diff --git a/includes/installer/i18n/pl.json b/includes/installer/i18n/pl.json index 0c48ffa35b..2e7e0233bc 100644 --- a/includes/installer/i18n/pl.json +++ b/includes/installer/i18n/pl.json @@ -62,6 +62,7 @@ "config-env-good": "Środowisko oprogramowania zostało sprawdzone.\nMożesz teraz zainstalować MediaWiki.", "config-env-bad": "Środowisko oprogramowania zostało sprawdzone.\nNie możesz zainstalować MediaWiki.", "config-env-php": "Zainstalowane jest PHP w wersji $1.", + "config-env-hhvm": "Zainstalowany jest HHVM $1.", "config-unicode-using-utf8": "Korzystanie z normalizacji Unicode utf8_normalize.so napisanej przez Brion Vibbera.", "config-unicode-using-intl": "Korzystanie z [http://pecl.php.net/intl rozszerzenia intl PECL] do normalizacji Unicode.", "config-unicode-pure-php-warning": "'''Uwaga!''' [http://pecl.php.net/intl Rozszerzenie intl PECL] do obsługi normalizacji Unicode nie jest dostępne. Użyta zostanie mało wydajna zwykła implementacja w PHP.\nJeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizacji Unicode].", @@ -321,6 +322,7 @@ "config-install-stats": "Inicjowanie statystyki", "config-install-keys": "Generowanie tajnych kluczy", "config-insecure-keys": "'''Ostrzeżenie:''' {{PLURAL:$2|Klucz bezpieczeństwa|Klucze bezpieczeństwa|Klucze bezpieczeństwa}} ($1) utworzone podczas instalacji {{PLURAL:$2|utworzony podczas instalacji nie jest|utworzone podczas instalacji nie są|utworzone podczas instalacji nie są}} w pełni bezpieczne. Być może warto wygenerować {{PLURAL:$2|własny klucz|własne klucze|własne klucze}}.", + "config-install-updates": "Zapobieganie uruchamianiu niepotrzebnych aktualizacji", "config-install-sysop": "Tworzenie konta administratora", "config-install-subscribe-fail": "Nie można zapisać na listę „mediawiki-announce“ – $1", "config-install-subscribe-notpossible": "cURL nie jest zainstalowany, więc allow_url_fopen nie jest dostępne.", diff --git a/includes/installer/i18n/pt.json b/includes/installer/i18n/pt.json index a827fac31d..835cf9b410 100644 --- a/includes/installer/i18n/pt.json +++ b/includes/installer/i18n/pt.json @@ -59,6 +59,7 @@ "config-env-good": "O ambiente foi verificado.\nPode instalar o MediaWiki.", "config-env-bad": "O ambiente foi verificado.\nNão pode instalar o MediaWiki.", "config-env-php": "O PHP $1 está instalado.", + "config-env-hhvm": "HHVM $1 está instalado.", "config-unicode-using-utf8": "A usar o utf8_normalize.so, por Brion Vibber, para a normalização Unicode.", "config-unicode-using-intl": "A usar a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.", "config-unicode-pure-php-warning": "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode. Irá recorrer-se à implementação em PHP puro, que é mais lenta.\nSe o seu site tem alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations/pt normalização Unicode].", diff --git a/includes/installer/i18n/qqq.json b/includes/installer/i18n/qqq.json index 410b1d5195..c879843aff 100644 --- a/includes/installer/i18n/qqq.json +++ b/includes/installer/i18n/qqq.json @@ -62,6 +62,7 @@ "config-env-good": "See also:\n* {{msg-mw|Config-env-bad}}", "config-env-bad": "See also:\n* {{msg-mw|Config-env-good}}", "config-env-php": "Parameters:\n* $1 - the version of PHP that has been installed\nSee also:\n* {{msg-mw|config-env-php-toolow}}", + "config-env-hhvm": "Parameters:\n* $1 - the version of HHVM that has been installed", "config-unicode-using-utf8": "Status message in the MediaWiki installer environment checks.", "config-unicode-using-intl": "Status message in the MediaWiki installer environment checks.", "config-unicode-pure-php-warning": "PECL is the name of a group producing standard pieces of software for PHP, and intl is the name of their library handling some aspects of internationalization.", @@ -300,7 +301,7 @@ "config-install-step-done": "{{Identical|Done}}", "config-install-step-failed": "{{Identical|Failed}}", "config-install-extensions": "Notice shown to the user during the install about progress when extensions are being installed.", - "config-install-database": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", + "config-install-database": "Message indicates the database is being set up\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-install-schema": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-install-pg-schema-not-exist": "Error message shown to users picking PostgreSQL.", "config-install-pg-schema-failed": "Parameters:\n* $1 = database user name (usernames in the database are unrelated to wiki user names)\n* $2 =", @@ -315,7 +316,7 @@ "config-install-user-grant-failed": "Parameters:\n* $1 is the database username for which granting rights failed\n* $2 is the error message", "config-install-user-missing": "Used as PostgreSQL error message. Parameters:\n* $1 - database username\nSee also:\n* {{msg-mw|Config-install-user-missing-create}}", "config-install-user-missing-create": "Used as PostgreSQL error message. Parameters:\n* $1 - database username\nSee also:\n* {{msg-mw|Config-install-user-missing}}", - "config-install-tables": "Message indicates that the tables are being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", + "config-install-tables": "Message indicates that the tables are being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-install-tables-exist": "Error notice during the installation saying that the database already seems set up for MediaWiki, so it's continuing without taking that step.", "config-install-tables-failed": "Used as PostgreSQL error message. Parameters:\n* $1 - detailed error message", "config-install-interwiki": "Message indicates that the interwikitables are being populated\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", @@ -324,7 +325,7 @@ "config-install-stats": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-install-keys": "*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-insecure-keys": "Parameters:\n* $1 - A list of names of the secret keys that were generated.\n* $2 - the number of items in the list $1, to be used with PLURAL.", - "config-install-updates": "Message indicating that the updatelog table is filled with keys of updates that won't be run when running database updates.", + "config-install-updates": "Message indicating that the updatelog table is filled with keys of updates that won't be run when running database updates.\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-install-updates-failed": "Used as error message. Parameters:\n* $1 - detailed error message", "config-install-sysop": "Message indicates that the administrator user account is being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}", "config-install-subscribe-fail": "{{doc-important|\"[[m:mail:mediawiki-announce|mediawiki-announce]]\" is the name of a mailing list and should not be translated.}}\nA message displayed if the MediaWiki installer encounters an error making a request to lists.wikimedia.org which hosts the mailing list.\n* $1 - the HTTP error encountered, reproduced as is (English string)", diff --git a/includes/installer/i18n/ru.json b/includes/installer/i18n/ru.json index b5441c12bc..b8a36bef28 100644 --- a/includes/installer/i18n/ru.json +++ b/includes/installer/i18n/ru.json @@ -62,6 +62,7 @@ "config-env-good": "Проверка внешней среды была успешно проведена.\nВы можете установить MediaWiki.", "config-env-bad": "Была проведена проверка внешней среды.\nВы не можете установить MediaWiki.", "config-env-php": "Установленная версия PHP: $1.", + "config-env-hhvm": "HHVM $1 установлена.", "config-unicode-using-utf8": "Использовать Brion Vibber utf8_normalize.so для нормализации Юникода.", "config-unicode-using-intl": "Будет использовано [http://pecl.php.net/intl расширение «intl» для PECL] для нормализации Юникода.", "config-unicode-pure-php-warning": "'''Внимание!''': [http://pecl.php.net/intl расширение intl из PECL] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.\nЕсли ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормализации Юникода].", @@ -324,6 +325,8 @@ "config-install-stats": "Статистика инициализации", "config-install-keys": "Создание секретных ключей", "config-insecure-keys": "'''Предупреждение.''' {{PLURAL:$2|1=Ключ безопасности $1, созданный во время установки, недостаточно надёжен|Ключи безопасности $1, созданные во время установки, недостаточно надёжны}}. Рассмотрите возможность {{PLURAL:$2|1=его|их}} изменения вручную.", + "config-install-updates": "Предотвращение запуска ненужных обновлений", + "config-install-updates-failed": "Ошибка: Вставка ключей обновления в таблицы завершилась со следующей ошибкой: $1", "config-install-sysop": "Создание учётной записи администратора", "config-install-subscribe-fail": "Не удаётся подписаться на mediawiki-announce: $1", "config-install-subscribe-notpossible": "cURL не установлен и не доступна опция allow_url_fopen.", diff --git a/includes/installer/i18n/sv.json b/includes/installer/i18n/sv.json index b46902c4fa..2bdfb10072 100644 --- a/includes/installer/i18n/sv.json +++ b/includes/installer/i18n/sv.json @@ -51,6 +51,7 @@ "config-env-good": "Miljön har kontrollerats.\nDu kan installera MediaWiki.", "config-env-bad": "Miljön har kontrollerats.\nDu kan inte installera MediaWiki.", "config-env-php": "PHP $1 är installerat.", + "config-env-hhvm": "HHVM $1 är installerat.", "config-unicode-using-utf8": "Använder Brion Vibbers utf8_normalize.so för Unicode-normalisering.", "config-unicode-using-intl": "Använder [http://pecl.php.net/intl intl PECL-tillägget] för Unicode-normalisering.", "config-unicode-pure-php-warning": "'''Varning:''' [http://pecl.php.net/intl intl PECL-tillägget] är inte tillgängligt för att hantera Unicode-normalisering, faller tillbaka till en lÃ¥ngsamt implementering i ren PHP.\nOm du driver en högtrafikerad webbplats bör du läsa lite om [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode-normalisering].", @@ -310,6 +311,8 @@ "config-install-stats": "Initierar statistik", "config-install-keys": "Genererar hemliga nycklar", "config-insecure-keys": "'''Varning:''' {{PLURAL:$2|En säkerhetsnyckel|Säkerhetsnycklar}} ($1) som generades under installationen är inte helt {{PLURAL:$2|säker|säkra}} . Överväg att ändra {{PLURAL:$2|den|dem}} manuellt.", + "config-install-updates": "Förhindra att onödiga uppdateringar körs", + "config-install-updates-failed": "Fel: Infogning av uppdateringsnycklar i tabeller misslyckades med följande fel:$1", "config-install-sysop": "Skapar administratörskonto", "config-install-subscribe-fail": "Det gick inte att prenumerera pÃ¥ mediawiki-announce: $1", "config-install-subscribe-notpossible": "cURL är inte installerad och allow_url_fopen är inte tillgänglig.", diff --git a/includes/installer/i18n/uk.json b/includes/installer/i18n/uk.json index 001a0f8d32..9eee1a7fd6 100644 --- a/includes/installer/i18n/uk.json +++ b/includes/installer/i18n/uk.json @@ -314,6 +314,7 @@ "config-install-stats": "Ініціалізація статистики", "config-install-keys": "Генерація секретних ключів", "config-insecure-keys": "'''Увага:''' {{PLURAL:$2|1=Секретний ключ|Секретні ключі}} ($1), {{PLURAL:$2|1=згенерований в процесі встановлення, недостатньо надійний|згенеровані в процесі встановлення, недостатньо надійні}}. Розгляньте можливість {{PLURAL:$2|1=його|їх}} заміни вручну.", + "config-install-updates-failed": "Помилка: Вставка оновленних ключів в таблиці не вдалося через таку помилку:$1", "config-install-sysop": "Створення облікового запису адміністратора", "config-install-subscribe-fail": "Не можливо підписатись на mediawiki-announce: $1", "config-install-subscribe-notpossible": "cURL не встановлено і опція allow_url_fopen не доступна.", diff --git a/includes/installer/i18n/zh-hans.json b/includes/installer/i18n/zh-hans.json index b297a19057..9e2833e2c0 100644 --- a/includes/installer/i18n/zh-hans.json +++ b/includes/installer/i18n/zh-hans.json @@ -66,6 +66,7 @@ "config-env-good": "环境检查已经完成。您可以安装MediaWiki。", "config-env-bad": "环境检查已经完成。您不能安装MediaWiki。", "config-env-php": "PHP $1已安装。", + "config-env-hhvm": "HHVM $1已安装。", "config-unicode-using-utf8": "使用Brion Vibber的utf8_normalize.so实现Unicode正常化。", "config-unicode-using-intl": "使用[http://pecl.php.net/intl intl PECL扩展程序]标准化Unicode。", "config-unicode-pure-php-warning": "警告:因为尚未安装 [http://pecl.php.net/intl intl PECL 扩展]以处理 Unicode 正常化,故只能退而采用运行较慢的纯 PHP 实现的方法。\n如果您运行着一个高流量的站点,请参阅 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode 正常化]一文。", @@ -325,6 +326,8 @@ "config-install-stats": "初始化统计", "config-install-keys": "生成密钥中", "config-insecure-keys": "'''警告''':在安装过程中生成的{{PLURAL:$2|安全密钥|安全密钥}}($1){{PLURAL:$2|并|并}}不一定安全。请考虑手动更改{{PLURAL:$2|它|它们}}。", + "config-install-updates": "防止运行不需要的更新", + "config-install-updates-failed": "错误:表格中插入更新关键字失败并出现如下错误:$1", "config-install-sysop": "正在创建管理员用户帐号", "config-install-subscribe-fail": "无法订阅mediawiki-announce:$1", "config-install-subscribe-notpossible": "没有安装cURL,allow_url_fopen也不可用。", diff --git a/includes/installer/i18n/zh-hant.json b/includes/installer/i18n/zh-hant.json index cb6cefb4c9..c5b5a4c007 100644 --- a/includes/installer/i18n/zh-hant.json +++ b/includes/installer/i18n/zh-hant.json @@ -58,6 +58,7 @@ "config-env-good": "環境檢查已完成。\n您可以安裝 MediaWiki。", "config-env-bad": "環境檢查已完成。\n您無法安裝 MediaWiki。", "config-env-php": "PHP $1 已安裝。", + "config-env-hhvm": "HHVM $1 已安裝。", "config-unicode-using-utf8": "使用 Brion Vibber 的 utf8_normalize.so 做 Unicode 正規化。", "config-unicode-using-intl": "使用 [http://pecl.php.net/intl intl PECL 擴充套件] 做 Unicode 正規化。", "config-unicode-pure-php-warning": "警告: 無法使用 [http://pecl.php.net/intl intl PECL 擴充套件] 處理 Unicode 正規化,故回退使用純 PHP 實作的正規化程式,此方式處理速度較緩慢。\n\n如果您的網站瀏覽人次很高,您應先閱讀 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations/zh Unicode 正規化]。", @@ -171,7 +172,7 @@ "config-sqlite-cant-create-db": "無法建立資料庫檔案 $1。", "config-sqlite-fts3-downgrade": "PHP 不支援 FTS3,正在降級資料表。", "config-can-upgrade": "在資料庫中找到 MediaWiki 的資料表。\n要升級至 MediaWiki $1,請點選 繼續。", - "config-upgrade-done": "升級完成。\n\n現在您可以 [$1 開始使用您的 Wiki] 了。\n\n如果您需要重新產生 LocalSettings.php 檔案,請點擊下方按鈕。\n除非您的 Wiki 出現了問題,否則我們 不建議 您執行此操作。", + "config-upgrade-done": "升級完成。\n\n現在您可以 [$1 開始使用您的 Wiki] 了。\n\n如果您需要重新產生 LocalSettings.php 檔案,請點選下方按鈕。\n除非您的 Wiki 出現了問題,否則我們 不建議 您執行此操作。", "config-upgrade-done-no-regenerate": "升級完成。\n\n現在您可以 [$1 開始使用您的 Wiki] 了。", "config-regenerate": "重新產生 LocalSettings.php →", "config-show-table-status": "SHOW TABLE STATUS 查詢失敗!", @@ -251,7 +252,7 @@ "config-email-usertalk": "開啟使用者討論頁面通知", "config-email-usertalk-help": "若使用者在個人偏好開啟了此功能,則可收到使用者討論頁面被修改的通知。", "config-email-watchlist": "開啟監視清單通知", - "config-email-watchlist-help": "若使用者在個人偏好開啟了此功能,允許使用者收到與其監視列表有關的通知。", + "config-email-watchlist-help": "若使用者在個人偏好開啟了此功能,允許使用者收到與其監視清單有關的通知。", "config-email-auth": "開啟電子郵件身份認證", "config-email-auth-help": "若開啟此選項,使用者不論設定或者更改電子郵件地址,都必須透過收信的方式確認沒有問題。\n只有驗證過的電子郵件地址可以收到來自其他使用者或修改通知的信件。\n公開的 Wiki 會 建議 設定此選項,以防使用者濫用電子郵件功能。", "config-email-sender": "電子郵件回覆位址:", diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php index 30b92c72ff..ae28163b25 100644 --- a/includes/libs/CSSJanus.php +++ b/includes/libs/CSSJanus.php @@ -179,6 +179,22 @@ class CSSJanus { $css = $noFlipClass->detokenize( $css ); $css = $noFlipSingle->detokenize( $css ); + // Remove remaining /* @noflip */ annotations, they won't be needed anymore + // and can interfere with other code (bug 69698). + $css = self::nullTransform( $css ); + + return $css; + } + + /** + * Remove @noflip annotations, but don't do any other transforms. + * @param string $css stylesheet to transform + * @return string Transformed stylesheet + */ + public static function nullTransform( $css ) { + $patt = self::$patterns['noflip_annotation']; + $css = preg_replace( "/($patt)\\s*/i", '', $css ); + return $css; } diff --git a/includes/objectcache/MemcachedBagOStuff.php b/includes/objectcache/MemcachedBagOStuff.php index 54a464de00..53edcddec9 100644 --- a/includes/objectcache/MemcachedBagOStuff.php +++ b/includes/objectcache/MemcachedBagOStuff.php @@ -154,7 +154,7 @@ class MemcachedBagOStuff extends BagOStuff { if ( $expiry > 2592000 && $expiry < 1000000000 ) { $expiry = 2592000; } - return (int) $expiry; + return (int)$expiry; } /** diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php index 380252f58a..80e5371ee2 100644 --- a/includes/page/ImagePage.php +++ b/includes/page/ImagePage.php @@ -267,8 +267,7 @@ class ImagePage extends Article { # @todo FIXME: Why is this using escapeId for a class?! $class = Sanitizer::escapeId( $v['id'] ); if ( $type == 'collapsed' ) { - // Handled by mediawiki.action.view.metadata module - // and skins/common/shared.css. + // Handled by mediawiki.action.view.metadata module and shared.css. $class .= ' collapsable'; } $r .= "\n"; @@ -1329,6 +1328,9 @@ class ImageHistoryList extends ContextSource { $url = $lang->userTimeAndDate( $timestamp, $user ); } $row .= '' . $url . ''; + } elseif ( !$file->exists() ) { + $row .= '' + . $lang->userTimeAndDate( $timestamp, $user ) . ''; } else { $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img ); $row .= Xml::element( diff --git a/includes/pager/IndexPager.php b/includes/pager/IndexPager.php index 1d93c27f68..5972296f39 100644 --- a/includes/pager/IndexPager.php +++ b/includes/pager/IndexPager.php @@ -447,8 +447,8 @@ abstract class IndexPager extends ContextSource implements Pager { * * @param string $text Text displayed on the link * @param array $query Associative array of parameter to be in the query string - * @param string $type Value of the "rel" attribute - * + * @param string $type Link type used to create additional attributes, like "rel", "class" or + * "title". Valid values (non-exhaustive list): 'first', 'last', 'prev', 'next', 'asc', 'desc'. * @return string HTML fragment */ function makeLink( $text, array $query = null, $type = null ) { @@ -457,15 +457,19 @@ abstract class IndexPager extends ContextSource implements Pager { } $attrs = array(); - if ( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) { - # HTML5 rel attributes + if ( in_array( $type, array( 'prev', 'next' ) ) ) { $attrs['rel'] = $type; } + if ( in_array( $type, array( 'asc', 'desc' ) ) ) { + $attrs['title'] = wfMessage( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text(); + } + if ( $type ) { $attrs['class'] = "mw-{$type}link"; } + return Linker::linkKnown( $this->getTitle(), $text, diff --git a/includes/pager/TablePager.php b/includes/pager/TablePager.php index 19538c6ac8..221878700d 100644 --- a/includes/pager/TablePager.php +++ b/includes/pager/TablePager.php @@ -50,48 +50,99 @@ abstract class TablePager extends IndexPager { parent::__construct(); } + /** + * Get the formatted result list. Calls getStartBody(), formatRow() and getEndBody(), concatenates + * the results and returns them. + * + * Also adds the required styles to our OutputPage object (this means that if context wasn't + * passed to constructor or otherwise set up, you will get a pager with missing styles). + * + * This method has been made 'final' in 1.24. There's no reason to override it, and if there exist + * any subclasses that do, the style loading hack is probably broken in them. Let's fail fast + * rather than mysteriously render things wrong. + * + * @deprecated since 1.24, use getBodyOutput() or getFullOutput() instead + * @return string + */ + final public function getBody() { + $this->getOutput()->addModuleStyles( $this->getModuleStyles() ); + return parent::getBody(); + } + + /** + * Get the formatted result list. + * + * Calls getBody() and getModuleStyles() and builds a ParserOutput object. (This is a bit hacky + * but works well.) + * + * @since 1.24 + * @return ParserOutput + */ + public function getBodyOutput() { + $body = parent::getBody(); + + $pout = new ParserOutput; + $pout->setText( $body ); + $pout->addModuleStyles( $this->getModuleStyles() ); + return $pout; + } + + /** + * Get the formatted result list, with navigation bars. + * + * Calls getBody(), getNavigationBar() and getModuleStyles() and + * builds a ParserOutput object. (This is a bit hacky but works well.) + * + * @since 1.24 + * @return ParserOutput + */ + public function getFullOutput() { + $navigation = $this->getNavigationBar(); + $body = parent::getBody(); + + $pout = new ParserOutput; + $pout->setText( $navigation . $body . $navigation ); + $pout->addModuleStyles( $this->getModuleStyles() ); + return $pout; + } + /** * @protected * @return string */ function getStartBody() { - global $wgStylePath; $sortClass = $this->getSortHeaderClass(); $s = ''; $fields = $this->getFieldNames(); - # Make table header + // Make table header foreach ( $fields as $field => $name ) { if ( strval( $name ) == '' ) { $s .= Html::rawElement( 'th', array(), ' ' ) . "\n"; } elseif ( $this->isFieldSortable( $field ) ) { $query = array( 'sort' => $field, 'limit' => $this->mLimit ); - if ( $field == $this->mSort ) { - # This is the sorted column - # Prepare a link that goes in the other sort order + $linkType = null; + $class = null; + + if ( $this->mSort == $field ) { + // The table is sorted by this field already, make a link to sort in the other direction + // We don't actually know in which direction other fields will be sorted by default… if ( $this->mDefaultDirection ) { - # Descending - $image = 'Arr_d.png'; + $linkType = 'asc'; + $class = "$sortClass TablePager_sort-descending"; $query['asc'] = '1'; $query['desc'] = ''; - $alt = $this->msg( 'descending_abbrev' )->escaped(); } else { - # Ascending - $image = 'Arr_u.png'; + $linkType = 'desc'; + $class = "$sortClass TablePager_sort-ascending"; $query['asc'] = ''; $query['desc'] = '1'; - $alt = $this->msg( 'ascending_abbrev' )->escaped(); } - $image = "$wgStylePath/common/images/$image"; - $link = $this->makeLink( - Html::element( 'img', array( 'width' => 12, 'height' => 12, - 'alt' => $alt, 'src' => $image ) ) . htmlspecialchars( $name ), $query ); - $s .= Html::rawElement( 'th', array( 'class' => $sortClass ), $link ) . "\n"; - } else { - $s .= Html::rawElement( 'th', array(), - $this->makeLink( htmlspecialchars( $name ), $query ) ) . "\n"; } + + $link = $this->makeLink( htmlspecialchars( $name ), $query, $linkType ); + $s .= Html::rawElement( 'th', array( 'class' => $class ), $link ) . "\n"; } else { $s .= Html::element( 'th', array(), $name ) . "\n"; } @@ -99,7 +150,6 @@ abstract class TablePager extends IndexPager { $tableClass = $this->getTableClass(); $ret = Html::openElement( 'table', array( - 'style' => 'border:1px;', 'class' => "mw-datatable $tableClass" ) ); $ret .= Html::rawElement( 'thead', array(), Html::rawElement( 'tr', array(), "\n" . $s . "\n" ) ); @@ -235,45 +285,23 @@ abstract class TablePager extends IndexPager { * @return string HTML */ public function getNavigationBar() { - global $wgStylePath; - if ( !$this->isNavigationBarShown() ) { return ''; } - $path = "$wgStylePath/common/images"; $labels = array( 'first' => 'table_pager_first', 'prev' => 'table_pager_prev', 'next' => 'table_pager_next', 'last' => 'table_pager_last', ); - $images = array( - 'first' => 'arrow_first_25.png', - 'prev' => 'arrow_left_25.png', - 'next' => 'arrow_right_25.png', - 'last' => 'arrow_last_25.png', - ); - $disabledImages = array( - 'first' => 'arrow_disabled_first_25.png', - 'prev' => 'arrow_disabled_left_25.png', - 'next' => 'arrow_disabled_right_25.png', - 'last' => 'arrow_disabled_last_25.png', - ); - if ( $this->getLanguage()->isRTL() ) { - $keys = array_keys( $labels ); - $images = array_combine( $keys, array_reverse( $images ) ); - $disabledImages = array_combine( $keys, array_reverse( $disabledImages ) ); - } $linkTexts = array(); $disabledTexts = array(); foreach ( $labels as $type => $label ) { $msgLabel = $this->msg( $label )->escaped(); - $linkTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$images[$type]}", - 'alt' => $msgLabel ) ) . "
    $msgLabel"; - $disabledTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$disabledImages[$type]}", - 'alt' => $msgLabel ) ) . "
    $msgLabel"; + $linkTexts[$type] = "
    $msgLabel
    "; + $disabledTexts[$type] = "
    $msgLabel
    "; } $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); @@ -281,12 +309,26 @@ abstract class TablePager extends IndexPager { $s .= Html::openElement( 'tr' ) . "\n"; $width = 100 / count( $links ) . '%'; foreach ( $labels as $type => $label ) { - $s .= Html::rawElement( 'td', array( 'style' => "width:$width;" ), $links[$type] ) . "\n"; + // We want every cell to have the same width. We could use table-layout: fixed; in CSS, + // but it only works if we specify the width of a cell or the table and we don't want to. + // There is no better way. + $s .= Html::rawElement( 'td', + array( 'style' => "width: $width;", 'class' => "TablePager_nav-$type" ), + $links[$type] ) . "\n"; } $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"; return $s; } + /** + * ResourceLoader modules that must be loaded to provide correct styling for this pager + * @since 1.24 + * @return string[] + */ + public function getModuleStyles() { + return array( 'mediawiki.pager.tablePager' ); + } + /** * Get a "" ); + + unset( $params['options'] ); + $field = new HTMLAutoCompleteSelectField( $params ); + $html = $field->getInputHTML( false ); + $this->assertNotRegExp( '/select/', $html, + "When the 'options' parameter is not set, the HTML does not include a