Merge "Avoid (s) for unknown plural in a message"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 16 Apr 2015 12:21:29 +0000 (12:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 16 Apr 2015 12:21:29 +0000 (12:21 +0000)
149 files changed:
RELEASE-NOTES-1.25
RELEASE-NOTES-1.26
autoload.php
includes/ChangeTags.php
includes/DefaultSettings.php
includes/User.php
includes/actions/RevisiondeleteAction.php
includes/actions/SpecialPageAction.php
includes/api/ApiBase.php
includes/api/ApiComparePages.php
includes/api/ApiContinuationManager.php [new file with mode: 0644]
includes/api/ApiCreateAccount.php
includes/api/ApiEditPage.php
includes/api/ApiErrorFormatter.php [new file with mode: 0644]
includes/api/ApiExpandTemplates.php
includes/api/ApiFeedWatchlist.php
includes/api/ApiFileRevert.php
includes/api/ApiFormatBase.php
includes/api/ApiFormatDbg.php
includes/api/ApiFormatDump.php
includes/api/ApiFormatFeedWrapper.php
includes/api/ApiFormatJson.php
includes/api/ApiFormatPhp.php
includes/api/ApiFormatRaw.php
includes/api/ApiFormatTxt.php
includes/api/ApiFormatWddx.php
includes/api/ApiFormatXml.php
includes/api/ApiHelp.php
includes/api/ApiImageRotate.php
includes/api/ApiImport.php
includes/api/ApiMain.php
includes/api/ApiManageTags.php
includes/api/ApiMessage.php [new file with mode: 0644]
includes/api/ApiMove.php
includes/api/ApiOpenSearch.php
includes/api/ApiPageSet.php
includes/api/ApiParamInfo.php
includes/api/ApiParse.php
includes/api/ApiProtect.php
includes/api/ApiPurge.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllCategories.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/ApiQueryAllImages.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryAllMessages.php
includes/api/ApiQueryAllPages.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryCategoryMembers.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryExtLinksUsage.php
includes/api/ApiQueryExternalLinks.php
includes/api/ApiQueryFileRepoInfo.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryIWBacklinks.php
includes/api/ApiQueryIWLinks.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLangBacklinks.php
includes/api/ApiQueryLangLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryORM.php
includes/api/ApiQueryPagePropNames.php
includes/api/ApiQueryPagesWithProp.php
includes/api/ApiQueryPrefixSearch.php
includes/api/ApiQueryProtectedTitles.php
includes/api/ApiQueryQueryPage.php
includes/api/ApiQueryRandom.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisionsBase.php
includes/api/ApiQuerySearch.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryStashImageInfo.php
includes/api/ApiQueryTags.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUserInfo.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiQueryWatchlistRaw.php
includes/api/ApiResult.php
includes/api/ApiRevisionDelete.php
includes/api/ApiRsd.php
includes/api/ApiSerializable.php [new file with mode: 0644]
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiUpload.php
includes/api/ApiUserrights.php
includes/api/ApiWatch.php
includes/api/i18n/en.json
includes/api/i18n/es.json
includes/api/i18n/gl.json
includes/api/i18n/ja.json
includes/api/i18n/lb.json
includes/api/i18n/qqq.json
includes/api/i18n/ru.json
includes/api/i18n/zh-hans.json
includes/changetags/ChangeTagsLogItem.php
includes/changetags/ChangeTagsLogList.php
includes/changetags/ChangeTagsRevisionItem.php
includes/changetags/ChangeTagsRevisionList.php
includes/debug/MWDebug.php
includes/installer/i18n/el.json
includes/resourceloader/ResourceLoaderImage.php
includes/resourceloader/ResourceLoaderImageModule.php
includes/specials/SpecialEditTags.php
languages/i18n/azb.json
languages/i18n/ce.json
languages/i18n/cv.json
languages/i18n/el.json
languages/i18n/et.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hsb.json
languages/i18n/hu.json
languages/i18n/ku-latn.json
languages/i18n/lrc.json
languages/i18n/pl.json
languages/i18n/ps.json
languages/i18n/qqq.json
languages/i18n/rup.json
languages/i18n/sah.json
languages/i18n/zh-hans.json
languages/messages/MessagesBgn.php
languages/messages/MessagesEn.php
tests/phpunit/includes/api/ApiContinuationManagerTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiErrorFormatterTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiMainTest.php
tests/phpunit/includes/api/ApiMessageTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/api/ApiResultTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiTestCase.php
tests/phpunit/includes/api/MockApi.php
tests/phpunit/includes/api/MockApiQueryBase.php
tests/phpunit/includes/api/format/ApiFormatDbgTest.php
tests/phpunit/includes/api/format/ApiFormatDumpTest.php
tests/phpunit/includes/api/format/ApiFormatJsonTest.php
tests/phpunit/includes/api/format/ApiFormatNoneTest.php
tests/phpunit/includes/api/format/ApiFormatPhpTest.php
tests/phpunit/includes/api/format/ApiFormatTxtTest.php
tests/phpunit/includes/api/format/ApiFormatWddxTest.php
tests/phpunit/includes/api/format/ApiFormatXmlTest.php
tests/phpunit/includes/debug/MWDebugTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
tests/phpunit/includes/upload/UploadFromUrlTest.php
tests/phpunit/maintenance/backupTextPassTest.php
tests/qunit/data/testrunner.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js

index 8a589f6..4f648f5 100644 (file)
@@ -94,8 +94,7 @@ production.
 * Update QUnit from v1.14.0 to v1.16.0.
 * Update Moment.js from v2.8.3 to v2.8.4.
 * Special:Tags now allows for manipulating the list of user-modifiable change
-  tags. Actually modifying the tagging of a revision or log entry is not
-  implemented yet.
+  tags.
 * Added 'managetags' user right and 'ChangeTagCanCreate', 'ChangeTagCanDelete',
   and 'ChangeTagCanCreate' hooks to allow for managing user-modifiable change
   tags.
@@ -122,6 +121,10 @@ production.
 * (T94536) You can now make the sitenotice appear to logged-in users only by
   editing MediaWiki:Anonnotice and replacing its content with "". Setting it to
   "-" (default) will continue disable it and fallback to MediaWiki:Sitenotice.
+* Modifying the tagging of a revision or log entry is now available via
+  Special:EditTags, generally accessed via the revision-deletion-like interface
+  on history pages and Special:Log is likely to be more useful.
+* Added 'applychangetags' and 'changetags' user rights.
 
 ==== External libraries ====
 * MediaWiki now requires certain external libraries to be installed. In the past
@@ -249,6 +252,7 @@ production.
   Title::userCan() via the API.
 * Default type param for query list=watchlist and list=recentchanges has
   been changed from all types (e.g. including 'external') to 'edit|new|log'.
+* Added formatversion to format=json, still experimental.
 
 === Action API internal changes in 1.25 ===
 * ApiHelp has been rewritten to support i18n and paginated HTML output.
@@ -286,6 +290,15 @@ production.
   the current request was sent with the 'callback' parameter (or any future
   method that breaks the same-origin policy).
 * Profiling methods in ApiBase are deprecated and no longer need to be called.
+* ApiResult was greatly overhauled. See inline documentation for details.
+* ApiResult will automatically convert objects to strings or arrays (depending
+  on whether a __toString() method exists on the object), and will refuse to
+  add unsupported value types.
+  * An informal interface, ApiSerializable, exists to override the default
+    object conversion.
+* ApiResult/ApiFormatBase "raw mode" is deprecated.
+* ApiFormatXml now assumes defaults and so on instead of throwing errors when
+  metadata isn't set.
 * The following methods have been deprecated and may be removed in a future
   release:
   * ApiBase::getDescription
@@ -302,15 +315,31 @@ production.
   * ApiBase::profileDBIn
   * ApiBase::profileDBOut
   * ApiBase::getProfileDBTime
+  * ApiBase::getResultData
   * ApiFormatBase::setUnescapeAmps
   * ApiFormatBase::getWantsHelp
   * ApiFormatBase::setHelp
   * ApiFormatBase::formatHTML
   * ApiFormatBase::setBufferResult
   * ApiFormatBase::getDescription
+  * ApiFormatBase::getNeedsRawData
   * ApiMain::setHelp
   * ApiMain::reallyMakeHelpMsg
   * ApiMain::makeHelpMsgHeader
+  * ApiResult::setRawMode
+  * ApiResult::getIsRawMode
+  * ApiResult::getData
+  * ApiResult::setElement
+  * ApiResult::setContent
+  * ApiResult::setIndexedTagName_recursive
+  * ApiResult::setIndexedTagName_internal
+  * ApiResult::setParsedLimit
+  * ApiResult::beginContinuation
+  * ApiResult::setContinueParam
+  * ApiResult::setGeneratorContinueParam
+  * ApiResult::endContinuation
+  * ApiResult::size
+  * ApiResult::convertStatusToArray
   * ApiQueryImageInfo::getPropertyDescriptions
 * The following classes have been deprecated and may be removed in a future
   release:
index 3a84d59..89dad11 100644 (file)
@@ -11,16 +11,16 @@ production.
 === Configuration changes in 1.26 ===
 
 === New features in 1.26 ===
-* Modifying the tagging of a revision or log entry is now available via
-  Special:EditTags, generally accessed via the revision-deletion-like interface
-  on history pages and Special:Log is likely to be more useful.
-* Added 'applychangetags' and 'changetags' user rights.
+* Change tags can now be hidden in the interface by disabling the associated
+  "tag-<id>" interface message.
 
 ==== External libraries ====
 
 === Bug fixes in 1.26 ===
 
 === Action API changes in 1.26 ===
+* API action=query&list=tags: The displayname can now be boolean false if the
+  tag is meant to be hidden from user interfaces.
 
 === Action API internal changes in 1.26 ===
 
@@ -32,7 +32,8 @@ changes to languages because of Bugzilla reports.
 
 
 === Other changes in 1.26 ===
-
+* ChangeTags::tagDescription() will return false if the interface message
+  for the tag is disabled.
 
 == Compatibility ==
 
index 92d6014..93f8e43 100644 (file)
@@ -21,11 +21,14 @@ $wgAutoloadLocalClasses = array(
        'ApiCheckToken' => __DIR__ . '/includes/api/ApiCheckToken.php',
        'ApiClearHasMsg' => __DIR__ . '/includes/api/ApiClearHasMsg.php',
        'ApiComparePages' => __DIR__ . '/includes/api/ApiComparePages.php',
+       'ApiContinuationManager' => __DIR__ . '/includes/api/ApiContinuationManager.php',
        'ApiCreateAccount' => __DIR__ . '/includes/api/ApiCreateAccount.php',
        'ApiDelete' => __DIR__ . '/includes/api/ApiDelete.php',
        'ApiDisabled' => __DIR__ . '/includes/api/ApiDisabled.php',
        'ApiEditPage' => __DIR__ . '/includes/api/ApiEditPage.php',
        'ApiEmailUser' => __DIR__ . '/includes/api/ApiEmailUser.php',
+       'ApiErrorFormatter' => __DIR__ . '/includes/api/ApiErrorFormatter.php',
+       'ApiErrorFormatter_BackCompat' => __DIR__ . '/includes/api/ApiErrorFormatter.php',
        'ApiExpandTemplates' => __DIR__ . '/includes/api/ApiExpandTemplates.php',
        'ApiFeedContributions' => __DIR__ . '/includes/api/ApiFeedContributions.php',
        'ApiFeedRecentChanges' => __DIR__ . '/includes/api/ApiFeedRecentChanges.php',
@@ -53,6 +56,7 @@ $wgAutoloadLocalClasses = array(
        'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
        'ApiMain' => __DIR__ . '/includes/api/ApiMain.php',
        'ApiManageTags' => __DIR__ . '/includes/api/ApiManageTags.php',
+       'ApiMessage' => __DIR__ . '/includes/api/ApiMessage.php',
        'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php',
        'ApiMove' => __DIR__ . '/includes/api/ApiMove.php',
        'ApiOpenSearch' => __DIR__ . '/includes/api/ApiOpenSearch.php',
@@ -118,10 +122,12 @@ $wgAutoloadLocalClasses = array(
        'ApiQueryUsers' => __DIR__ . '/includes/api/ApiQueryUsers.php',
        'ApiQueryWatchlist' => __DIR__ . '/includes/api/ApiQueryWatchlist.php',
        'ApiQueryWatchlistRaw' => __DIR__ . '/includes/api/ApiQueryWatchlistRaw.php',
+       'ApiRawMessage' => __DIR__ . '/includes/api/ApiMessage.php',
        'ApiResult' => __DIR__ . '/includes/api/ApiResult.php',
        'ApiRevisionDelete' => __DIR__ . '/includes/api/ApiRevisionDelete.php',
        'ApiRollback' => __DIR__ . '/includes/api/ApiRollback.php',
        'ApiRsd' => __DIR__ . '/includes/api/ApiRsd.php',
+       'ApiSerializable' => __DIR__ . '/includes/api/ApiSerializable.php',
        'ApiSetNotificationTimestamp' => __DIR__ . '/includes/api/ApiSetNotificationTimestamp.php',
        'ApiStashEdit' => __DIR__ . '/includes/api/ApiStashEdit.php',
        'ApiTag' => __DIR__ . '/includes/api/ApiTag.php',
@@ -516,6 +522,7 @@ $wgAutoloadLocalClasses = array(
        'Http' => __DIR__ . '/includes/HttpFunctions.php',
        'HttpError' => __DIR__ . '/includes/exception/HttpError.php',
        'HttpStatus' => __DIR__ . '/includes/libs/HttpStatus.php',
+       'IApiMessage' => __DIR__ . '/includes/api/ApiMessage.php',
        'ICacheHelper' => __DIR__ . '/includes/cache/CacheHelper.php',
        'IContextSource' => __DIR__ . '/includes/context/IContextSource.php',
        'IDBAccessObject' => __DIR__ . '/includes/dao/IDBAccessObject.php',
index 3103edd..43f957c 100644 (file)
@@ -51,14 +51,26 @@ class ChangeTags {
                $tags = explode( ',', $tags );
                $displayTags = array();
                foreach ( $tags as $tag ) {
+                       if ( !$tag ) {
+                               continue;
+                       }
+                       $description = self::tagDescription( $tag );
+                       if ( $description === false ) {
+                               continue;
+                       }
                        $displayTags[] = Xml::tags(
                                'span',
                                array( 'class' => 'mw-tag-marker ' .
                                                                Sanitizer::escapeClass( "mw-tag-marker-$tag" ) ),
-                               self::tagDescription( $tag )
+                               $description
                        );
                        $classes[] = Sanitizer::escapeClass( "mw-tag-$tag" );
                }
+
+               if ( !$displayTags ) {
+                       return array( '', array() );
+               }
+
                $markers = wfMessage( 'tag-list-wrapper' )
                        ->numParams( count( $displayTags ) )
                        ->rawParams( $wgLang->commaList( $displayTags ) )
@@ -69,16 +81,30 @@ class ChangeTags {
        }
 
        /**
-        * Get a short description for a tag
+        * Get a short description for a tag.
         *
-        * @param string $tag Tag
+        * Checks if message key "mediawiki:tag-$tag" exists. If it does not,
+        * returns the HTML-escaped tag name. Uses the message if the message
+        * exists, provided it is not disabled. If the message is disabled,
+        * we consider the tag hidden, and return false.
         *
-        * @return string Short description of the tag from "mediawiki:tag-$tag" if this message exists,
-        *   html-escaped version of $tag otherwise
+        * @param string $tag Tag
+        * @return string|bool Tag description or false if tag is to be hidden.
+        * @since 1.25 Returns false if tag is to be hidden.
         */
        public static function tagDescription( $tag ) {
                $msg = wfMessage( "tag-$tag" );
-               return $msg->exists() ? $msg->parse() : htmlspecialchars( $tag );
+               if ( !$msg->exists() ) {
+                       // No such message, so return the HTML-escaped tag name.
+                       return htmlspecialchars( $tag );
+               }
+               if ( $msg->isDisabled() ) {
+                       // The message exists but is disabled, hide the tag.
+                       return false;
+               }
+
+               // Message exists and isn't disabled, use it.
+               return $msg->parse();
        }
 
        /**
@@ -1171,7 +1197,7 @@ class ChangeTags {
 
                $out = array();
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_SLAVE, 'vslow' );
                $res = $dbr->select(
                        'change_tag',
                        array( 'ct_tag', 'hitcount' => 'count(*)' ),
index d5801cb..c2d6c95 100644 (file)
@@ -4297,6 +4297,7 @@ $wgReservedUsernames = array(
        'msg:double-redirect-fixer', // Automatic double redirect fix
        'msg:usermessage-editor', // Default user for leaving user messages
        'msg:proxyblocker', // For $wgProxyList and Special:Blockme (removed in 1.22)
+       'msg:spambot_username', // Used by cleanupSpam.php
 );
 
 /**
index 3c2939f..f862953 100644 (file)
@@ -3633,7 +3633,8 @@ class User implements IDBAccessObject {
                if ( !$dbw->affectedRows() ) {
                        // User was changed in the meantime or loaded with stale data
                        MWExceptionHandler::logException( new MWException(
-                               "CAS update failed on user_touched for user ID '{$this->mId}'."
+                               "CAS update failed on user_touched for user ID '{$this->mId}';" .
+                               "the version of the user to be saved is older than the current version."
                        ) );
                        // Maybe the problem was a missed cache update; clear it to be safe
                        $this->clearSharedCache();
index 6c84bbd..dbcb848 100644 (file)
  * An action that just pass the request to Special:RevisionDelete
  *
  * @ingroup Actions
- * @deprecated since 1.26 This class has been replaced by SpecialPageAction, but
+ * @deprecated since 1.25 This class has been replaced by SpecialPageAction, but
  * you really shouldn't have been using it outside core in the first place
  */
 class RevisiondeleteAction extends FormlessAction {
        public function __construct( Page $page, IContextSource $context = null ) {
-               wfDeprecated( 'RevisiondeleteAction class', '1.26' );
+               wfDeprecated( 'RevisiondeleteAction class', '1.25' );
                parent::__construct( $page, $context );
        }
 
index 3c8a21e..9b72163 100644 (file)
@@ -22,7 +22,7 @@
  * An action that just passes the request to the relevant special page
  *
  * @ingroup Actions
- * @since 1.26
+ * @since 1.25
  */
 class SpecialPageAction extends FormlessAction {
 
index f4f2c8c..143fc0f 100644 (file)
@@ -472,11 +472,17 @@ abstract class ApiBase extends ContextSource {
        }
 
        /**
-        * Get the result data array (read-only)
-        * @return array
+        * Get the error formatter
+        * @return ApiErrorFormatter
         */
-       public function getResultData() {
-               return $this->getResult()->getData();
+       public function getErrorFormatter() {
+               // Main module has getErrorFormatter() method overridden
+               // Safety - avoid infinite loop:
+               if ( $this->isMain() ) {
+                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+               }
+
+               return $this->getMain()->getErrorFormatter();
        }
 
        /**
@@ -491,6 +497,34 @@ abstract class ApiBase extends ContextSource {
                return $this->mSlaveDB;
        }
 
+       /**
+        * Get the continuation manager
+        * @return ApiContinuationManager|null
+        */
+       public function getContinuationManager() {
+               // Main module has getContinuationManager() method overridden
+               // Safety - avoid infinite loop:
+               if ( $this->isMain() ) {
+                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+               }
+
+               return $this->getMain()->getContinuationManager();
+       }
+
+       /**
+        * Set the continuation manager
+        * @param ApiContinuationManager|null
+        */
+       public function setContinuationManager( $manager ) {
+               // Main module has setContinuationManager() method overridden
+               // Safety - avoid infinite loop:
+               if ( $this->isMain() ) {
+                       ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+               }
+
+               $this->getMain()->setContinuationManager( $manager );
+       }
+
        /**@}*/
 
        /************************************************************************//**
@@ -870,7 +904,7 @@ abstract class ApiBase extends ContextSource {
                                                        $value = $this->getMain()->canApiHighLimits()
                                                                ? $paramSettings[self::PARAM_MAX2]
                                                                : $paramSettings[self::PARAM_MAX];
-                                                       $this->getResult()->setParsedLimit( $this->getModuleName(), $value );
+                                                       $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
                                                } else {
                                                        $value = intval( $value );
                                                        $this->validateLimit(
@@ -1246,28 +1280,8 @@ abstract class ApiBase extends ContextSource {
         * @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 );
+               $msg = new ApiRawMessage( $warning, 'warning' );
+               $this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg );
        }
 
        /**
@@ -2809,6 +2823,15 @@ abstract class ApiBase extends ContextSource {
                return 0;
        }
 
+       /**
+        * Get the result data array (read-only)
+        * @deprecated since 1.25, use $this->getResult() methods instead
+        * @return array
+        */
+       public function getResultData() {
+               return $this->getResult()->getData();
+       }
+
        /**@}*/
 }
 
index ce256a6..2300912 100644 (file)
@@ -72,7 +72,7 @@ class ApiComparePages extends ApiBase {
                        );
                }
 
-               ApiResult::setContent( $vals, $difftext );
+               ApiResult::setContentValue( $vals, 'body', $difftext );
 
                $this->getResult()->addValue( null, $this->getModuleName(), $vals );
        }
diff --git a/includes/api/ApiContinuationManager.php b/includes/api/ApiContinuationManager.php
new file mode 100644 (file)
index 0000000..dea1cf4
--- /dev/null
@@ -0,0 +1,238 @@
+<?php
+/**
+ * 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 manages continuation state.
+ * @since 1.25 this is no longer a subclass of ApiBase
+ * @ingroup API
+ */
+class ApiContinuationManager {
+       private $source;
+
+       private $allModules = array();
+       private $generatedModules = array();
+
+       private $continuationData = array();
+       private $generatorContinuationData = array();
+
+       private $generatorParams = array();
+       private $generatorDone = false;
+
+       /**
+        * @param ApiBase $module Module starting the continuation
+        * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
+        * @param array $generatedModules Names of modules that depend on the generator
+        */
+       public function __construct(
+               ApiBase $module, array $allModules = array(), array $generatedModules = array()
+       ) {
+               $this->source = get_class( $module );
+               $request = $module->getRequest();
+
+               $this->generatedModules = $generatedModules
+                       ? array_combine( $generatedModules, $generatedModules )
+                       : array();
+
+               $skip = array();
+               $continue = $request->getVal( 'continue', '' );
+               if ( $continue !== '' ) {
+                       $continue = explode( '||', $continue );
+                       if ( count( $continue ) !== 2 ) {
+                               throw new UsageException(
+                                       'Invalid continue param. You should pass the original value returned by the previous query',
+                                       'badcontinue'
+                               );
+                       }
+                       $this->generatorDone = ( $continue[0] === '-' );
+                       $skip = explode( '|', $continue[1] );
+                       if ( !$this->generatorDone ) {
+                               $params = explode( '|', $continue[0] );
+                               if ( $params ) {
+                                       $this->generatorParams = array_intersect_key(
+                                               $request->getValues(),
+                                               array_flip( $params )
+                                       );
+                               }
+                       } else {
+                               // When the generator is complete, don't run any modules that
+                               // depend on it.
+                               $skip += $this->generatedModules;
+                       }
+               }
+
+               foreach ( $allModules as $module ) {
+                       $name = $module->getModuleName();
+                       if ( in_array( $name, $skip, true ) ) {
+                               $this->allModules[$name] = false;
+                               // Prevent spurious "unused parameter" warnings
+                               $module->extractRequestParams();
+                       } else {
+                               $this->allModules[$name] = $module;
+                       }
+               }
+       }
+
+       /**
+        * Get the class that created this manager
+        * @return string
+        */
+       public function getSource() {
+               return $this->source;
+       }
+
+       /**
+        * Is the generator done?
+        * @return bool
+        */
+       public function isGeneratorDone() {
+               return $this->generatorDone;
+       }
+
+       /**
+        * Get the list of modules that should actually be run
+        * @return ApiBase[]
+        */
+       public function getRunModules() {
+               return array_values( array_filter( $this->allModules ) );
+       }
+
+       /**
+        * Set the continuation parameter for a module
+        * @param ApiBase $module
+        * @param string $paramName
+        * @param string|array $paramValue
+        * @throws UnexpectedValueException
+        */
+       public function addContinueParam( ApiBase $module, $paramName, $paramValue ) {
+               $name = $module->getModuleName();
+               if ( !isset( $this->allModules[$name] ) ) {
+                       throw new UnexpectedValueException(
+                               "Module '$name' called " . __METHOD__ .
+                                       ' but was not passed to ' . __CLASS__ . '::__construct'
+                       );
+               }
+               if ( !$this->allModules[$name] ) {
+                       throw new UnexpectedValueException(
+                               "Module '$name' was not supposed to have been executed, but " .
+                                       'it was executed anyway'
+                       );
+               }
+               $paramName = $module->encodeParamName( $paramName );
+               if ( is_array( $paramValue ) ) {
+                       $paramValue = join( '|', $paramValue );
+               }
+               $this->continuationData[$name][$paramName] = $paramValue;
+       }
+
+       /**
+        * Set the continuation parameter for the generator module
+        * @param ApiBase $module
+        * @param string $paramName
+        * @param string|array $paramValue
+        */
+       public function addGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
+               $name = $module->getModuleName();
+               $paramName = $module->encodeParamName( $paramName );
+               if ( is_array( $paramValue ) ) {
+                       $paramValue = join( '|', $paramValue );
+               }
+               $this->generatorContinuationData[$name][$paramName] = $paramValue;
+       }
+
+       /**
+        * Fetch raw continuation data
+        * @return array
+        */
+       public function getRawContinuation() {
+               return array_merge_recursive( $this->continuationData, $this->generatorContinuationData );
+       }
+
+       /**
+        * Fetch continuation result data
+        * @return array Array( (array)$data, (bool)$batchcomplete )
+        */
+       public function getContinuation() {
+               $data = array();
+               $batchcomplete = false;
+
+               $finishedModules = array_diff(
+                       array_keys( $this->allModules ),
+                       array_keys( $this->continuationData )
+               );
+
+               // First, grab the non-generator-using continuation data
+               $continuationData = array_diff_key( $this->continuationData, $this->generatedModules );
+               foreach ( $continuationData as $module => $kvp ) {
+                       $data += $kvp;
+               }
+
+               // Next, handle the generator-using continuation data
+               $continuationData = array_intersect_key( $this->continuationData, $this->generatedModules );
+               if ( $continuationData ) {
+                       // Some modules are unfinished: include those params, and copy
+                       // the generator params.
+                       foreach ( $continuationData as $module => $kvp ) {
+                               $data += $kvp;
+                       }
+                       $data += $this->generatorParams;
+                       $generatorKeys = join( '|', array_keys( $this->generatorParams ) );
+               } elseif ( $this->generatorContinuationData ) {
+                       // All the generator-using modules are complete, but the
+                       // generator isn't. Continue the generator and restart the
+                       // generator-using modules
+                       $generatorParams = array();
+                       foreach ( $this->generatorContinuationData as $kvp ) {
+                               $generatorParams += $kvp;
+                       }
+                       $data += $generatorParams;
+                       $finishedModules = array_diff( $finishedModules, $this->generatedModules );
+                       $generatorKeys = join( '|', array_keys( $generatorParams ) );
+                       $batchcomplete = true;
+               } else {
+                       // Generator and prop modules are all done. Mark it so.
+                       $generatorKeys = '-';
+                       $batchcomplete = true;
+               }
+
+               // Set 'continue' if any continuation data is set or if the generator
+               // still needs to run
+               if ( $data || $generatorKeys !== '-' ) {
+                       $data['continue'] = $generatorKeys . '||' . join( '|', $finishedModules );
+               }
+
+               return array( $data, $batchcomplete );
+       }
+
+       /**
+        * Store the continuation data into the result
+        * @param ApiResult $result
+        */
+       public function setContinuationIntoResult( ApiResult $result ) {
+               list( $data, $batchcomplete ) = $this->getContinuation();
+               if ( $data ) {
+                       $result->addValue( null, 'continue', $data,
+                               ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+               }
+               if ( $batchcomplete ) {
+                       $result->addValue( null, 'batchcomplete', '',
+                               ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+               }
+       }
+}
index b56a244..455540b 100644 (file)
@@ -152,9 +152,9 @@ class ApiCreateAccount extends ApiBase {
                        $warnings = $status->getErrorsByType( 'warning' );
                        if ( $warnings ) {
                                foreach ( $warnings as &$warning ) {
-                                       $apiResult->setIndexedTagName( $warning['params'], 'param' );
+                                       ApiResult::setIndexedTagName( $warning['params'], 'param' );
                                }
-                               $apiResult->setIndexedTagName( $warnings, 'warning' );
+                               ApiResult::setIndexedTagName( $warnings, 'warning' );
                                $result['warnings'] = $warnings;
                        }
                } else {
index 8c7d31d..56c67e0 100644 (file)
@@ -82,7 +82,7 @@ class ApiEditPage extends ApiBase {
                                        $titleObj = $newTitle;
                                }
 
-                               $apiResult->setIndexedTagName( $redirValues, 'r' );
+                               ApiResult::setIndexedTagName( $redirValues, 'r' );
                                $apiResult->addValue( null, 'redirects', $redirValues );
 
                                // Since the page changed, update $pageObj
diff --git a/includes/api/ApiErrorFormatter.php b/includes/api/ApiErrorFormatter.php
new file mode 100644 (file)
index 0000000..9414329
--- /dev/null
@@ -0,0 +1,303 @@
+<?php
+/**
+ * This file contains the ApiErrorFormatter definition, plus implementations of
+ * specific formatters.
+ *
+ * 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
+ */
+
+/**
+ * Formats errors and warnings for the API, and add them to the associated
+ * ApiResult.
+ * @since 1.25
+ * @ingroup API
+ */
+class ApiErrorFormatter {
+       /** @var Title Dummy title to silence warnings from MessageCache::parse() */
+       private static $dummyTitle = null;
+
+       /** @var ApiResult */
+       protected $result;
+
+       /** @var Language */
+       protected $lang;
+       protected $useDB = false;
+       protected $format = 'none';
+
+       /**
+        * @param ApiResult $result Into which data will be added
+        * @param Language $lang Used for i18n
+        * @param string $format
+        *  - text: Error message as wikitext
+        *  - html: Error message as HTML
+        *  - raw: Raw message key and parameters, no human-readable text
+        *  - none: Code and data only, no human-readable text
+        * @param bool $useDB Whether to use local translations for errors and warnings.
+        */
+       public function __construct( ApiResult $result, Language $lang, $format, $useDB = false ) {
+               $this->result = $result;
+               $this->lang = $lang;
+               $this->useDB = $useDB;
+               $this->format = $format;
+       }
+
+       /**
+        * Fetch a dummy title to set on Messages
+        * @return Title
+        */
+       protected function getDummyTitle() {
+               if ( self::$dummyTitle === null ) {
+                       self::$dummyTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ );
+               }
+               return self::$dummyTitle;
+       }
+
+       /**
+        * Add a warning to the result
+        * @param string $moduleName
+        * @param MessageSpecifier|array|string $msg i18n message for the warning
+        * @param string $code Machine-readable code for the warning. Defaults as
+        *   for IApiMessage::getApiCode().
+        * @param array $data Machine-readable data for the warning, if any.
+        *   Uses IApiMessage::getApiData() if $msg implements that interface.
+        */
+       public function addWarning( $moduleName, $msg, $code = null, $data = null ) {
+               $msg = ApiMessage::create( $msg, $code, $data )
+                       ->inLanguage( $this->lang )
+                       ->title( $this->getDummyTitle() )
+                       ->useDatabase( $this->useDB );
+               $this->addWarningOrError( 'warning', $moduleName, $msg );
+       }
+
+       /**
+        * Add an error to the result
+        * @param string $moduleName
+        * @param MessageSpecifier|array|string $msg i18n message for the error
+        * @param string $code Machine-readable code for the warning. Defaults as
+        *   for IApiMessage::getApiCode().
+        * @param array $data Machine-readable data for the warning, if any.
+        *   Uses IApiMessage::getApiData() if $msg implements that interface.
+        */
+       public function addError( $moduleName, $msg, $code = null, $data = null ) {
+               $msg = ApiMessage::create( $msg, $code, $data )
+                       ->inLanguage( $this->lang )
+                       ->title( $this->getDummyTitle() )
+                       ->useDatabase( $this->useDB );
+               $this->addWarningOrError( 'error', $moduleName, $msg );
+       }
+
+       /**
+        * Add warnings and errors from a Status object to the result
+        * @param string $moduleName
+        * @param Status $status
+        * @param string[] $types 'warning' and/or 'error'
+        */
+       public function addMessagesFromStatus(
+               $moduleName, Status $status, $types = array( 'warning', 'error' )
+       ) {
+               if ( $status->isGood() || !$status->errors ) {
+                       return;
+               }
+
+               $types = (array)$types;
+               foreach ( $status->errors as $error ) {
+                       if ( !in_array( $error['type'], $types, true ) ) {
+                               continue;
+                       }
+
+                       if ( $error['type'] === 'error' ) {
+                               $tag = 'error';
+                       } else {
+                               // Assume any unknown type is a warning
+                               $tag = 'warning';
+                       }
+
+                       if ( is_array( $error ) && isset( $error['message'] ) ) {
+                               // Normal case
+                               if ( $error['message'] instanceof Message ) {
+                                       $msg = ApiMessage::create( $error['message'], null, array() );
+                               } else {
+                                       $args = isset( $error['params'] ) ? $error['params'] : array();
+                                       array_unshift( $args, $error['message'] );
+                                       $error += array( 'params' => array() );
+                                       $msg = ApiMessage::create( $args, null, array() );
+                               }
+                       } elseif ( is_array( $error ) ) {
+                               // Weird case handled by Message::getErrorMessage
+                               $msg = ApiMessage::create( $error, null, array() );
+                       } else {
+                               // Another weird case handled by Message::getErrorMessage
+                               $msg = ApiMessage::create( $error, null, array() );
+                       }
+
+                       $msg->inLanguage( $this->lang )
+                               ->title( $this->getDummyTitle() )
+                               ->useDatabase( $this->useDB );
+                       $this->addWarningOrError( $tag, $moduleName, $msg );
+               }
+       }
+
+       /**
+        * Format messages from a Status as an array
+        * @param Status $status
+        * @param string $type 'warning' or 'error'
+        * @param string|null $format
+        * @return array
+        */
+       public function arrayFromStatus( Status $status, $type = 'error', $format = null ) {
+               if ( $status->isGood() || !$status->errors ) {
+                       return array();
+               }
+
+               $result = new ApiResult( 1e6 );
+               $formatter = new ApiErrorFormatter(
+                       $result, $this->lang, $format ?: $this->format, $this->useDB
+               );
+               $formatter->addMessagesFromStatus( 'dummy', $status, array( $type ) );
+               switch ( $type ) {
+                       case 'error':
+                               return (array)$result->getResultData( array( 'errors', 'dummy' ) );
+                       case 'warning':
+                               return (array)$result->getResultData( array( 'warnings', 'dummy' ) );
+               }
+       }
+
+       /**
+        * Actually add the warning or error to the result
+        * @param string $tag 'warning' or 'error'
+        * @param string $moduleName
+        * @param ApiMessage|ApiRawMessage $msg
+        */
+       protected function addWarningOrError( $tag, $moduleName, $msg ) {
+               $value = array( 'code' => $msg->getApiCode() );
+               switch ( $this->format ) {
+                       case 'wikitext':
+                               $value += array(
+                                       'text' => $msg->text(),
+                                       ApiResult::META_CONTENT => 'text',
+                               );
+                               break;
+
+                       case 'html':
+                               $value += array(
+                                       'html' => $msg->parse(),
+                                       ApiResult::META_CONTENT => 'html',
+                               );
+                               break;
+
+                       case 'raw':
+                               $value += array(
+                                       'message' => $msg->getKey(),
+                                       'params' => $msg->getParams(),
+                               );
+                               ApiResult::setIndexedTagName( $value['params'], 'param' );
+                               break;
+
+                       case 'none':
+                               break;
+               }
+               $value += $msg->getApiData();
+
+               $path = array( $tag . 's', $moduleName );
+               $existing = $this->result->getResultData( $path );
+               if ( $existing === null || !in_array( $value, $existing ) ) {
+                       $flags = ApiResult::NO_SIZE_CHECK;
+                       if ( $existing === null ) {
+                               $flags |= ApiResult::ADD_ON_TOP;
+                       }
+                       $this->result->addValue( $path, null, $value, $flags );
+                       $this->result->addIndexedTagName( $path, $tag );
+               }
+       }
+}
+
+/**
+ * Format errors and warnings in the old style, for backwards compatibility.
+ * @since 1.25
+ * @deprecated Only for backwards compatibility, do not use
+ * @ingroup API
+ */
+class ApiErrorFormatter_BackCompat extends ApiErrorFormatter {
+       /**
+        * @param ApiResult $result Into which data will be added
+        */
+       public function __construct( ApiResult $result ) {
+               parent::__construct( $result, Language::factory( 'en' ), 'none', false );
+       }
+
+       public function arrayFromStatus( Status $status, $type = 'error', $format = null ) {
+               if ( $status->isGood() || !$status->errors ) {
+                       return array();
+               }
+
+               $result = array();
+               foreach ( $status->getErrorsByType( $type ) as $error ) {
+                       if ( $error['message'] instanceof Message ) {
+                               $error = array(
+                                       'message' => $error['message']->getKey(),
+                                       'params' => $error['message']->getParams(),
+                               ) + $error;
+                       }
+                       ApiResult::setIndexedTagName( $error['params'], 'param' );
+                       $result[] = $error;
+               }
+               ApiResult::setIndexedTagName( $result, $type );
+
+               return $result;
+       }
+
+       protected function addWarningOrError( $tag, $moduleName, $msg ) {
+               $value = $msg->plain();
+
+               if ( $tag === 'error' ) {
+                       // In BC mode, only one error
+                       $code = $msg->getApiCode();
+                       if ( isset( ApiBase::$messageMap[$code] ) ) {
+                               // Backwards compatibility
+                               $code = ApiBase::$messageMap[$code]['code'];
+                       }
+
+                       $value = array(
+                               'code' => $code,
+                               'info' => $value,
+                       ) + $msg->getApiData();
+                       $this->result->addValue( null, 'error', $value,
+                               ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+               } else {
+                       // Don't add duplicate warnings
+                       $tag .= 's';
+                       $path = array( $tag, $moduleName );
+                       $oldWarning = $this->result->getResultData( array( $tag, $moduleName, $tag ) );
+                       if ( $oldWarning !== null ) {
+                               $warnPos = strpos( $oldWarning, $value );
+                               // If $value was found in $oldWarning, check if it starts at 0 or after "\n"
+                               if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
+                                       // Check if $value is followed by "\n" or the end of the $oldWarning
+                                       $warnPos += strlen( $value );
+                                       if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
+                                               return;
+                                       }
+                               }
+                               // If there is a warning already, append it to the existing one
+                               $value = "$oldWarning\n$value";
+                       }
+                       $this->result->addContentValue( $path, $tag, $value,
+                               ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+               }
+       }
+}
index 4a5afb9..5c7717f 100644 (file)
@@ -97,7 +97,7 @@ class ApiExpandTemplates extends ApiBase {
                        } else {
                                // the old way
                                $xml_result = array();
-                               ApiResult::setContent( $xml_result, $xml );
+                               ApiResult::setContentValue( $xml_result, 'xml', $xml );
                                $result->addValue( null, 'parsetree', $xml_result );
                        }
                }
@@ -110,7 +110,7 @@ class ApiExpandTemplates extends ApiBase {
                        $wikitext = $wgParser->preprocess( $params['text'], $title_obj, $options, $revid, $frame );
                        if ( $params['prop'] === null ) {
                                // the old way
-                               ApiResult::setContent( $retval, $wikitext );
+                               ApiResult::setContentValue( $retval, 'wikitext', $wikitext );
                        } else {
                                if ( isset( $prop['categories'] ) ) {
                                        $categories = $wgParser->getOutput()->getCategories();
@@ -119,10 +119,10 @@ class ApiExpandTemplates extends ApiBase {
                                                foreach ( $categories as $category => $sortkey ) {
                                                        $entry = array();
                                                        $entry['sortkey'] = $sortkey;
-                                                       ApiResult::setContent( $entry, $category );
+                                                       ApiResult::setContentValue( $entry, 'category', $category );
                                                        $categories_result[] = $entry;
                                                }
-                                               $result->setIndexedTagName( $categories_result, 'category' );
+                                               ApiResult::setIndexedTagName( $categories_result, 'category' );
                                                $retval['categories'] = $categories_result;
                                        }
                                }
@@ -133,10 +133,10 @@ class ApiExpandTemplates extends ApiBase {
                                                foreach ( $properties as $name => $value ) {
                                                        $entry = array();
                                                        $entry['name'] = $name;
-                                                       ApiResult::setContent( $entry, $value );
+                                                       ApiResult::setContentValue( $entry, 'value', $value );
                                                        $properties_result[] = $entry;
                                                }
-                                               $result->setIndexedTagName( $properties_result, 'property' );
+                                               ApiResult::setIndexedTagName( $properties_result, 'property' );
                                                $retval['properties'] = $properties_result;
                                        }
                                }
@@ -151,7 +151,7 @@ class ApiExpandTemplates extends ApiBase {
                                }
                        }
                }
-               $result->setSubelements( $retval, array( 'wikitext', 'parsetree' ) );
+               ApiResult::setSubelementsList( $retval, array( 'wikitext', 'parsetree' ) );
                $result->addValue( null, $this->getModuleName(), $retval );
        }
 
index bfa750b..d1beef8 100644 (file)
@@ -112,11 +112,12 @@ class ApiFeedWatchlist extends ApiBase {
                        $module = new ApiMain( $fauxReq );
                        $module->execute();
 
-                       // Get data array
-                       $data = $module->getResultData();
-
+                       $data = $module->getResult()->getResultData( array( 'query', 'watchlist' ) );
                        $feedItems = array();
-                       foreach ( (array)$data['query']['watchlist'] as $info ) {
+                       foreach ( (array)$data as $key => $info ) {
+                               if ( ApiResult::isMetadataKey( $key ) ) {
+                                       continue;
+                               }
                                $feedItem = $this->createFeedItem( $info );
                                if ( $feedItem ) {
                                        $feedItems[] = $feedItem;
index 61966e5..5517ee0 100644 (file)
@@ -61,7 +61,7 @@ class ApiFileRevert extends ApiBase {
                } else {
                        $result = array(
                                'result' => 'Failure',
-                               'errors' => $this->getResult()->convertStatusToArray( $status ),
+                               'errors' => $this->getErrorFormatter()->arrayFromStatus( $status ),
                        );
                }
 
index 7bbd968..26eac08 100644 (file)
@@ -60,14 +60,6 @@ abstract class ApiFormatBase extends ApiBase {
         */
        abstract public function getMimeType();
 
-       /**
-        * Whether this formatter needs raw data such as _element tags
-        * @return bool
-        */
-       public function getNeedsRawData() {
-               return false;
-       }
-
        /**
         * Get the internal format name
         * @return string
@@ -350,6 +342,22 @@ abstract class ApiFormatBase extends ApiBase {
        public function setBufferResult( $value ) {
        }
 
+       /**
+        * Formerly indicated whether the formatter needed metadata from ApiResult.
+        *
+        * ApiResult previously (indirectly) used this to decide whether to add
+        * metadata or to ignore calls to metadata-setting methods, which
+        * unfortunately made several methods that should have been static have to
+        * be dynamic instead. Now ApiResult always stores metadata and formatters
+        * are required to ignore it or filter it out.
+        *
+        * @deprecated since 1.25
+        * @return bool
+        */
+       public function getNeedsRawData() {
+               return false;
+       }
+
        /**@}*/
 }
 
index 273e205..7d359ad 100644 (file)
@@ -40,7 +40,12 @@ class ApiFormatDbg extends ApiFormatBase {
 
        public function execute() {
                $this->markDeprecated();
-               $this->printText( var_export( $this->getResultData(), true ) );
+               $data = $this->getResult()->getResultData( null, array(
+                       'BC' => array(),
+                       'Types' => array(),
+                       'Strip' => 'all',
+               ) );
+               $this->printText( var_export( $data, true ) );
        }
 
        public function isDeprecated() {
index 7ef8960..f34e1ae 100644 (file)
@@ -40,8 +40,13 @@ class ApiFormatDump extends ApiFormatBase {
 
        public function execute() {
                $this->markDeprecated();
+               $data = $this->getResult()->getResultData( null, array(
+                       'BC' => array(),
+                       'Types' => array(),
+                       'Strip' => 'all',
+               ) );
                ob_start();
-               var_dump( $this->getResultData() );
+               var_dump( $data );
                $result = ob_get_contents();
                ob_end_clean();
                $this->printText( $result );
index 3f53ed4..00747ee 100644 (file)
@@ -46,8 +46,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
                // 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 );
+               $result->addValue( null, '_feed', $feed, ApiResult::NO_VALIDATE );
+               $result->addValue( null, '_feeditems', $feedItems, ApiResult::NO_VALIDATE );
        }
 
        /**
@@ -89,7 +89,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
                        return;
                }
 
-               $data = $this->getResultData();
+               $data = $this->getResult()->getResultData();
                if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
                        $data['_feed']->httpHeaders();
                } else {
@@ -104,7 +104,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
         * $result['_feeditems'] - an array of FeedItem instances
         */
        public function execute() {
-               $data = $this->getResultData();
+               $data = $this->getResult()->getResultData();
                if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
                        $feed = $data['_feed'];
                        $items = $data['_feeditems'];
index 966e82d..41d7051 100644 (file)
  */
 class ApiFormatJson extends ApiFormatBase {
 
-       private $mIsRaw;
+       private $isRaw;
 
        public function __construct( ApiMain $main, $format ) {
                parent::__construct( $main, $format );
-               $this->mIsRaw = ( $format === 'rawfm' );
+               $this->isRaw = ( $format === 'rawfm' );
        }
 
        public function getMimeType() {
@@ -47,8 +47,11 @@ class ApiFormatJson extends ApiFormatBase {
                return 'application/json';
        }
 
+       /**
+        * @deprecated since 1.25
+        */
        public function getNeedsRawData() {
-               return $this->mIsRaw;
+               return $this->isRaw;
        }
 
        /**
@@ -62,11 +65,37 @@ class ApiFormatJson extends ApiFormatBase {
 
        public function execute() {
                $params = $this->extractRequestParams();
-               $json = FormatJson::encode(
-                       $this->getResultData(),
-                       $this->getIsHtml(),
-                       $params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK
-               );
+
+               $opt = 0;
+               if ( $this->isRaw ) {
+                       $opt |= FormatJson::ALL_OK;
+                       $transform = array();
+               } else {
+                       switch ( $params['formatversion'] ) {
+                               case 1:
+                                       $opt |= $params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK;
+                                       $transform = array(
+                                               'BC' => array(),
+                                               'Types' => array( 'AssocAsObject' => true ),
+                                               'Strip' => 'all',
+                                       );
+                                       break;
+
+                               case 2:
+                               case 'latest':
+                                       $opt |= $params['ascii'] ? FormatJson::XMLMETA_OK : FormatJson::ALL_OK;
+                                       $transform = array(
+                                               'Types' => array( 'AssocAsObject' => true ),
+                                               'Strip' => 'all',
+                                       );
+                                       break;
+
+                               default:
+                                       self::dieUsage( __METHOD__ . ': Unknown value for \'formatversion\'' );
+                       }
+               }
+               $data = $this->getResult()->getResultData( null, $transform );
+               $json = FormatJson::encode( $data, $this->getIsHtml(), $opt );
 
                // Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
                // Flash, but what it does isn't friendly for the API, so we need to
@@ -89,7 +118,11 @@ class ApiFormatJson extends ApiFormatBase {
        }
 
        public function getAllowedParams() {
-               return array(
+               if ( $this->isRaw ) {
+                       return array();
+               }
+
+               $ret = array(
                        'callback' => array(
                                ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-callback',
                        ),
@@ -97,6 +130,16 @@ class ApiFormatJson extends ApiFormatBase {
                                ApiBase::PARAM_DFLT => false,
                                ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-utf8',
                        ),
+                       'ascii' => array(
+                               ApiBase::PARAM_DFLT => false,
+                               ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-ascii',
+                       ),
+                       'formatversion' => array(
+                               ApiBase::PARAM_TYPE => array( 1, 2, 'latest' ),
+                               ApiBase::PARAM_DFLT => 1,
+                               ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-formatversion',
+                       ),
                );
+               return $ret;
        }
 }
index a4b4a11..a6a6f32 100644 (file)
@@ -35,7 +35,29 @@ class ApiFormatPhp extends ApiFormatBase {
        }
 
        public function execute() {
-               $text = serialize( $this->getResultData() );
+               $params = $this->extractRequestParams();
+
+               switch ( $params['formatversion'] ) {
+                       case 1:
+                               $transforms = array(
+                                       'BC' => array(),
+                                       'Types' => array(),
+                                       'Strip' => 'all',
+                               );
+                               break;
+
+                       case 2:
+                       case 'latest':
+                               $transforms = array(
+                                       'Types' => array(),
+                                       'Strip' => 'all',
+                               );
+                               break;
+
+                       default:
+                               self::dieUsage( __METHOD__ . ': Unknown value for \'formatversion\'' );
+               }
+               $text = serialize( $this->getResult()->getResultData( null, $transforms ) );
 
                // Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
                // Flash, but what it does isn't friendly for the API. There's nothing
@@ -53,4 +75,15 @@ class ApiFormatPhp extends ApiFormatBase {
 
                $this->printText( $text );
        }
+
+       public function getAllowedParams() {
+               $ret = array(
+                       'formatversion' => array(
+                               ApiBase::PARAM_TYPE => array( 1, 2, 'latest' ),
+                               ApiBase::PARAM_DFLT => 1,
+                               ApiBase::PARAM_HELP_MSG => 'apihelp-php-param-formatversion',
+                       ),
+               );
+               return $ret;
+       }
 }
index 81d2f4f..7bb2453 100644 (file)
@@ -42,7 +42,7 @@ class ApiFormatRaw extends ApiFormatBase {
        }
 
        public function getMimeType() {
-               $data = $this->getResultData();
+               $data = $this->getResult()->getResultData();
 
                if ( isset( $data['error'] ) ) {
                        return $this->errorFallback->getMimeType();
@@ -56,7 +56,7 @@ class ApiFormatRaw extends ApiFormatBase {
        }
 
        public function initPrinter( $unused = false ) {
-               $data = $this->getResultData();
+               $data = $this->getResult()->getResultData();
                if ( isset( $data['error'] ) ) {
                        $this->errorFallback->initPrinter( $unused );
                } else {
@@ -65,7 +65,7 @@ class ApiFormatRaw extends ApiFormatBase {
        }
 
        public function closePrinter() {
-               $data = $this->getResultData();
+               $data = $this->getResult()->getResultData();
                if ( isset( $data['error'] ) ) {
                        $this->errorFallback->closePrinter();
                } else {
@@ -74,7 +74,7 @@ class ApiFormatRaw extends ApiFormatBase {
        }
 
        public function execute() {
-               $data = $this->getResultData();
+               $data = $this->getResult()->getResultData();
                if ( isset( $data['error'] ) ) {
                        $this->errorFallback->execute();
                        return;
index 505b259..e739d5a 100644 (file)
@@ -40,7 +40,12 @@ class ApiFormatTxt extends ApiFormatBase {
 
        public function execute() {
                $this->markDeprecated();
-               $this->printText( print_r( $this->getResultData(), true ) );
+               $data = $this->getResult()->getResultData( null, array(
+                       'BC' => array(),
+                       'Types' => array(),
+                       'Strip' => 'all',
+               ) );
+               $this->printText( print_r( $data, true ) );
        }
 
        public function isDeprecated() {
index 8662a64..c18353f 100644 (file)
@@ -38,8 +38,20 @@ class ApiFormatWddx extends ApiFormatBase {
        public function execute() {
                $this->markDeprecated();
 
+               $data = $this->getResult()->getResultData( null, array(
+                       'BC' => array(),
+                       'Types' => array( 'AssocAsObject' => true ),
+                       'Strip' => 'all',
+               ) );
+
                if ( !$this->getIsHtml() && !static::useSlowPrinter() ) {
-                       $this->printText( wddx_serialize_value( $this->getResultData() ) );
+                       $txt = wddx_serialize_value( $data );
+                       $txt = str_replace(
+                               '<struct><var name=\'php_class_name\'><string>stdClass</string></var>',
+                               '<struct>',
+                               $txt
+                       );
+                       $this->printText( $txt );
                } else {
                        // Don't do newlines and indentation if we weren't asked
                        // for pretty output
@@ -49,7 +61,7 @@ class ApiFormatWddx extends ApiFormatBase {
                        $this->printText( "<wddxPacket version=\"1.0\">$nl" );
                        $this->printText( "$indstr<header />$nl" );
                        $this->printText( "$indstr<data>$nl" );
-                       $this->slowWddxPrinter( $this->getResultData(), 4 );
+                       $this->slowWddxPrinter( $data, 4 );
                        $this->printText( "$indstr</data>$nl" );
                        $this->printText( "</wddxPacket>$nl" );
                }
@@ -102,34 +114,37 @@ class ApiFormatWddx extends ApiFormatBase {
                $indstr = ( $this->getIsHtml() ? str_repeat( ' ', $indent ) : '' );
                $indstr2 = ( $this->getIsHtml() ? str_repeat( ' ', $indent + 2 ) : '' );
                $nl = ( $this->getIsHtml() ? "\n" : '' );
+
                if ( is_array( $elemValue ) ) {
-                       // Check whether we've got an associative array (<struct>)
-                       // or a regular array (<array>)
                        $cnt = count( $elemValue );
-                       if ( $cnt == 0 || array_keys( $elemValue ) === range( 0, $cnt - 1 ) ) {
-                               // Regular array
-                               $this->printText( $indstr . Xml::element( 'array', array(
-                                       'length' => $cnt ), null ) . $nl );
-                               foreach ( $elemValue as $subElemValue ) {
-                                       $this->slowWddxPrinter( $subElemValue, $indent + 2 );
-                               }
-                               $this->printText( "$indstr</array>$nl" );
-                       } else {
-                               // Associative array (<struct>)
-                               $this->printText( "$indstr<struct>$nl" );
-                               foreach ( $elemValue as $subElemName => $subElemValue ) {
-                                       $this->printText( $indstr2 . Xml::element( 'var', array(
-                                               'name' => $subElemName
-                                       ), null ) . $nl );
-                                       $this->slowWddxPrinter( $subElemValue, $indent + 4 );
-                                       $this->printText( "$indstr2</var>$nl" );
-                               }
-                               $this->printText( "$indstr</struct>$nl" );
+                       if ( $cnt != 0 && array_keys( $elemValue ) !== range( 0, $cnt - 1 ) ) {
+                               $elemValue = (object)$elemValue;
+                       }
+               }
+
+               if ( is_array( $elemValue ) ) {
+                       // Regular array
+                       $this->printText( $indstr . Xml::element( 'array', array(
+                               'length' => count( $elemValue ) ), null ) . $nl );
+                       foreach ( $elemValue as $subElemValue ) {
+                               $this->slowWddxPrinter( $subElemValue, $indent + 2 );
+                       }
+                       $this->printText( "$indstr</array>$nl" );
+               } elseif ( is_object( $elemValue ) ) {
+                       // Associative array (<struct>)
+                       $this->printText( "$indstr<struct>$nl" );
+                       foreach ( $elemValue as $subElemName => $subElemValue ) {
+                               $this->printText( $indstr2 . Xml::element( 'var', array(
+                                       'name' => $subElemName
+                               ), null ) . $nl );
+                               $this->slowWddxPrinter( $subElemValue, $indent + 4 );
+                               $this->printText( "$indstr2</var>$nl" );
                        }
+                       $this->printText( "$indstr</struct>$nl" );
                } elseif ( is_int( $elemValue ) || is_float( $elemValue ) ) {
                        $this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl );
                } elseif ( is_string( $elemValue ) ) {
-                       $this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl );
+                       $this->printText( $indstr . Xml::element( 'string', null, $elemValue, false ) . $nl );
                } elseif ( is_bool( $elemValue ) ) {
                        $this->printText( $indstr . Xml::element( 'boolean',
                                array( 'value' => $elemValue ? 'true' : 'false' ) ) . $nl
index 7010dd6..dbd5645 100644 (file)
@@ -39,6 +39,9 @@ class ApiFormatXml extends ApiFormatBase {
                return 'text/xml';
        }
 
+       /**
+        * @deprecated since 1.25
+        */
        public function getNeedsRawData() {
                return true;
        }
@@ -56,18 +59,32 @@ class ApiFormatXml extends ApiFormatBase {
                if ( !is_null( $this->mXslt ) ) {
                        $this->addXslt();
                }
-               if ( $this->mIncludeNamespace ) {
+
+               $result = $this->getResult();
+               if ( $this->mIncludeNamespace && $result->getResultData( 'xmlns' ) === null ) {
                        // If the result data already contains an 'xmlns' namespace added
                        // for custom XML output types, it will override the one for the
                        // generic API results.
                        // This allows API output of other XML types like Atom, RSS, RSD.
-                       $data = $this->getResultData() + array( 'xmlns' => self::$namespace );
-               } else {
-                       $data = $this->getResultData();
+                       $result->addValue( null, 'xmlns', self::$namespace, ApiResult::NO_SIZE_CHECK );
                }
+               $data = $result->getResultData( null, array(
+                       'Custom' => function ( &$data, &$metadata ) {
+                               if ( isset( $metadata[ApiResult::META_TYPE] ) ) {
+                                       // We want to use non-BC for BCassoc to force outputting of _idx.
+                                       switch( $metadata[ApiResult::META_TYPE] ) {
+                                               case 'BCassoc':
+                                                       $metadata[ApiResult::META_TYPE] = 'assoc';
+                                                       break;
+                                       }
+                               }
+                       },
+                       'BC' => array( 'nobool', 'no*', 'nosub' ),
+                       'Types' => array( 'ArmorKVP' => '_name' ),
+               ) );
 
                $this->printText(
-                       self::recXmlPrint( $this->mRootElemName,
+                       static::recXmlPrint( $this->mRootElemName,
                                $data,
                                $this->getIsHtml() ? -2 : null
                        )
@@ -77,143 +94,185 @@ class ApiFormatXml extends ApiFormatBase {
        /**
         * This method takes an array and converts it to XML.
         *
-        * There are several noteworthy cases:
-        *
-        * If array contains a key '_element', then the code assumes that ALL
-        * other keys are not important and replaces them with the
-        * value['_element'].
-        *
-        * @par Example:
-        * @verbatim
-        * name='root', value = array( '_element'=>'page', 'x', 'y', 'z')
-        * @endverbatim
-        * creates:
-        * @verbatim
-        * <root>  <page>x</page>  <page>y</page>  <page>z</page> </root>
-        * @endverbatim
-        *
-        * If any of the array's element key is '*', then the code treats all
-        * other key->value pairs as attributes, and the value['*'] as the
-        * element's content.
-        *
-        * @par Example:
-        * @verbatim
-        * name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10)
-        * @endverbatim
-        * creates:
-        * @verbatim
-        * <root lang='en' id='10'>text</root>
-        * @endverbatim
-        *
-        * Finally neither key is found, all keys become element names, and values
-        * become element content.
-        *
-        * @note The method is recursive, so the same rules apply to any
-        * sub-arrays.
-        *
-        * @param string $elemName
-        * @param mixed $elemValue
-        * @param int $indent
-        *
+        * @param string|null $name Tag name
+        * @param mixed $value Tag value (attributes/content/subelements)
+        * @param int|null $indent Indentation
+        * @param array $attributes Additional attributes
         * @return string
         */
-       public static function recXmlPrint( $elemName, $elemValue, $indent ) {
+       public static function recXmlPrint( $name, $value, $indent, $attributes = array() ) {
                $retval = '';
-               if ( !is_null( $indent ) ) {
-                       $indent += 2;
+               if ( $indent !== null ) {
+                       if ( $name !== null ) {
+                               $indent += 2;
+                       }
                        $indstr = "\n" . str_repeat( ' ', $indent );
                } else {
                        $indstr = '';
                }
-               $elemName = str_replace( ' ', '_', $elemName );
-
-               if ( is_array( $elemValue ) ) {
-                       if ( isset( $elemValue['*'] ) ) {
-                               $subElemContent = $elemValue['*'];
-                               unset( $elemValue['*'] );
 
-                               // Add xml:space="preserve" to the
-                               // element so XML parsers will leave
-                               // whitespace in the content alone
-                               $elemValue['xml:space'] = 'preserve';
-                       } else {
-                               $subElemContent = null;
+               if ( is_object( $value ) ) {
+                       $value = (array)$value;
+               }
+               if ( is_array( $value ) ) {
+                       $contentKey = isset( $value[ApiResult::META_CONTENT] )
+                               ? $value[ApiResult::META_CONTENT]
+                               : '*';
+                       $subelementKeys = isset( $value[ApiResult::META_SUBELEMENTS] )
+                               ? $value[ApiResult::META_SUBELEMENTS]
+                               : array();
+                       if ( isset( $value[ApiResult::META_BC_SUBELEMENTS] ) ) {
+                               $subelementKeys = array_merge(
+                                       $subelementKeys, $value[ApiResult::META_BC_SUBELEMENTS]
+                               );
                        }
+                       $preserveKeys = isset( $value[ApiResult::META_PRESERVE_KEYS] )
+                               ? $value[ApiResult::META_PRESERVE_KEYS]
+                               : array();
+                       $indexedTagName = isset( $value[ApiResult::META_INDEXED_TAG_NAME] )
+                               ? $value[ApiResult::META_INDEXED_TAG_NAME]
+                               : '_v';
+                       $bcBools = isset( $value[ApiResult::META_BC_BOOLS] )
+                               ? $value[ApiResult::META_BC_BOOLS]
+                               : array();
+                       $indexSubelements = isset( $value[ApiResult::META_TYPE] )
+                               ? $value[ApiResult::META_TYPE] !== 'array'
+                               : false;
 
-                       if ( isset( $elemValue['_element'] ) ) {
-                               $subElemIndName = $elemValue['_element'];
-                               unset( $elemValue['_element'] );
-                       } else {
-                               $subElemIndName = null;
-                       }
+                       $content = null;
+                       $subelements = array();
+                       $indexedSubelements = array();
+                       foreach ( $value as $k => $v ) {
+                               if ( ApiResult::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
+                                       continue;
+                               }
 
-                       if ( isset( $elemValue['_subelements'] ) ) {
-                               foreach ( $elemValue['_subelements'] as $subElemId ) {
-                                       if ( isset( $elemValue[$subElemId] ) && !is_array( $elemValue[$subElemId] ) ) {
-                                               $elemValue[$subElemId] = array( '*' => $elemValue[$subElemId] );
-                                       }
+                               $oldv = $v;
+                               if ( is_bool( $v ) && !in_array( $k, $bcBools, true ) ) {
+                                       $v = $v ? 'true' : 'false';
                                }
-                               unset( $elemValue['_subelements'] );
-                       }
 
-                       $indElements = array();
-                       $subElements = array();
-                       foreach ( $elemValue as $subElemId => & $subElemValue ) {
-                               if ( is_int( $subElemId ) ) {
-                                       $indElements[] = $subElemValue;
-                                       unset( $elemValue[$subElemId] );
-                               } elseif ( is_array( $subElemValue ) ) {
-                                       $subElements[$subElemId] = $subElemValue;
-                                       unset( $elemValue[$subElemId] );
-                               } elseif ( is_bool( $subElemValue ) ) {
-                                       // treat true as empty string, skip false in xml format
-                                       if ( $subElemValue === true ) {
-                                               $subElemValue = '';
-                                       } else {
-                                               unset( $elemValue[$subElemId] );
+                               if ( $name !== null && $k === $contentKey ) {
+                                       $content = $v;
+                               } elseif ( is_int( $k ) ) {
+                                       $indexedSubelements[$k] = $v;
+                               } elseif ( is_array( $v ) || is_object( $v ) ) {
+                                       $subelements[self::mangleName( $k, $preserveKeys )] = $v;
+                               } elseif ( in_array( $k, $subelementKeys, true ) || $name === null ) {
+                                       $subelements[self::mangleName( $k, $preserveKeys )] = array(
+                                               'content' => $v,
+                                               ApiResult::META_CONTENT => 'content',
+                                               ApiResult::META_TYPE => 'assoc',
+                                       );
+                               } elseif ( is_bool( $oldv ) ) {
+                                       if ( $oldv ) {
+                                               $attributes[self::mangleName( $k, $preserveKeys )] = '';
                                        }
+                               } elseif ( $v !== null ) {
+                                       $attributes[self::mangleName( $k, $preserveKeys )] = $v;
                                }
                        }
 
-                       if ( is_null( $subElemIndName ) && count( $indElements ) ) {
-                               ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys " .
-                                       "without _element value. Use ApiResult::setIndexedTagName()." );
-                       }
-
-                       if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
-                               ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" );
+                       if ( $content !== null ) {
+                               if ( $subelements || $indexedSubelements ) {
+                                       $subelements[self::mangleName( $contentKey, $preserveKeys )] = array(
+                                               'content' => $content,
+                                               ApiResult::META_CONTENT => 'content',
+                                               ApiResult::META_TYPE => 'assoc',
+                                       );
+                                       $content = null;
+                               } elseif ( is_scalar( $content ) ) {
+                                       // Add xml:space="preserve" to the element so XML parsers
+                                       // will leave whitespace in the content alone
+                                       $attributes += array( 'xml:space' => 'preserve' );
+                               }
                        }
 
-                       if ( !is_null( $subElemContent ) ) {
-                               $retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent );
-                       } elseif ( !count( $indElements ) && !count( $subElements ) ) {
-                               $retval .= $indstr . Xml::element( $elemName, $elemValue );
+                       if ( $content !== null ) {
+                               if ( is_scalar( $content ) ) {
+                                       $retval .= $indstr . Xml::element( $name, $attributes, $content );
+                               } else {
+                                       if ( $name !== null ) {
+                                               $retval .= $indstr . Xml::element( $name, $attributes, null );
+                                       }
+                                       $retval .= static::recXmlPrint( null, $content, $indent );
+                                       if ( $name !== null ) {
+                                               $retval .= $indstr . Xml::closeElement( $name );
+                                       }
+                               }
+                       } elseif ( !$indexedSubelements && !$subelements ) {
+                               if ( $name !== null ) {
+                                       $retval .= $indstr . Xml::element( $name, $attributes );
+                               }
                        } else {
-                               $retval .= $indstr . Xml::element( $elemName, $elemValue, null );
-
-                               foreach ( $subElements as $subElemId => & $subElemValue ) {
-                                       $retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent );
+                               if ( $name !== null ) {
+                                       $retval .= $indstr . Xml::element( $name, $attributes, null );
                                }
-
-                               foreach ( $indElements as &$subElemValue ) {
-                                       $retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent );
+                               foreach ( $subelements as $k => $v ) {
+                                       $retval .= static::recXmlPrint( $k, $v, $indent );
+                               }
+                               foreach ( $indexedSubelements as $k => $v ) {
+                                       $retval .= static::recXmlPrint( $indexedTagName, $v, $indent,
+                                               $indexSubelements ? array( '_idx' => $k ) : array()
+                                       );
+                               }
+                               if ( $name !== null ) {
+                                       $retval .= $indstr . Xml::closeElement( $name );
                                }
-
-                               $retval .= $indstr . Xml::closeElement( $elemName );
                        }
-               } elseif ( !is_object( $elemValue ) ) {
+               } else {
                        // to make sure null value doesn't produce unclosed element,
-                       // which is what Xml::element( $elemName, null, null ) returns
-                       if ( $elemValue === null ) {
-                               $retval .= $indstr . Xml::element( $elemName );
+                       // which is what Xml::element( $name, null, null ) returns
+                       if ( $value === null ) {
+                               $retval .= $indstr . Xml::element( $name, $attributes );
                        } else {
-                               $retval .= $indstr . Xml::element( $elemName, null, $elemValue );
+                               $retval .= $indstr . Xml::element( $name, $attributes, $value );
                        }
                }
 
                return $retval;
        }
 
+       /**
+        * Mangle XML-invalid names to be valid in XML
+        * @param string $name
+        * @param array $preserveKeys Names to not mangle
+        * @return string Mangled name
+        */
+       private static function mangleName( $name, $preserveKeys = array() ) {
+               static $nsc = null, $nc = null;
+
+               if ( in_array( $name, $preserveKeys, true ) ) {
+                       return $name;
+               }
+
+               if ( $name === '' ) {
+                       return '_';
+               }
+
+               if ( $nsc === null ) {
+                       // Note we omit ':' from $nsc and $nc because it's reserved for XML
+                       // namespacing, and we omit '_' from $nsc (but not $nc) because we
+                       // reserve it.
+                       $nsc = 'A-Za-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}' .
+                               '\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}' .
+                               '\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
+                       $nc = $nsc . '_\-.0-9\x{B7}\x{300}-\x{36F}\x{203F}-\x{2040}';
+               }
+
+               if ( preg_match( "/^[$nsc][$nc]*$/uS", $name ) ) {
+                       return $name;
+               }
+
+               return '_' . preg_replace_callback(
+                       "/[^$nc]/uS",
+                       function ( $m ) {
+                               return sprintf( '.%X.', utf8ToCodepoint( $m[0] ) );
+                       },
+                       str_replace( '.', '.2E.', $name )
+               );
+       }
+
        function addXslt() {
                $nt = Title::newFromText( $this->mXslt );
                if ( is_null( $nt ) || !$nt->exists() ) {
index d2d5e7c..00fc12e 100644 (file)
@@ -60,7 +60,7 @@ class ApiHelp extends ApiBase {
                                'mime' => 'text/html',
                                'help' => $html,
                        );
-                       $result->setSubelements( $data, 'help' );
+                       ApiResult::setSubelementsList( $data, 'help' );
                        $result->addValue( null, $this->getModuleName(), $data );
                } else {
                        $result->reset();
index 6fd79f4..7b99921 100644 (file)
@@ -52,7 +52,8 @@ class ApiImageRotate extends ApiBase {
                $params = $this->extractRequestParams();
                $rotation = $params['rotation'];
 
-               $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+               $continuationManager = new ApiContinuationManager( $this, array(), array() );
+               $this->setContinuationManager( $continuationManager );
 
                $pageSet = $this->getPageSet();
                $pageSet->execute();
@@ -122,7 +123,7 @@ class ApiImageRotate extends ApiBase {
                                        $r['result'] = 'Success';
                                } else {
                                        $r['result'] = 'Failure';
-                                       $r['errormessage'] = $this->getResult()->convertStatusToArray( $status );
+                                       $r['errormessage'] = $this->getErrorFormatter()->arrayFromStatus( $status );
                                }
                        } else {
                                $r['result'] = 'Failure';
@@ -131,9 +132,11 @@ class ApiImageRotate extends ApiBase {
                        $result[] = $r;
                }
                $apiResult = $this->getResult();
-               $apiResult->setIndexedTagName( $result, 'page' );
+               ApiResult::setIndexedTagName( $result, 'page' );
                $apiResult->addValue( null, $this->getModuleName(), $result );
-               $apiResult->endContinuation();
+
+               $this->setContinuationManager( null );
+               $continuationManager->setContinuationIntoResult( $apiResult );
        }
 
        /**
index c7dcce8..9d76a46 100644 (file)
@@ -85,7 +85,7 @@ class ApiImport extends ApiBase {
 
                $resultData = $reporter->getData();
                $result = $this->getResult();
-               $result->setIndexedTagName( $resultData, 'page' );
+               ApiResult::setIndexedTagName( $resultData, 'page' );
                $result->addValue( null, $this->getModuleName(), $resultData );
        }
 
index ee1cfa6..465025c 100644 (file)
@@ -140,7 +140,7 @@ class ApiMain extends ApiBase {
         */
        private $mPrinter;
 
-       private $mModuleMgr, $mResult;
+       private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager;
        private $mAction;
        private $mEnableWrite;
        private $mInternalMode, $mSquidMaxage, $mModule;
@@ -218,7 +218,11 @@ class ApiMain extends ApiBase {
 
                Hooks::run( 'ApiMain::moduleManager', array( $this->mModuleMgr ) );
 
-               $this->mResult = new ApiResult( $this );
+               $this->mResult = new ApiResult( $this->getConfig()->get( 'APIMaxResultSize' ) );
+               $this->mErrorFormatter = new ApiErrorFormatter_BackCompat( $this->mResult );
+               $this->mResult->setErrorFormatter( $this->mErrorFormatter );
+               $this->mResult->setMainForContinuation( $this );
+               $this->mContinuationManager = null;
                $this->mEnableWrite = $enableWrite;
 
                $this->mSquidMaxage = -1; // flag for executeActionWithErrorHandling()
@@ -242,6 +246,43 @@ class ApiMain extends ApiBase {
                return $this->mResult;
        }
 
+       /**
+        * Get the ApiErrorFormatter object associated with current request
+        * @return ApiErrorFormatter
+        */
+       public function getErrorFormatter() {
+               return $this->mErrorFormatter;
+       }
+
+       /**
+        * Get the continuation manager
+        * @return ApiContinuationManager|null
+        */
+       public function getContinuationManager() {
+               return $this->mContinuationManager;
+       }
+
+       /**
+        * Set the continuation manager
+        * @param ApiContinuationManager|null
+        */
+       public function setContinuationManager( $manager ) {
+               if ( $manager !== null ) {
+                       if ( !$manager instanceof ApiContinuationManager ) {
+                               throw new InvalidArgumentException( __METHOD__ . ': Was passed ' .
+                                       is_object( $manager ) ? get_class( $manager ) : gettype( $manager )
+                               );
+                       }
+                       if ( $this->mContinuationManager !== null ) {
+                               throw new UnexpectedValueException(
+                                       __METHOD__ . ': tried to set manager from ' . $manager->getSource() .
+                                       ' when a manager is already set from ' . $this->mContinuationManager->getSource()
+                               );
+                       }
+               }
+               $this->mContinuationManager = $manager;
+       }
+
        /**
         * Get the API module object. Only works after executeAction()
         *
@@ -785,7 +826,7 @@ class ApiMain extends ApiBase {
                        // User entered incorrect parameters - generate error response
                        $errMessage = $e->getMessageArray();
                        $link = wfExpandUrl( wfScript( 'api' ) );
-                       ApiResult::setContent( $errMessage, "See $link for API usage" );
+                       ApiResult::setContentValue( $errMessage, 'docref', "See $link for API usage" );
                } else {
                        // Something is seriously wrong
                        if ( ( $e instanceof DBQueryError ) && !$config->get( 'ShowSQLErrors' ) ) {
@@ -799,16 +840,16 @@ class ApiMain extends ApiBase {
                                'info' => '[' . MWExceptionHandler::getLogId( $e ) . '] ' . $info,
                        );
                        if ( $config->get( 'ShowExceptionDetails' ) ) {
-                               ApiResult::setContent(
+                               ApiResult::setContentValue(
                                        $errMessage,
+                                       'trace',
                                        MWExceptionHandler::getRedactedTraceAsString( $e )
                                );
                        }
                }
 
                // Remember all the warnings to re-add them later
-               $oldResult = $result->getData();
-               $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null;
+               $warnings = $result->getResultData( array( 'warnings' ) );
 
                $result->reset();
                // Re-add the id
@@ -1191,9 +1232,7 @@ class ApiMain extends ApiBase {
                        $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
                }
 
-               $this->getResult()->cleanUpUTF8();
                $printer = $this->mPrinter;
-
                $printer->initPrinter( false );
                $printer->execute();
                $printer->closePrinter();
index b027f33..80317d3 100644 (file)
@@ -47,7 +47,7 @@ class ApiManageTags extends ApiBase {
                        'tag' => $params['tag'],
                );
                if ( !$status->isGood() ) {
-                       $ret['warnings'] = $result->convertStatusToArray( $status, 'warning' );
+                       $ret['warnings'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'warning' );
                }
                if ( $status->value !== null ) {
                        $ret['success'] = '';
diff --git a/includes/api/ApiMessage.php b/includes/api/ApiMessage.php
new file mode 100644 (file)
index 0000000..6717c39
--- /dev/null
@@ -0,0 +1,191 @@
+<?php
+/**
+ * Defines an interface for messages with additional machine-readable data for
+ * use by the API, and provides concrete implementations of that interface.
+ *
+ * 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
+ */
+
+/**
+ * Interface for messages with machine-readable data for use by the API
+ * @since 1.25
+ * @ingroup API
+ */
+interface IApiMessage extends MessageSpecifier {
+       /**
+        * Returns a machine-readable code for use by the API
+        *
+        * The message key is often sufficient, but sometimes there are multiple
+        * messages used for what is really the same underlying condition (e.g.
+        * badaccess-groups and badaccess-group0)
+        * @return string
+        */
+       public function getApiCode();
+
+       /**
+        * Returns additional machine-readable data about the error condition
+        * @return array
+        */
+       public function getApiData();
+
+       /**
+        * Sets the machine-readable code for use by the API
+        * @param string|null $code If null, the message key should be returned by self::getApiCode()
+        * @param array|null $data If non-null, passed to self::setApiData()
+        */
+       public function setApiCode( $code, array $data = null );
+
+       /**
+        * Sets additional machine-readable data about the error condition
+        * @param array $data
+        */
+       public function setApiData( array $data );
+}
+
+/**
+ * Extension of Message implementing IApiMessage
+ * @since 1.25
+ * @ingroup API
+ * @todo: Would be nice to use a Trait here to avoid code duplication
+ */
+class ApiMessage extends Message implements IApiMessage {
+       protected $apiCode = null;
+       protected $apiData = array();
+
+       /**
+        * Create an IApiMessage for the message
+        *
+        * This returns $msg if it's an IApiMessage, calls 'new ApiRawMessage' if
+        * $msg is a RawMessage, or calls 'new ApiMessage' in all other cases.
+        *
+        * @param Message|RawMessage|array|string $msg
+        * @param string|null $code
+        * @param array|null $data
+        * @return ApiMessage
+        */
+       public static function create( $msg, $code = null, array $data = null ) {
+               if ( $msg instanceof IApiMessage ) {
+                       return $msg;
+               } elseif ( $msg instanceof RawMessage ) {
+                       return new ApiRawMessage( $msg, $code, $data );
+               } else {
+                       return new ApiMessage( $msg, $code, $data );
+               }
+       }
+
+       /**
+        * @param Message|string|array $msg
+        *  - Message: is cloned
+        *  - array: first element is $key, rest are $params to Message::__construct
+        *  - string: passed to Message::__construct
+        * @param string|null $code
+        * @param array|null $data
+        * @return ApiMessage
+        */
+       public function __construct( $msg, $code = null, array $data = null ) {
+               if ( $msg instanceof Message ) {
+                       foreach ( get_class_vars( get_class( $this ) ) as $key => $value ) {
+                               if ( isset( $msg->$key ) ) {
+                                       $this->$key = $msg->$key;
+                               }
+                       }
+               } elseif ( is_array( $msg ) ) {
+                       $key = array_shift( $msg );
+                       parent::__construct( $key, $msg );
+               } else {
+                       parent::__construct( $msg );
+               }
+               $this->apiCode = $code;
+               $this->apiData = (array)$data;
+       }
+
+       public function getApiCode() {
+               return $this->apiCode === null ? $this->getKey() : $this->apiCode;
+       }
+
+       public function setApiCode( $code, array $data = null ) {
+               $this->apiCode = $code;
+               if ( $data !== null ) {
+                       $this->setApiData( $data );
+               }
+       }
+
+       public function getApiData() {
+               return $this->apiData;
+       }
+
+       public function setApiData( array $data ) {
+               $this->apiData = $data;
+       }
+}
+
+/**
+ * Extension of RawMessage implementing IApiMessage
+ * @since 1.25
+ * @ingroup API
+ * @todo: Would be nice to use a Trait here to avoid code duplication
+ */
+class ApiRawMessage extends RawMessage implements IApiMessage {
+       protected $apiCode = null;
+       protected $apiData = array();
+
+       /**
+        * @param RawMessage|string|array $msg
+        *  - RawMessage: is cloned
+        *  - array: first element is $key, rest are $params to RawMessage::__construct
+        *  - string: passed to RawMessage::__construct
+        * @param string|null $code
+        * @param array|null $data
+        * @return ApiMessage
+        */
+       public function __construct( $msg, $code = null, array $data = null ) {
+               if ( $msg instanceof RawMessage ) {
+                       foreach ( get_class_vars( get_class( $this ) ) as $key => $value ) {
+                               if ( isset( $msg->$key ) ) {
+                                       $this->$key = $msg->$key;
+                               }
+                       }
+               } elseif ( is_array( $msg ) ) {
+                       $key = array_shift( $msg );
+                       parent::__construct( $key, $msg );
+               } else {
+                       parent::__construct( $msg );
+               }
+               $this->apiCode = $code;
+               $this->apiData = (array)$data;
+       }
+
+       public function getApiCode() {
+               return $this->apiCode === null ? $this->getKey() : $this->apiCode;
+       }
+
+       public function setApiCode( $code, array $data = null ) {
+               $this->apiCode = $code;
+               if ( $data !== null ) {
+                       $this->setApiData( $data );
+               }
+       }
+
+       public function getApiData() {
+               return $this->apiData;
+       }
+
+       public function setApiData( array $data ) {
+               $this->apiData = $data;
+       }
+}
index 7fb6303..0db18e7 100644 (file)
@@ -120,12 +120,12 @@ class ApiMove extends ApiBase {
                if ( $params['movesubpages'] ) {
                        $r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle,
                                $params['reason'], $params['noredirect'] );
-                       $result->setIndexedTagName( $r['subpages'], 'subpage' );
+                       ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
 
                        if ( $params['movetalk'] ) {
                                $r['subpages-talk'] = $this->moveSubpages( $fromTalk, $toTalk,
                                        $params['reason'], $params['noredirect'] );
-                               $result->setIndexedTagName( $r['subpages-talk'], 'subpage' );
+                               ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
                        }
                }
 
index f24a03f..33790f9 100644 (file)
@@ -237,23 +237,24 @@ class ApiOpenSearch extends ApiBase {
                                );
                                $items = array();
                                foreach ( $results as $r ) {
-                                       $item = array();
-                                       $result->setContent( $item, $r['title']->getPrefixedText(), 'Text' );
-                                       $result->setContent( $item, $r['url'], 'Url' );
+                                       $item = array(
+                                               'Text' => $r['title']->getPrefixedText(),
+                                               'Url' => $r['url'],
+                                       );
                                        if ( is_string( $r['extract'] ) && $r['extract'] !== '' ) {
-                                               $result->setContent( $item, $r['extract'], 'Description' );
+                                               $item['Description'] = $r['extract'];
                                        }
                                        if ( is_array( $r['image'] ) && isset( $r['image']['source'] ) ) {
                                                $item['Image'] = array_intersect_key( $r['image'], $imageKeys );
                                        }
+                                       ApiResult::setSubelementsList( $item, array_keys( $item ) );
                                        $items[] = $item;
                                }
-                               $result->setIndexedTagName( $items, 'Item' );
+                               ApiResult::setIndexedTagName( $items, 'Item' );
                                $result->addValue( null, 'version', '2.0' );
                                $result->addValue( null, 'xmlns', 'http://opensearch.org/searchsuggest2' );
-                               $query = array();
-                               $result->setContent( $query, strval( $search ) );
-                               $result->addValue( null, 'Query', $query );
+                               $result->addValue( null, 'Query', strval( $search ) );
+                               $result->addSubelementsList( null, 'Query' );
                                $result->addValue( null, 'Section', $items );
                                break;
 
index d462862..02a25fe 100644 (file)
@@ -442,7 +442,7 @@ class ApiPageSet extends ApiBase {
                        $values[] = $r;
                }
                if ( !empty( $values ) && $result ) {
-                       $result->setIndexedTagName( $values, 'r' );
+                       ApiResult::setIndexedTagName( $values, 'r' );
                }
 
                return $values;
@@ -473,7 +473,7 @@ class ApiPageSet extends ApiBase {
                        );
                }
                if ( !empty( $values ) && $result ) {
-                       $result->setIndexedTagName( $values, 'n' );
+                       ApiResult::setIndexedTagName( $values, 'n' );
                }
 
                return $values;
@@ -504,7 +504,7 @@ class ApiPageSet extends ApiBase {
                        );
                }
                if ( !empty( $values ) && $result ) {
-                       $result->setIndexedTagName( $values, 'c' );
+                       ApiResult::setIndexedTagName( $values, 'c' );
                }
 
                return $values;
@@ -541,7 +541,7 @@ class ApiPageSet extends ApiBase {
                        $values[] = $item;
                }
                if ( !empty( $values ) && $result ) {
-                       $result->setIndexedTagName( $values, 'i' );
+                       ApiResult::setIndexedTagName( $values, 'i' );
                }
 
                return $values;
@@ -633,7 +633,7 @@ class ApiPageSet extends ApiBase {
                        );
                }
                if ( !empty( $values ) && $result ) {
-                       $result->setIndexedTagName( $values, 'rev' );
+                       ApiResult::setIndexedTagName( $values, 'rev' );
                }
 
                return $values;
@@ -1188,17 +1188,20 @@ class ApiPageSet extends ApiBase {
         */
        public function populateGeneratorData( &$result, array $path = array() ) {
                if ( $result instanceof ApiResult ) {
-                       $data = $result->getData();
+                       $data = $result->getResultData( $path );
+                       if ( $data === null ) {
+                               return true;
+                       }
                } else {
                        $data = &$result;
-               }
-               foreach ( $path as $key ) {
-                       if ( !isset( $data[$key] ) ) {
-                               // Path isn't in $result, so nothing to add, so everything
-                               // "fits"
-                               return true;
+                       foreach ( $path as $key ) {
+                               if ( !isset( $data[$key] ) ) {
+                                       // Path isn't in $result, so nothing to add, so everything
+                                       // "fits"
+                                       return true;
+                               }
+                               $data = &$data[$key];
                        }
-                       $data = &$data[$key];
                }
                foreach ( $this->mGeneratorData as $ns => $dbkeys ) {
                        if ( $ns === -1 ) {
index bb4967b..bb661b2 100644 (file)
@@ -105,7 +105,7 @@ class ApiParamInfo extends ApiBase {
                $result->addValue( array( $this->getModuleName() ), 'helpformat', $this->helpFormat );
 
                foreach ( $res as $key => $stuff ) {
-                       $result->setIndexedTagName( $res[$key], 'module' );
+                       ApiResult::setIndexedTagName( $res[$key], 'module' );
                }
 
                if ( $params['mainmodule'] ) {
@@ -171,7 +171,7 @@ class ApiParamInfo extends ApiBase {
                                        }
                                        $res[$key][] = $a;
                                }
-                               $this->getResult()->setIndexedTagName( $res[$key], 'msg' );
+                               ApiResult::setIndexedTagName( $res[$key], 'msg' );
                                break;
                }
        }
@@ -223,7 +223,7 @@ class ApiParamInfo extends ApiBase {
                if ( isset( $ret['helpurls'][0] ) && $ret['helpurls'][0] === false ) {
                        $ret['helpurls'] = array();
                }
-               $result->setIndexedTagName( $ret['helpurls'], 'helpurl' );
+               ApiResult::setIndexedTagName( $ret['helpurls'], 'helpurl' );
 
                if ( $this->helpFormat !== 'none' ) {
                        $ret['examples'] = array();
@@ -242,12 +242,12 @@ class ApiParamInfo extends ApiBase {
                                        if ( is_array( $item['description'] ) ) {
                                                $item['description'] = $item['description'][0];
                                        } else {
-                                               $result->setSubelements( $item, 'description' );
+                                               ApiResult::setSubelementsList( $item, 'description' );
                                        }
                                }
                                $ret['examples'][] = $item;
                        }
-                       $result->setIndexedTagName( $ret['examples'], 'example' );
+                       ApiResult::setIndexedTagName( $ret['examples'], 'example' );
                }
 
                $ret['parameters'] = array();
@@ -331,7 +331,7 @@ class ApiParamInfo extends ApiBase {
                                if ( is_array( $item['type'] ) ) {
                                        // To prevent sparse arrays from being serialized to JSON as objects
                                        $item['type'] = array_values( $item['type'] );
-                                       $result->setIndexedTagName( $item['type'], 't' );
+                                       ApiResult::setIndexedTagName( $item['type'], 't' );
                                }
                        }
                        if ( isset( $settings[ApiBase::PARAM_MAX] ) ) {
@@ -353,7 +353,7 @@ class ApiParamInfo extends ApiBase {
                                        );
                                        if ( count( $i ) ) {
                                                $info['values'] = $i;
-                                               $result->setIndexedTagName( $info['values'], 'v' );
+                                               ApiResult::setIndexedTagName( $info['values'], 'v' );
                                        }
                                        $this->formatHelpMessages( $info, 'text', array(
                                                $this->context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
@@ -361,15 +361,15 @@ class ApiParamInfo extends ApiBase {
                                                        ->params( $this->context->getLanguage()->commaList( $i ) )
                                                        ->params( $module->getModulePrefix() )
                                        ) );
-                                       $result->setSubelements( $info, 'text' );
+                                       ApiResult::setSubelementsList( $info, 'text' );
                                        $item['info'][] = $info;
                                }
-                               $result->setIndexedTagName( $item['info'], 'i' );
+                               ApiResult::setIndexedTagName( $item['info'], 'i' );
                        }
 
                        $ret['parameters'][] = $item;
                }
-               $result->setIndexedTagName( $ret['parameters'], 'param' );
+               ApiResult::setIndexedTagName( $ret['parameters'], 'param' );
 
                return $ret;
        }
index b565dea..267bced 100644 (file)
@@ -127,7 +127,6 @@ class ApiParse extends ApiBase {
                        } else { // Not $oldid, but $pageid or $page
                                if ( $params['redirects'] ) {
                                        $reqParams = array(
-                                               'action' => 'query',
                                                'redirects' => '',
                                        );
                                        if ( !is_null( $pageid ) ) {
@@ -137,14 +136,12 @@ class ApiParse extends ApiBase {
                                        }
                                        $req = new FauxRequest( $reqParams );
                                        $main = new ApiMain( $req );
-                                       $main->execute();
-                                       $data = $main->getResultData();
-                                       $redirValues = isset( $data['query']['redirects'] )
-                                               ? $data['query']['redirects']
-                                               : array();
+                                       $pageSet = new ApiPageSet( $main );
+                                       $pageSet->execute();
+
                                        $to = $page;
-                                       foreach ( (array)$redirValues as $r ) {
-                                               $to = $r['to'];
+                                       foreach ( $pageSet->getRedirectTitles() as $title ) {
+                                               $to = $title->getFullText();
                                        }
                                        $pageParams = array( 'title' => $to );
                                } elseif ( !is_null( $pageid ) ) {
@@ -229,16 +226,20 @@ class ApiParse extends ApiBase {
                                // Build a result and bail out
                                $result_array = array();
                                $result_array['text'] = array();
-                               ApiResult::setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
+                               ApiResult::setContentValue( $result_array['text'], 'text', $this->pstContent->serialize( $format ) );
                                if ( isset( $prop['wikitext'] ) ) {
                                        $result_array['wikitext'] = array();
-                                       ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+                                       ApiResult::setContentValue( $result_array['wikitext'], 'wikitext', $this->content->serialize( $format ) );
                                }
                                if ( !is_null( $params['summary'] ) ||
                                        ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
                                ) {
                                        $result_array['parsedsummary'] = array();
-                                       ApiResult::setContent( $result_array['parsedsummary'], $this->formatSummary( $titleObj, $params ) );
+                                       ApiResult::setContentValue(
+                                               $result_array['parsedsummary'],
+                                               'parsedsummary',
+                                               $this->formatSummary( $titleObj, $params )
+                                       );
                                }
 
                                $result->addValue( null, $this->getModuleName(), $result_array );
@@ -272,14 +273,18 @@ class ApiParse extends ApiBase {
 
                if ( isset( $prop['text'] ) ) {
                        $result_array['text'] = array();
-                       ApiResult::setContent( $result_array['text'], $p_result->getText() );
+                       ApiResult::setContentValue( $result_array['text'], 'text', $p_result->getText() );
                }
 
                if ( !is_null( $params['summary'] ) ||
                        ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
                ) {
                        $result_array['parsedsummary'] = array();
-                       ApiResult::setContent( $result_array['parsedsummary'], $this->formatSummary( $titleObj, $params ) );
+                       ApiResult::setContentValue(
+                               $result_array['parsedsummary'],
+                               'parsedsummary',
+                               $this->formatSummary( $titleObj, $params )
+                       );
                }
 
                if ( isset( $prop['langlinks'] ) ) {
@@ -304,7 +309,7 @@ class ApiParse extends ApiBase {
                if ( isset( $prop['categorieshtml'] ) ) {
                        $categoriesHtml = $this->categoriesHtml( $p_result->getCategories() );
                        $result_array['categorieshtml'] = array();
-                       ApiResult::setContent( $result_array['categorieshtml'], $categoriesHtml );
+                       ApiResult::setContentValue( $result_array['categorieshtml'], 'categorieshtml', $categoriesHtml );
                }
                if ( isset( $prop['links'] ) ) {
                        $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
@@ -345,8 +350,9 @@ class ApiParse extends ApiBase {
 
                        if ( isset( $prop['headhtml'] ) ) {
                                $result_array['headhtml'] = array();
-                               ApiResult::setContent(
+                               ApiResult::setContentValue(
                                        $result_array['headhtml'],
+                                       'headhtml',
                                        $context->getOutput()->headElement( $context->getSkin() )
                                );
                        }
@@ -362,7 +368,7 @@ class ApiParse extends ApiBase {
                if ( isset( $prop['indicators'] ) ) {
                        foreach ( $p_result->getIndicators() as $name => $content ) {
                                $indicator = array( 'name' => $name );
-                               ApiResult::setContent( $indicator, $content );
+                               ApiResult::setContentValue( $indicator, 'content', $content );
                                $result_array['indicators'][] = $indicator;
                        }
                }
@@ -373,10 +379,10 @@ class ApiParse extends ApiBase {
 
                if ( isset( $prop['wikitext'] ) ) {
                        $result_array['wikitext'] = array();
-                       ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+                       ApiResult::setContentValue( $result_array['wikitext'], 'wikitext', $this->content->serialize( $format ) );
                        if ( !is_null( $this->pstContent ) ) {
                                $result_array['psttext'] = array();
-                               ApiResult::setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
+                               ApiResult::setContentValue( $result_array['psttext'], 'psttext', $this->pstContent->serialize( $format ) );
                        }
                }
                if ( isset( $prop['properties'] ) ) {
@@ -390,7 +396,7 @@ class ApiParse extends ApiBase {
                if ( isset( $prop['limitreporthtml'] ) ) {
                        $limitreportHtml = EditPage::getPreviewLimitReport( $p_result );
                        $result_array['limitreporthtml'] = array();
-                       ApiResult::setContent( $result_array['limitreporthtml'], $limitreportHtml );
+                       ApiResult::setContentValue( $result_array['limitreporthtml'], 'limitreporthtml', $limitreportHtml );
                }
 
                if ( $params['generatexml'] ) {
@@ -406,7 +412,7 @@ class ApiParse extends ApiBase {
                                $xml = $dom->__toString();
                        }
                        $result_array['parsetree'] = array();
-                       ApiResult::setContent( $result_array['parsetree'], $xml );
+                       ApiResult::setContentValue( $result_array['parsetree'], 'parsetree', $xml );
                }
 
                $result_mapping = array(
@@ -546,7 +552,7 @@ class ApiParse extends ApiBase {
                                // native language name
                                $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
                        }
-                       ApiResult::setContent( $entry, $bits[1] );
+                       ApiResult::setContentValue( $entry, 'title', $bits[1] );
                        $result[] = $entry;
                }
 
@@ -581,7 +587,7 @@ class ApiParse extends ApiBase {
                foreach ( $links as $link => $sortkey ) {
                        $entry = array();
                        $entry['sortkey'] = $sortkey;
-                       ApiResult::setContent( $entry, $link );
+                       ApiResult::setContentValue( $entry, 'category', $link );
                        if ( !isset( $hiddencats[$link] ) ) {
                                $entry['missing'] = '';
                        } elseif ( $hiddencats[$link] ) {
@@ -606,7 +612,7 @@ class ApiParse extends ApiBase {
                        foreach ( $nslinks as $title => $id ) {
                                $entry = array();
                                $entry['ns'] = $ns;
-                               ApiResult::setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
+                               ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
                                if ( $id != 0 ) {
                                        $entry['exists'] = '';
                                }
@@ -629,7 +635,7 @@ class ApiParse extends ApiBase {
                                        $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
                                }
 
-                               ApiResult::setContent( $entry, $title->getFullText() );
+                               ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
                                $result[] = $entry;
                        }
                }
@@ -642,7 +648,7 @@ class ApiParse extends ApiBase {
                foreach ( $headItems as $tag => $content ) {
                        $entry = array();
                        $entry['tag'] = $tag;
-                       ApiResult::setContent( $entry, $content );
+                       ApiResult::setContentValue( $entry, 'content', $content );
                        $result[] = $entry;
                }
 
@@ -654,7 +660,7 @@ class ApiParse extends ApiBase {
                foreach ( $properties as $name => $value ) {
                        $entry = array();
                        $entry['name'] = $name;
-                       ApiResult::setContent( $entry, $value );
+                       ApiResult::setContentValue( $entry, 'value', $value );
                        $result[] = $entry;
                }
 
@@ -666,7 +672,7 @@ class ApiParse extends ApiBase {
                foreach ( $css as $file => $link ) {
                        $entry = array();
                        $entry['file'] = $file;
-                       ApiResult::setContent( $entry, $link );
+                       ApiResult::setContentValue( $entry, 'link', $link );
                        $result[] = $entry;
                }
 
@@ -683,8 +689,8 @@ class ApiParse extends ApiBase {
                        if ( !is_array( $value ) ) {
                                $value = array( $value );
                        }
-                       $apiResult->setIndexedTagName( $value, 'param' );
-                       $apiResult->setIndexedTagName_recursive( $value, 'param' );
+                       ApiResult::setIndexedTagName( $value, 'param' );
+                       ApiResult::setIndexedTagNameOnSubarrays( $value, 'param' );
                        $entry = array_merge( $entry, $value );
                        $result[] = $entry;
                }
@@ -695,7 +701,7 @@ class ApiParse extends ApiBase {
        private function setIndexedTagNames( &$array, $mapping ) {
                foreach ( $mapping as $key => $name ) {
                        if ( isset( $array[$key] ) ) {
-                               $this->getResult()->setIndexedTagName( $array[$key], $name );
+                               ApiResult::setIndexedTagName( $array[$key], $name );
                        }
                }
        }
index 675aa58..496db8f 100644 (file)
@@ -127,7 +127,7 @@ class ApiProtect extends ApiBase {
                }
                $res['protections'] = $resultProtections;
                $result = $this->getResult();
-               $result->setIndexedTagName( $res['protections'], 'protection' );
+               ApiResult::setIndexedTagName( $res['protections'], 'protection' );
                $result->addValue( null, $this->getModuleName(), $res );
        }
 
index ec55137..67f7834 100644 (file)
@@ -38,7 +38,8 @@ class ApiPurge extends ApiBase {
        public function execute() {
                $params = $this->extractRequestParams();
 
-               $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+               $continuationManager = new ApiContinuationManager( $this, array(), array() );
+               $this->setContinuationManager( $continuationManager );
 
                $forceLinkUpdate = $params['forcelinkupdate'];
                $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
@@ -89,7 +90,7 @@ class ApiPurge extends ApiBase {
                        $result[] = $r;
                }
                $apiResult = $this->getResult();
-               $apiResult->setIndexedTagName( $result, 'page' );
+               ApiResult::setIndexedTagName( $result, 'page' );
                $apiResult->addValue( null, $this->getModuleName(), $result );
 
                $values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
@@ -105,7 +106,8 @@ class ApiPurge extends ApiBase {
                        $apiResult->addValue( null, 'redirects', $values );
                }
 
-               $apiResult->endContinuation();
+               $this->setContinuationManager( null );
+               $continuationManager->setContinuationIntoResult( $apiResult );
        }
 
        /**
index ac89419..b1069c7 100644 (file)
@@ -257,11 +257,11 @@ class ApiQuery extends ApiBase {
                $this->instantiateModules( $allModules, 'meta' );
 
                // Filter modules based on continue parameter
-               list( $generatorDone, $modules ) = $this->getResult()->beginContinuation(
-                       $this->mParams['continue'], $allModules, $propModules
-               );
+               $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
+               $this->setContinuationManager( $continuationManager );
+               $modules = $continuationManager->getRunModules();
 
-               if ( !$generatorDone ) {
+               if ( !$continuationManager->isGeneratorDone() ) {
                        // Query modules may optimize data requests through the $this->getPageSet()
                        // object by adding extra fields from the page table.
                        foreach ( $modules as $module ) {
@@ -291,12 +291,19 @@ class ApiQuery extends ApiBase {
                $this->getMain()->setCacheMode( $cacheMode );
 
                // Write the continuation data into the result
-               $this->getResult()->endContinuation(
-                       $this->mParams['continue'] === null ? 'raw' : 'standard'
-               );
+               $this->setContinuationManager( null );
+               if ( $this->mParams['continue'] === null ) {
+                       $data = $continuationManager->getRawContinuation();
+                       if ( $data ) {
+                               $this->getResult()->addValue( null, 'query-continue', $data,
+                                       ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+                       }
+               } else {
+                       $continuationManager->setContinuationIntoResult( $this->getResult() );
+               }
 
                if ( $this->mParams['continue'] === null && !$this->mParams['rawcontinue'] &&
-                       array_key_exists( 'query-continue', $this->getResult()->getData() )
+                       $this->getResult()->getResultData( 'query-continue' ) !== null
                ) {
                        $this->logFeatureUsage( 'action=query&!rawcontinue&!continue' );
                        $this->setWarning(
@@ -443,11 +450,11 @@ class ApiQuery extends ApiBase {
                                $pageIDs = array_keys( $pages );
                                // json treats all map keys as strings - converting to match
                                $pageIDs = array_map( 'strval', $pageIDs );
-                               $result->setIndexedTagName( $pageIDs, 'id' );
+                               ApiResult::setIndexedTagName( $pageIDs, 'id' );
                                $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
                        }
 
-                       $result->setIndexedTagName( $pages, 'page' );
+                       ApiResult::setIndexedTagName( $pages, 'page' );
                        $fit = $fit && $result->addValue( 'query', 'pages', $pages );
                }
 
@@ -476,7 +483,7 @@ class ApiQuery extends ApiBase {
         */
        public function setGeneratorContinue( $module, $paramName, $paramValue ) {
                wfDeprecated( __METHOD__, '1.24' );
-               $this->getResult()->setGeneratorContinueParam( $module, $paramName, $paramValue );
+               $this->getContinuationManager()->addGeneratorContinueParam( $module, $paramName, $paramValue );
                return $this->getParameter( 'continue' ) !== null;
        }
 
@@ -519,7 +526,7 @@ class ApiQuery extends ApiBase {
                        $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
                } else {
                        $r = array();
-                       ApiResult::setContent( $r, $exportxml );
+                       ApiResult::setContentValue( $r, 'xml', $exportxml );
                        $result->addValue( 'query', 'export', $r, ApiResult::NO_SIZE_CHECK );
                }
        }
index 672c234..4ecc4b7 100644 (file)
@@ -128,7 +128,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
                                $pages[] = $titleObj;
                        } else {
                                $item = array();
-                               ApiResult::setContent( $item, $titleObj->getText() );
+                               ApiResult::setContentValue( $item, 'category', $titleObj->getText() );
                                if ( isset( $prop['size'] ) ) {
                                        $item['size'] = intval( $row->cat_pages );
                                        $item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
@@ -147,7 +147,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'c' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'c' );
                } else {
                        $resultPageSet->populateFromTitles( $pages );
                }
index 4e95f5b..4e4d2af 100644 (file)
@@ -319,7 +319,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                                                'pageid' => $title->getArticleID(),
                                                'revisions' => array( $rev ),
                                        );
-                                       $result->setIndexedTagName( $a['revisions'], 'rev' );
+                                       ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
                                        ApiQueryBase::addTitleInfo( $a, $title );
                                        $fit = $result->addValue( array( 'query', $this->getModuleName() ), $index, $a );
                                } else {
@@ -348,7 +348,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                                $resultPageSet->populateFromRevisionIDs( $generated );
                        }
                } else {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
                }
        }
 
index 6c962cd..381938b 100644 (file)
@@ -308,7 +308,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'img' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'img' );
                } else {
                        $resultPageSet->populateFromTitles( $titles );
                }
index a70d019..1a4a4d9 100644 (file)
@@ -230,7 +230,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->indexTag );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), $this->indexTag );
                } elseif ( $params['unique'] ) {
                        $resultPageSet->populateFromTitles( $titles );
                } else {
index 98552ba..00816a3 100644 (file)
@@ -165,7 +165,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
                                                $msgString = $msg->plain();
                                        }
                                        if ( !$params['nocontent'] ) {
-                                               ApiResult::setContent( $a, $msgString );
+                                               ApiResult::setContentValue( $a, 'content', $msgString );
                                        }
                                        if ( isset( $prop['default'] ) ) {
                                                $default = wfMessage( $message )->inLanguage( $langObj )->useDatabase( false );
@@ -183,7 +183,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
                                }
                        }
                }
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'message' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'message' );
        }
 
        public function getCacheMode( $params ) {
index e243593..0149ad2 100644 (file)
@@ -234,7 +234,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'p' );
                }
        }
 
index 1c3f9fb..8c9e1ba 100644 (file)
@@ -100,7 +100,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
 
                        // no group with the given right(s) exists, no need for a query
                        if ( !count( $groups ) ) {
-                               $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' );
+                               $this->getResult()->addIndexedTagName( array( 'query', $this->getModuleName() ), '' );
 
                                return;
                        }
@@ -279,17 +279,17 @@ class ApiQueryAllUsers extends ApiQueryBase {
 
                                if ( $fld_groups ) {
                                        $data['groups'] = $groups;
-                                       $result->setIndexedTagName( $data['groups'], 'g' );
+                                       ApiResult::setIndexedTagName( $data['groups'], 'g' );
                                }
 
                                if ( $fld_implicitgroups ) {
                                        $data['implicitgroups'] = $implicitGroups;
-                                       $result->setIndexedTagName( $data['implicitgroups'], 'g' );
+                                       ApiResult::setIndexedTagName( $data['implicitgroups'], 'g' );
                                }
 
                                if ( $fld_rights ) {
                                        $data['rights'] = User::getGroupPermissions( $groups );
-                                       $result->setIndexedTagName( $data['rights'], 'r' );
+                                       ApiResult::setIndexedTagName( $data['rights'], 'r' );
                                }
                        }
 
@@ -300,7 +300,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                        }
                }
 
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'u' );
        }
 
        public function getCacheMode( $params ) {
index 5e17a5c..92cf62d 100644 (file)
@@ -338,7 +338,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
 
                if ( $this->params['limit'] == 'max' ) {
                        $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
-                       $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] );
+                       $result->addParsedLimit( $this->getModuleName(), $this->params['limit'] );
                } else {
                        $this->params['limit'] = intval( $this->params['limit'] );
                        $this->validateLimit( 'limit', $this->params['limit'], 1, $userMax, $botMax );
@@ -426,7 +426,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                        $data = array_map( function ( $arr ) use ( $result, $code ) {
                                if ( isset( $arr['redirlinks'] ) ) {
                                        $arr['redirlinks'] = array_values( $arr['redirlinks'] );
-                                       $result->setIndexedTagName( $arr['redirlinks'], $code );
+                                       ApiResult::setIndexedTagName( $arr['redirlinks'], $code );
                                }
                                return $arr;
                        }, array_values( $this->resultArr ) );
@@ -482,7 +482,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                                                $hasRedirs = true;
                                        }
                                        if ( $hasRedirs ) {
-                                               $result->setIndexedTagName_internal(
+                                               $result->addIndexedTagName(
                                                        array( 'query', $this->getModuleName(), $idx, 'redirlinks' ),
                                                        $this->bl_code );
                                        }
@@ -494,7 +494,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                                }
                        }
 
-                       $result->setIndexedTagName_internal(
+                       $result->addIndexedTagName(
                                array( 'query', $this->getModuleName() ),
                                $this->bl_code
                        );
index 6c9d999..89e92b8 100644 (file)
@@ -457,7 +457,7 @@ abstract class ApiQueryBase extends ApiBase {
         */
        protected function addPageSubItems( $pageId, $data ) {
                $result = $this->getResult();
-               $result->setIndexedTagName( $data, $this->getModulePrefix() );
+               ApiResult::setIndexedTagName( $data, $this->getModulePrefix() );
 
                return $result->addValue( array( 'query', 'pages', intval( $pageId ) ),
                        $this->getModuleName(),
@@ -482,7 +482,7 @@ abstract class ApiQueryBase extends ApiBase {
                if ( !$fit ) {
                        return false;
                }
-               $result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
+               $result->addIndexedTagName( array( 'query', 'pages', $pageId,
                        $this->getModuleName() ), $elemname );
 
                return true;
@@ -494,7 +494,7 @@ abstract class ApiQueryBase extends ApiBase {
         * @param string|array $paramValue Parameter value
         */
        protected function setContinueEnumParameter( $paramName, $paramValue ) {
-               $this->getResult()->setContinueParam( $this, $paramName, $paramValue );
+               $this->getContinuationManager()->addContinueParam( $this, $paramName, $paramValue );
        }
 
        /**
@@ -711,7 +711,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
         */
        protected function setContinueEnumParameter( $paramName, $paramValue ) {
                if ( $this->mGeneratorPageSet !== null ) {
-                       $this->getResult()->setGeneratorContinueParam( $this, $paramName, $paramValue );
+                       $this->getContinuationManager()->addGeneratorContinueParam( $this, $paramName, $paramValue );
                } else {
                        parent::setContinueEnumParameter( $paramName, $paramValue );
                }
index f6bde41..72e4fef 100644 (file)
@@ -246,7 +246,7 @@ class ApiQueryBlocks extends ApiQueryBase {
                                break;
                        }
                }
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'block' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'block' );
        }
 
        protected function prepareUsername( $user ) {
index a6fc223..82ab939 100644 (file)
@@ -285,7 +285,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal(
+                       $result->addIndexedTagName(
                                array( 'query', $this->getModuleName() ), 'cm' );
                }
        }
index f46fb34..b2c59d8 100644 (file)
@@ -177,7 +177,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
 
                if ( $limit == 'max' ) {
                        $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
-                       $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
+                       $this->getResult()->addParsedLimit( $this->getModuleName(), $limit );
                }
 
                $this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
@@ -376,9 +376,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                                if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
                                        if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
                                                // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow)
-                                               ApiResult::setContent( $rev, Revision::getRevisionText( $row, 'ar_' ) );
+                                               ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row, 'ar_' ) );
                                        } else {
-                                               ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+                                               ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row ) );
                                        }
                                }
                        }
@@ -386,7 +386,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                        if ( $fld_tags ) {
                                if ( $row->ts_tags ) {
                                        $tags = explode( ',', $row->ts_tags );
-                                       $this->getResult()->setIndexedTagName( $tags, 'tag' );
+                                       ApiResult::setIndexedTagName( $tags, 'tag' );
                                        $rev['tags'] = $tags;
                                } else {
                                        $rev['tags'] = array();
@@ -401,7 +401,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                                $pageID = $newPageID++;
                                $pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
                                $a['revisions'] = array( $rev );
-                               $result->setIndexedTagName( $a['revisions'], 'rev' );
+                               ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
                                $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
                                ApiQueryBase::addTitleInfo( $a, $title );
                                if ( $fld_token ) {
@@ -425,7 +425,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                                break;
                        }
                }
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
        }
 
        public function isDeprecated() {
index e77355b..a26eff2 100644 (file)
@@ -139,7 +139,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ),
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ),
                                $this->getModulePrefix() );
                }
        }
index 6ddb6c8..ec3d9d2 100644 (file)
@@ -91,7 +91,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
                        if ( $params['expandurl'] ) {
                                $to = wfExpandUrl( $to, PROTO_CANONICAL );
                        }
-                       ApiResult::setContent( $entry, $to );
+                       ApiResult::setContentValue( $entry, 'url', $to );
                        $fit = $this->addPageSubItem( $row->el_from, $entry );
                        if ( !$fit ) {
                                $this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
index 39c5902..8896140 100644 (file)
@@ -55,7 +55,7 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
                $repos[] = array_intersect_key( $repoGroup->getLocalRepo()->getInfo(), $props );
 
                $result = $this->getResult();
-               $result->setIndexedTagName( $repos, 'repo' );
+               ApiResult::setIndexedTagName( $repos, 'repo' );
                $result->addValue( array( 'query' ), 'repos', $repos );
        }
 
index 6b92603..cba3b73 100644 (file)
@@ -240,7 +240,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
                        }
                }
 
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'fa' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'fa' );
        }
 
        public function getAllowedParams() {
index a2af124..61928c3 100644 (file)
@@ -155,7 +155,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'iw' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'iw' );
                } else {
                        $resultPageSet->populateFromTitles( $pages );
                }
index c1208cb..aca3f70 100644 (file)
@@ -129,7 +129,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
                                }
                        }
 
-                       ApiResult::setContent( $entry, $row->iwl_title );
+                       ApiResult::setContentValue( $entry, 'title', $row->iwl_title );
                        $fit = $this->addPageSubItem( $row->iwl_from, $entry );
                        if ( !$fit ) {
                                $this->setContinueEnumParameter(
index c4ca5d6..d5da495 100644 (file)
@@ -599,7 +599,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                $retval[] = $r;
                        }
                }
-               $result->setIndexedTagName( $retval, 'metadata' );
+               ApiResult::setIndexedTagName( $retval, 'metadata' );
 
                return $retval;
        }
index 5af44ee..db28df7 100644 (file)
@@ -421,14 +421,14 @@ class ApiQueryInfo extends ApiQueryBase {
                                $pageInfo['protection'] =
                                        $this->protections[$ns][$dbkey];
                        }
-                       $this->getResult()->setIndexedTagName( $pageInfo['protection'], 'pr' );
+                       ApiResult::setIndexedTagName( $pageInfo['protection'], 'pr' );
 
                        $pageInfo['restrictiontypes'] = array();
                        if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
                                $pageInfo['restrictiontypes'] =
                                        $this->restrictionTypes[$ns][$dbkey];
                        }
-                       $this->getResult()->setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' );
+                       ApiResult::setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' );
                }
 
                if ( $this->fld_watched && isset( $this->watched[$ns][$dbkey] ) ) {
index b41b4b7..885d10c 100644 (file)
@@ -154,7 +154,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'll' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'll' );
                } else {
                        $resultPageSet->populateFromTitles( $pages );
                }
index 2d03347..5919ee9 100644 (file)
@@ -124,7 +124,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
                        if ( isset( $prop['autonym'] ) ) {
                                $entry['autonym'] = Language::fetchLanguageName( $row->ll_lang );
                        }
-                       ApiResult::setContent( $entry, $row->ll_title );
+                       ApiResult::setContentValue( $entry, 'title', $row->ll_title );
                        $fit = $this->addPageSubItem( $row->ll_from, $entry );
                        if ( !$fit ) {
                                $this->setContinueEnumParameter( 'continue', "{$row->ll_from}|{$row->ll_lang}" );
index bbb8060..a626c8a 100644 (file)
@@ -239,7 +239,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
                                break;
                        }
                }
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'item' );
        }
 
        /**
@@ -387,8 +387,8 @@ class ApiQueryLogEvents extends ApiQueryBase {
                                $logParam = explode( ':', $key, 3 );
                                $logParams[$logParam[2]] = $value;
                        }
-                       $result->setIndexedTagName( $logParams, 'param' );
-                       $result->setIndexedTagName_recursive( $logParams, 'param' );
+                       ApiResult::setIndexedTagName( $logParams, 'param' );
+                       ApiResult::setIndexedTagNameOnSubarrays( $logParams, 'param' );
                        $vals = array_merge( $vals, $logParams );
                }
 
@@ -482,7 +482,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
                if ( $this->fld_tags ) {
                        if ( $row->ts_tags ) {
                                $tags = explode( ',', $row->ts_tags );
-                               $this->getResult()->setIndexedTagName( $tags, 'tag' );
+                               ApiResult::setIndexedTagName( $tags, 'tag' );
                                $vals['tags'] = $tags;
                        } else {
                                $vals['tags'] = array();
index 035f901..dc10c91 100644 (file)
@@ -205,7 +205,7 @@ abstract class ApiQueryORM extends ApiQueryBase {
         * @param array $serializedResults
         */
        protected function setIndexedTagNames( array &$serializedResults ) {
-               $this->getResult()->setIndexedTagName( $serializedResults, $this->getRowName() );
+               ApiResult::setIndexedTagName( $serializedResults, $this->getRowName() );
        }
 
        /**
index 026f061..11a29ff 100644 (file)
@@ -78,7 +78,7 @@ class ApiQueryPagePropNames extends ApiQueryBase {
                        }
                }
 
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'p' );
        }
 
        public function getAllowedParams() {
index 6ffe0ae..143bc06 100644 (file)
@@ -121,7 +121,7 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
                }
 
                if ( $resultPageSet === null ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
                }
        }
 
index 7a31c48..35942ca 100644 (file)
@@ -79,7 +79,7 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
                                        break;
                                }
                        }
-                       $result->setIndexedTagName_internal(
+                       $result->addIndexedTagName(
                                array( 'query', $this->getModuleName() ), $this->getModulePrefix()
                        );
                }
index f1e6d01..fb65e5e 100644 (file)
@@ -156,7 +156,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal(
+                       $result->addIndexedTagName(
                                array( 'query', $this->getModuleName() ),
                                $this->getModulePrefix()
                        );
index 74586bb..062a44f 100644 (file)
@@ -119,7 +119,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
                        }
                }
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal(
+                       $result->addIndexedTagName(
                                array( 'query', $this->getModuleName(), 'results' ),
                                'page'
                        );
index 282f498..a2c2844 100644 (file)
@@ -131,7 +131,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
                }
        }
 
index aa22264..3dbfdf9 100644 (file)
@@ -399,7 +399,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
 
                if ( is_null( $resultPageSet ) ) {
                        /* Format the result */
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'rc' );
                } else {
                        $resultPageSet->populateFromTitles( $titles );
                }
@@ -551,7 +551,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                if ( $this->fld_tags ) {
                        if ( $row->ts_tags ) {
                                $tags = explode( ',', $row->ts_tags );
-                               $this->getResult()->setIndexedTagName( $tags, 'tag' );
+                               ApiResult::setIndexedTagName( $tags, 'tag' );
                                $vals['tags'] = $tags;
                        } else {
                                $vals['tags'] = array();
index 281f838..1805f40 100644 (file)
@@ -138,7 +138,7 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                if ( $this->limit == 'max' ) {
                        $this->limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
                        if ( $this->setParsedLimit ) {
-                               $this->getResult()->setParsedLimit( $this->getModuleName(), $this->limit );
+                               $this->getResult()->addParsedLimit( $this->getModuleName(), $this->limit );
                        }
                }
 
@@ -243,7 +243,7 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                if ( $this->fld_tags ) {
                        if ( $row->ts_tags ) {
                                $tags = explode( ',', $row->ts_tags );
-                               $this->getResult()->setIndexedTagName( $tags, 'tag' );
+                               ApiResult::setIndexedTagName( $tags, 'tag' );
                                $vals['tags'] = $tags;
                        } else {
                                $vals['tags'] = array();
@@ -347,7 +347,7 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                        }
 
                        if ( $text !== false ) {
-                               ApiResult::setContent( $vals, $text );
+                               ApiResult::setContentValue( $vals, 'content', $text );
                        }
                }
 
@@ -389,7 +389,7 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                }
                                if ( $engine ) {
                                        $difftext = $engine->getDiffBody();
-                                       ApiResult::setContent( $vals['diff'], $difftext );
+                                       ApiResult::setContentValue( $vals['diff'], 'body', $difftext );
                                        if ( !$engine->wasCacheHit() ) {
                                                $n++;
                                        }
index e489b2f..e29ef8d 100644 (file)
@@ -252,11 +252,11 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                }
 
                if ( $resultPageSet === null ) {
-                       $apiResult->setIndexedTagName_internal( array(
+                       $apiResult->addIndexedTagName( array(
                                'query', $this->getModuleName()
                        ), 'p' );
                        if ( $hasInterwikiResults ) {
-                               $apiResult->setIndexedTagName_internal( array(
+                               $apiResult->addIndexedTagName( array(
                                        'query', 'interwiki' . $this->getModuleName()
                                ), 'p' );
                        }
index d4f7e6a..22447f7 100644 (file)
@@ -158,7 +158,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                }
                if ( $allowException ) {
                        $data['externalimages'] = (array)$allowFrom;
-                       $this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' );
+                       ApiResult::setIndexedTagName( $data['externalimages'], 'prefix' );
                }
 
                if ( !$config->get( 'DisableLangConversion' ) ) {
@@ -210,7 +210,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        $fallbacks[] = array( 'code' => $code );
                }
                $data['fallback'] = $fallbacks;
-               $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
+               ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
 
                if ( $wgContLang->hasVariants() ) {
                        $variants = array();
@@ -221,7 +221,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                );
                        }
                        $data['variants'] = $variants;
-                       $this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
+                       ApiResult::setIndexedTagName( $data['variants'], 'lang' );
                }
 
                if ( $wgContLang->isRTL() ) {
@@ -263,9 +263,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
 
                $data['thumblimits'] = $config->get( 'ThumbLimits' );
-               $this->getResult()->setIndexedTagName( $data['thumblimits'], 'limit' );
+               ApiResult::setIndexedTagName( $data['thumblimits'], 'limit' );
                $data['imagelimits'] = array();
-               $this->getResult()->setIndexedTagName( $data['imagelimits'], 'limit' );
+               ApiResult::setIndexedTagName( $data['imagelimits'], 'limit' );
                foreach ( $config->get( 'ImageLimits' ) as $k => $limit ) {
                        $data['imagelimits'][$k] = array( 'width' => $limit[0], 'height' => $limit[1] );
                }
@@ -290,7 +290,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                'id' => intval( $ns ),
                                'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
                        );
-                       ApiResult::setContent( $data[$ns], $title );
+                       ApiResult::setContentValue( $data[$ns], 'name', $title );
                        $canonical = MWNamespace::getCanonicalName( $ns );
 
                        if ( MWNamespace::hasSubpages( $ns ) ) {
@@ -315,7 +315,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        }
                }
 
-               $this->getResult()->setIndexedTagName( $data, 'ns' );
+               ApiResult::setIndexedTagName( $data, 'ns' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -334,13 +334,13 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        $item = array(
                                'id' => intval( $ns )
                        );
-                       ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
+                       ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
                        $data[] = $item;
                }
 
                sort( $data );
 
-               $this->getResult()->setIndexedTagName( $data, 'ns' );
+               ApiResult::setIndexedTagName( $data, 'ns' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -352,11 +352,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                foreach ( SpecialPageFactory::getNames() as $specialpage ) {
                        if ( isset( $aliases[$specialpage] ) ) {
                                $arr = array( 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] );
-                               $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
+                               ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
                                $data[] = $arr;
                        }
                }
-               $this->getResult()->setIndexedTagName( $data, 'specialpage' );
+               ApiResult::setIndexedTagName( $data, 'specialpage' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -370,10 +370,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        if ( $caseSensitive ) {
                                $arr['case-sensitive'] = '';
                        }
-                       $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
+                       ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
                        $data[] = $arr;
                }
-               $this->getResult()->setIndexedTagName( $data, 'magicword' );
+               ApiResult::setIndexedTagName( $data, 'magicword' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -442,7 +442,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        $data[] = $val;
                }
 
-               $this->getResult()->setIndexedTagName( $data, 'iw' );
+               ApiResult::setIndexedTagName( $data, 'iw' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -477,7 +477,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                }
 
                $result = $this->getResult();
-               $result->setIndexedTagName( $data, 'db' );
+               ApiResult::setIndexedTagName( $data, 'db' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -533,16 +533,16 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                        $groups = array_intersect( $rights[$group], $allGroups );
                                        if ( $groups ) {
                                                $arr[$type] = $groups;
-                                               $result->setIndexedTagName( $arr[$type], 'group' );
+                                               ApiResult::setIndexedTagName( $arr[$type], 'group' );
                                        }
                                }
                        }
 
-                       $result->setIndexedTagName( $arr['rights'], 'permission' );
+                       ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
                        $data[] = $arr;
                }
 
-               $result->setIndexedTagName( $data, 'group' );
+               ApiResult::setIndexedTagName( $data, 'group' );
 
                return $result->addValue( 'query', $property, $data );
        }
@@ -552,7 +552,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
                        $data[] = array( 'ext' => $ext );
                }
-               $this->getResult()->setIndexedTagName( $data, 'fe' );
+               ApiResult::setIndexedTagName( $data, 'fe' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -581,7 +581,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                'version' => $info['version'],
                        );
                }
-               $this->getResult()->setIndexedTagName( $data, 'library' );
+               ApiResult::setIndexedTagName( $data, 'library' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
 
@@ -607,7 +607,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                        if ( is_array( $ext['descriptionmsg'] ) ) {
                                                $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
                                                $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
-                                               $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
+                                               ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
                                        } else {
                                                $ret['descriptionmsg'] = $ext['descriptionmsg'];
                                        }
@@ -667,7 +667,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        }
                }
 
-               $this->getResult()->setIndexedTagName( $data, 'ext' );
+               ApiResult::setIndexedTagName( $data, 'ext' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -704,10 +704,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
                );
 
-               $this->getResult()->setIndexedTagName( $data['types'], 'type' );
-               $this->getResult()->setIndexedTagName( $data['levels'], 'level' );
-               $this->getResult()->setIndexedTagName( $data['cascadinglevels'], 'level' );
-               $this->getResult()->setIndexedTagName( $data['semiprotectedlevels'], 'level' );
+               ApiResult::setIndexedTagName( $data['types'], 'type' );
+               ApiResult::setIndexedTagName( $data['levels'], 'level' );
+               ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
+               ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -721,10 +721,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
 
                foreach ( $langNames as $code => $name ) {
                        $lang = array( 'code' => $code );
-                       ApiResult::setContent( $lang, $name );
+                       ApiResult::setContentValue( $lang, 'name', $name );
                        $data[] = $lang;
                }
-               $this->getResult()->setIndexedTagName( $data, 'lang' );
+               ApiResult::setIndexedTagName( $data, 'lang' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -745,7 +745,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                $displayName = $msg->text();
                        }
                        $skin = array( 'code' => $name );
-                       ApiResult::setContent( $skin, $displayName );
+                       ApiResult::setContentValue( $skin, 'name', $displayName );
                        if ( !isset( $allowed[$name] ) ) {
                                $skin['unusable'] = '';
                        }
@@ -754,7 +754,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        }
                        $data[] = $skin;
                }
-               $this->getResult()->setIndexedTagName( $data, 'skin' );
+               ApiResult::setIndexedTagName( $data, 'skin' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
@@ -763,7 +763,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                global $wgParser;
                $wgParser->firstCallInit();
                $tags = array_map( array( $this, 'formatParserTags' ), $wgParser->getTags() );
-               $this->getResult()->setIndexedTagName( $tags, 't' );
+               ApiResult::setIndexedTagName( $tags, 't' );
 
                return $this->getResult()->addValue( 'query', $property, $tags );
        }
@@ -772,14 +772,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                global $wgParser;
                $wgParser->firstCallInit();
                $hooks = $wgParser->getFunctionHooks();
-               $this->getResult()->setIndexedTagName( $hooks, 'h' );
+               ApiResult::setIndexedTagName( $hooks, 'h' );
 
                return $this->getResult()->addValue( 'query', $property, $hooks );
        }
 
        public function appendVariables( $property ) {
                $variables = MagicWord::getVariableIDs();
-               $this->getResult()->setIndexedTagName( $variables, 'v' );
+               ApiResult::setIndexedTagName( $variables, 'v' );
 
                return $this->getResult()->addValue( 'query', $property, $variables );
        }
@@ -787,7 +787,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
        public function appendProtocols( $property ) {
                // Make a copy of the global so we don't try to set the _element key of it - bug 45130
                $protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
-               $this->getResult()->setIndexedTagName( $protocols, 'p' );
+               ApiResult::setIndexedTagName( $protocols, 'p' );
 
                return $this->getResult()->addValue( 'query', $property, $protocols );
        }
@@ -812,11 +812,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                                'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $subscribers ),
                        );
 
-                       $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
+                       ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
                        $data[] = $arr;
                }
 
-               $this->getResult()->setIndexedTagName( $data, 'hook' );
+               ApiResult::setIndexedTagName( $data, 'hook' );
 
                return $this->getResult()->addValue( 'query', $property, $data );
        }
index 342e367..1126842 100644 (file)
@@ -59,7 +59,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
                                $finalThumbParam = $this->mergeThumbParams( $file, $scale, $params['urlparam'] );
                                $imageInfo = ApiQueryImageInfo::getInfo( $file, $prop, $result, $finalThumbParam );
                                $result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo );
-                               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix );
+                               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), $modulePrefix );
                        }
                // @todo Update exception handling here to understand current getFile exceptions
                } catch ( UploadStashFileNotFoundException $e ) {
index 0e3307b..aa91216 100644 (file)
@@ -135,7 +135,7 @@ class ApiQueryTags extends ApiQueryBase {
                        }
                }
 
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'tag' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'tag' );
        }
 
        public function getCacheMode( $params ) {
index 41f7ee7..f6c6356 100644 (file)
@@ -120,7 +120,7 @@ class ApiQueryContributions extends ApiQueryBase {
                        }
                }
 
-               $this->getResult()->setIndexedTagName_internal(
+               $this->getResult()->addIndexedTagName(
                        array( 'query', $this->getModuleName() ),
                        'item'
                );
@@ -421,7 +421,7 @@ class ApiQueryContributions extends ApiQueryBase {
                if ( $this->fld_tags ) {
                        if ( $row->ts_tags ) {
                                $tags = explode( ',', $row->ts_tags );
-                               $this->getResult()->setIndexedTagName( $tags, 'tag' );
+                               ApiResult::setIndexedTagName( $tags, 'tag' );
                                $vals['tags'] = $tags;
                        } else {
                                $vals['tags'] = array();
index 820a360..fd095fc 100644 (file)
@@ -84,26 +84,26 @@ class ApiQueryUserInfo extends ApiQueryBase {
 
                if ( isset( $this->prop['groups'] ) ) {
                        $vals['groups'] = $user->getEffectiveGroups();
-                       $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
+                       ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty
                }
 
                if ( isset( $this->prop['implicitgroups'] ) ) {
                        $vals['implicitgroups'] = $user->getAutomaticGroups();
-                       $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
+                       ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
                }
 
                if ( isset( $this->prop['rights'] ) ) {
                        // User::getRights() may return duplicate values, strip them
                        $vals['rights'] = array_values( array_unique( $user->getRights() ) );
-                       $result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
+                       ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
                }
 
                if ( isset( $this->prop['changeablegroups'] ) ) {
                        $vals['changeablegroups'] = $user->changeableGroups();
-                       $result->setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
-                       $result->setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
-                       $result->setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
-                       $result->setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
+                       ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
+                       ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
+                       ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
+                       ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
                }
 
                if ( isset( $this->prop['options'] ) ) {
@@ -159,10 +159,10 @@ class ApiQueryUserInfo extends ApiQueryBase {
                        $acceptLang = array();
                        foreach ( $langs as $lang => $val ) {
                                $r = array( 'q' => $val );
-                               ApiResult::setContent( $r, $lang );
+                               ApiResult::setContentValue( $r, 'code', $lang );
                                $acceptLang[] = $r;
                        }
-                       $result->setIndexedTagName( $acceptLang, 'lang' );
+                       ApiResult::setIndexedTagName( $acceptLang, 'lang' );
                        $vals['acceptlang'] = $acceptLang;
                }
 
index 52636cc..e2c47f5 100644 (file)
@@ -256,13 +256,13 @@ class ApiQueryUsers extends ApiQueryBase {
                                }
                        } else {
                                if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
-                                       $result->setIndexedTagName( $data[$u]['groups'], 'g' );
+                                       ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
                                }
                                if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
-                                       $result->setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
+                                       ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
                                }
                                if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
-                                       $result->setIndexedTagName( $data[$u]['rights'], 'r' );
+                                       ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
                                }
                        }
 
@@ -275,7 +275,7 @@ class ApiQueryUsers extends ApiQueryBase {
                        }
                        $done[] = $u;
                }
-               $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
+               $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'user' );
        }
 
        public function getCacheMode( $params ) {
index 3857a08..04eea54 100644 (file)
@@ -281,7 +281,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                }
 
                if ( is_null( $resultPageSet ) ) {
-                       $this->getResult()->setIndexedTagName_internal(
+                       $this->getResult()->addIndexedTagName(
                                array( 'query', $this->getModuleName() ),
                                'item'
                        );
index ae3596d..493c192 100644 (file)
@@ -123,7 +123,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
                        }
                }
                if ( is_null( $resultPageSet ) ) {
-                       $this->getResult()->setIndexedTagName_internal( $this->getModuleName(), 'wr' );
+                       $this->getResult()->addIndexedTagName( $this->getModuleName(), 'wr' );
                } else {
                        $resultPageSet->populateFromTitles( $titles );
                }
index 306c478..da28010 100644 (file)
@@ -1,11 +1,5 @@
 <?php
 /**
- *
- *
- * Created on Sep 4, 2006
- *
- * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@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
  * Each subarray may either be a dictionary - key-value pairs with unique keys,
  * or lists, where the items are added using $data[] = $value notation.
  *
- * There are three special key values that change how XML output is generated:
- *   '_element'     This key sets the tag name for the rest of the elements in the current array.
- *                  It is only inserted if the formatter returned true for getNeedsRawData()
- *   '_subelements' This key causes the specified elements to be returned as subelements rather than attributes.
- *                  It is only inserted if the formatter returned true for getNeedsRawData()
- *   '*'            This key has special meaning only to the XML formatter, and is outputted as is
- *                  for all others. In XML it becomes the content of the current element.
- *
+ * @since 1.25 this is no longer a subclass of ApiBase
  * @ingroup API
  */
-class ApiResult extends ApiBase {
+class ApiResult implements ApiSerializable {
 
        /**
-        * override existing value in addValue() and setElement()
+        * Override existing value in addValue(), setValue(), and similar functions
         * @since 1.21
         */
        const OVERRIDE = 1;
 
        /**
-        * For addValue() and setElement(), if the value does not exist, add it as the first element.
-        * In case the new value has no name (numerical index), all indexes will be renumbered.
+        * For addValue(), setValue() and similar functions, if the value does not
+        * exist, add it as the first element. In case the new value has no name
+        * (numerical index), all indexes will be renumbered.
         * @since 1.21
         */
        const ADD_ON_TOP = 2;
 
        /**
-        * For addValue() and setElement(), do not check size while adding a value
+        * For addValue() and similar functions, do not check size while adding a value
         * Don't use this unless you REALLY know what you're doing.
-        * Values added while the size checking was disabled will never be counted
+        * Values added while the size checking was disabled will never be counted.
+        * Ignored for setValue() and similar functions.
         * @since 1.24
         */
        const NO_SIZE_CHECK = 4;
 
-       private $mData, $mIsRawMode, $mSize, $mCheckingSize;
+       /**
+        * For addValue(), setValue() and similar functions, do not validate data.
+        * Also disables size checking. If you think you need to use this, you're
+        * probably wrong.
+        * @since 1.25
+        */
+       const NO_VALIDATE = 12;
 
-       private $continueAllModules = array();
-       private $continueGeneratedModules = array();
-       private $continuationData = array();
-       private $generatorContinuationData = array();
-       private $generatorParams = array();
-       private $generatorDone = false;
+       /**
+        * Key for the 'indexed tag name' metadata item. Value is string.
+        * @since 1.25
+        */
+       const META_INDEXED_TAG_NAME = '_element';
 
        /**
-        * @param ApiMain $main
+        * Key for the 'subelements' metadata item. Value is string[].
+        * @since 1.25
         */
-       public function __construct( ApiMain $main ) {
-               parent::__construct( $main, 'result' );
-               $this->mIsRawMode = false;
-               $this->mCheckingSize = true;
-               $this->reset();
-       }
+       const META_SUBELEMENTS = '_subelements';
 
        /**
-        * Clear the current result data.
+        * Key for the 'preserve keys' metadata item. Value is string[].
+        * @since 1.25
         */
-       public function reset() {
-               $this->mData = array();
-               $this->mSize = 0;
-       }
+       const META_PRESERVE_KEYS = '_preservekeys';
 
        /**
-        * Call this function when special elements such as '_element'
-        * are needed by the formatter, for example in XML printing.
-        * @since 1.23 $flag parameter added
-        * @param bool $flag Set the raw mode flag to this state
+        * Key for the 'content' metadata item. Value is string.
+        * @since 1.25
         */
-       public function setRawMode( $flag = true ) {
-               $this->mIsRawMode = $flag;
-       }
+       const META_CONTENT = '_content';
 
        /**
-        * Returns true whether the formatter requested raw data.
-        * @return bool
+        * Key for the 'type' metadata item. Value is one of the following strings:
+        *  - default: Like 'array' if all (non-metadata) keys are numeric with no
+        *    gaps, otherwise like 'assoc'.
+        *  - array: Keys are used for ordering, but are not output. In a format
+        *    like JSON, outputs as [].
+        *  - assoc: In a format like JSON, outputs as {}.
+        *  - kvp: For a format like XML where object keys have a restricted
+        *    character set, use an alternative output format. For example,
+        *    <container><item name="key">value</item></container> rather than
+        *    <container key="value" />
+        *  - BCarray: Like 'array' normally, 'default' in backwards-compatibility mode.
+        *  - BCassoc: Like 'assoc' normally, 'default' in backwards-compatibility mode.
+        *  - BCkvp: Like 'kvp' normally. In backwards-compatibility mode, forces
+        *    the alternative output format for all formats, for example
+        *    [{"name":key,"*":value}] in JSON. META_KVP_KEY_NAME must also be set.
+        * @since 1.25
         */
-       public function getIsRawMode() {
-               return $this->mIsRawMode;
-       }
+       const META_TYPE = '_type';
 
        /**
-        * Get the result's internal data array (read-only)
-        * @return array
+        * Key (rather than "name" or other default) for when META_TYPE is 'kvp' or
+        * 'BCkvp'. Value is string.
+        * @since 1.25
         */
-       public function getData() {
-               return $this->mData;
-       }
+       const META_KVP_KEY_NAME = '_kvpkeyname';
 
        /**
-        * Get the 'real' size of a result item. This means the strlen() of the item,
-        * or the sum of the strlen()s of the elements if the item is an array.
-        * @param mixed $value
-        * @return int
+        * Key for the 'BC bools' metadata item. Value is string[].
+        * Note no setter is provided.
+        * @since 1.25
         */
-       public static function size( $value ) {
-               $s = 0;
-               if ( is_array( $value ) ) {
-                       foreach ( $value as $v ) {
-                               $s += self::size( $v );
-                       }
-               } elseif ( !is_object( $value ) ) {
-                       // Objects can't always be cast to string
-                       $s = strlen( $value );
+       const META_BC_BOOLS = '_BC_bools';
+
+       /**
+        * Key for the 'BC subelements' metadata item. Value is string[].
+        * Note no setter is provided.
+        * @since 1.25
+        */
+       const META_BC_SUBELEMENTS = '_BC_subelements';
+
+       private $data, $size, $maxSize;
+       private $errorFormatter;
+
+       // Deprecated fields
+       private $isRawMode, $checkingSize, $mainForContinuation;
+
+       /**
+        * @param int|bool $maxSize Maximum result "size", or false for no limit
+        * @since 1.25 Takes an integer|bool rather than an ApiMain
+        */
+       public function __construct( $maxSize ) {
+               if ( $maxSize instanceof ApiMain ) {
+                       /// @todo: After fixing Wikidata unit tests, warn
+                       //wfDeprecated( 'Passing ApiMain to ' . __METHOD__ . ' is deprecated', '1.25' );
+                       $this->errorFormatter = $maxSize->getErrorFormatter();
+                       $this->mainForContinuation = $maxSize;
+                       $maxSize = $maxSize->getConfig()->get( 'APIMaxResultSize' );
                }
 
-               return $s;
+               $this->maxSize = $maxSize;
+               $this->isRawMode = false;
+               $this->checkingSize = true;
+               $this->reset();
        }
 
        /**
-        * Get the size of the result, i.e. the amount of bytes in it
-        * @return int
+        * Set the error formatter
+        * @since 1.25
+        * @param ApiErrorFormatter $formatter
         */
-       public function getSize() {
-               return $this->mSize;
+       public function setErrorFormatter( ApiErrorFormatter $formatter ) {
+               $this->errorFormatter = $formatter;
        }
 
        /**
-        * Disable size checking in addValue(). Don't use this unless you
-        * REALLY know what you're doing. Values added while size checking
-        * was disabled will not be counted (ever)
-        * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
+        * Allow for adding one ApiResult into another
+        * @since 1.25
+        * @return mixed
         */
-       public function disableSizeCheck() {
-               $this->mCheckingSize = false;
+       public function serializeForApiResult() {
+               return $this->data;
        }
 
+       /************************************************************************//**
+        * @name   Content
+        * @{
+        */
+
        /**
-        * Re-enable size checking in addValue()
-        * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
+        * Clear the current result data.
         */
-       public function enableSizeCheck() {
-               $this->mCheckingSize = true;
+       public function reset() {
+               $this->data = array();
+               $this->size = 0;
+       }
+
+       /**
+        * Get the result data array
+        *
+        * The returned value should be considered read-only.
+        *
+        * Transformations include:
+        *
+        * Custom: (callable) Applied before other transformations. Signature is
+        *  function ( &$data, &$metadata ), return value is ignored. Called for
+        *  each nested array.
+        *
+        * BC: (array) This transformation does various adjustments to bring the
+        *  output in line with the pre-1.25 result format. The value array is a
+        *  list of flags: 'nobools', 'no*', 'nosub'.
+        *  - Boolean-valued items are changed to '' if true or removed if false,
+        *    unless listed in META_BC_BOOLS. This may be skipped by including
+        *    'nobools' in the value array.
+        *  - The tag named by META_CONTENT is renamed to '*', and META_CONTENT is
+        *    set to '*'. This may be skipped by including 'no*' in the value
+        *    array.
+        *  - Tags listed in META_BC_SUBELEMENTS will have their values changed to
+        *    array( '*' => $value ). This may be skipped by including 'nosub' in
+        *    the value array.
+        *  - If META_TYPE is 'BCarray', set it to 'default'
+        *  - If META_TYPE is 'BCassoc', set it to 'default'
+        *  - If META_TYPE is 'BCkvp', perform the transformation (even if
+        *    the Types transformation is not being applied).
+        *
+        * Types: (assoc) Apply transformations based on META_TYPE. The values
+        * array is an associative array with the following possible keys:
+        *  - AssocAsObject: (bool) If true, return arrays with META_TYPE 'assoc'
+        *    as objects.
+        *  - ArmorKVP: (string) If provided, transform arrays with META_TYPE 'kvp'
+        *    and 'BCkvp' into arrays of two-element arrays, something like this:
+        *      $output = array();
+        *      foreach ( $input as $key => $value ) {
+        *          $pair = array();
+        *          $pair[$META_KVP_KEY_NAME ?: $ArmorKVP_value] = $key;
+        *          ApiResult::setContentValue( $pair, 'value', $value );
+        *          $output[] = $pair;
+        *      }
+        *
+        * Strip: (string) Strips metadata keys from the result.
+        *  - 'all': Strip all metadata, recursively
+        *  - 'base': Strip metadata at the top-level only.
+        *  - 'none': Do not strip metadata.
+        *  - 'bc': Like 'all', but leave certain pre-1.25 keys.
+        *
+        * @since 1.25
+        * @param array|string|null $path Path to fetch, see ApiResult::addValue
+        * @param array $transforms See above
+        * @return mixed Result data, or null if not found
+        */
+       public function getResultData( $path = array(), $transforms = array() ) {
+               $path = (array)$path;
+               if ( !$path ) {
+                       return self::applyTransformations( $this->data, $transforms );
+               }
+
+               $last = array_pop( $path );
+               $ret = &$this->path( $path, 'dummy' );
+               if ( !isset( $ret[$last] ) ) {
+                       return null;
+               } elseif ( is_array( $ret[$last] ) ) {
+                       return self::applyTransformations( $ret[$last], $transforms );
+               } else {
+                       return $ret[$last];
+               }
+       }
+
+       /**
+        * Get the size of the result, i.e. the amount of bytes in it
+        * @return int
+        */
+       public function getSize() {
+               return $this->size;
        }
 
        /**
         * Add an output value to the array by name.
+        *
         * Verifies that value with the same name has not been added before.
-        * @param array $arr To add $value to
-        * @param string $name Index of $arr to add $value at
+        *
+        * @since 1.25
+        * @param array &$arr To add $value to
+        * @param string|int|null $name Index of $arr to add $value at,
+        *   or null to use the next numeric index.
         * @param mixed $value
         * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
-        *    This parameter used to be boolean, and the value of OVERRIDE=1 was
-        *    specifically chosen so that it would be backwards compatible with the
-        *    new method signature.
-        *
-        * @since 1.21 int $flags replaced boolean $override
         */
-       public static function setElement( &$arr, $name, $value, $flags = 0 ) {
-               if ( $arr === null || $name === null || $value === null
-                       || !is_array( $arr ) || is_array( $name )
-               ) {
-                       ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+       public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
+               if ( $name === null ) {
+                       if ( $flags & ApiResult::ADD_ON_TOP ) {
+                               array_unshift( $arr, $value );
+                       } else {
+                               array_push( $arr, $value );
+                       }
+                       return;
+               }
+
+               if ( !( $flags & ApiResult::NO_VALIDATE ) ) {
+                       $value = self::validateValue( $value );
                }
 
                $exists = isset( $arr[$name] );
@@ -193,451 +296,1197 @@ class ApiResult extends ApiBase {
                                $arr[$name] = $value;
                        }
                } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
-                       $merged = array_intersect_key( $arr[$name], $value );
-                       if ( !count( $merged ) ) {
+                       $conflicts = array_intersect_key( $arr[$name], $value );
+                       if ( !$conflicts ) {
                                $arr[$name] += $value;
                        } else {
-                               ApiBase::dieDebug( __METHOD__, "Attempting to merge element $name" );
+                               $keys = join( ', ', array_keys( $conflicts ) );
+                               throw new RuntimeException( "Conflicting keys ($keys) when attempting to merge element $name" );
                        }
                } else {
-                       ApiBase::dieDebug(
-                               __METHOD__,
-                               "Attempting to add element $name=$value, existing value is {$arr[$name]}"
-                       );
+                       throw new RuntimeException( "Attempting to add element $name=$value, existing value is {$arr[$name]}" );
                }
        }
 
        /**
-        * Adds a content element to an array.
-        * Use this function instead of hardcoding the '*' element.
-        * @param array $arr To add the content element to
+        * Validate a value for addition to the result
         * @param mixed $value
-        * @param string $subElemName When present, content element is created
-        *  as a sub item of $arr. Use this parameter to create elements in
-        *  format "<elem>text</elem>" without attributes.
         */
-       public static function setContent( &$arr, $value, $subElemName = null ) {
-               if ( is_array( $value ) ) {
-                       ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+       private static function validateValue( $value ) {
+               global $wgContLang;
+
+               if ( is_object( $value ) ) {
+                       // Note we use is_callable() here instead of instanceof because
+                       // ApiSerializable is an informal protocol (see docs there for details).
+                       if ( is_callable( array( $value, 'serializeForApiResult' ) ) ) {
+                               $oldValue = $value;
+                               $value = $value->serializeForApiResult();
+                               if ( is_object( $value ) ) {
+                                       throw new UnexpectedValueException(
+                                               get_class( $oldValue ) . "::serializeForApiResult() returned an object of class " .
+                                                       get_class( $value )
+                                       );
+                               }
+
+                               // Recursive call instead of fall-through so we can throw a
+                               // better exception message.
+                               try {
+                                       return self::validateValue( $value );
+                               } catch ( Exception $ex ) {
+                                       throw new UnexpectedValueException(
+                                               get_class( $oldValue ) . "::serializeForApiResult() returned an invalid value: " .
+                                                       $ex->getMessage(),
+                                               0,
+                                               $ex
+                                       );
+                               }
+                       } elseif ( is_callable( array( $value, '__toString' ) ) ) {
+                               $value = (string)$value;
+                       } else {
+                               $value = (array)$value + array( self::META_TYPE => 'assoc' );
+                       }
                }
-               if ( is_null( $subElemName ) ) {
-                       ApiResult::setElement( $arr, '*', $value );
-               } else {
-                       if ( !isset( $arr[$subElemName] ) ) {
-                               $arr[$subElemName] = array();
+               if ( is_array( $value ) ) {
+                       foreach ( $value as $k => $v ) {
+                               $value[$k] = self::validateValue( $v );
                        }
-                       ApiResult::setElement( $arr[$subElemName], '*', $value );
+               } elseif ( is_float( $value ) && !is_finite( $value ) ) {
+                       throw new InvalidArgumentException( "Cannot add non-finite floats to ApiResult" );
+               } elseif ( is_string( $value ) ) {
+                       $value = $wgContLang->normalize( $value );
+               } elseif ( $value !== null && !is_scalar( $value ) ) {
+                       $type = gettype( $value );
+                       if ( is_resource( $value ) ) {
+                               $type .= '(' . get_resource_type( $value ) . ')';
+                       }
+                       throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
                }
+
+               return $value;
        }
 
        /**
-        * Causes the elements with the specified names to be output as
-        * subelements rather than attributes.
-        * @param array $arr
-        * @param array|string $names The element name(s) to be output as subelements
+        * Add value to the output data at the given path.
+        *
+        * Path can be an indexed array, each element specifying the branch at which to add the new
+        * value. Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value.
+        * If $path is null, the value will be inserted at the data root.
+        *
+        * @param array|string|int|null $path
+        * @param string|int|null $name See ApiResult::setValue()
+        * @param mixed $value
+        * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+        *   This parameter used to be boolean, and the value of OVERRIDE=1 was specifically
+        *   chosen so that it would be backwards compatible with the new method signature.
+        * @return bool True if $value fits in the result, false if not
+        * @since 1.21 int $flags replaced boolean $override
         */
-       public function setSubelements( &$arr, $names ) {
-               // In raw mode, add the '_subelements', otherwise just ignore
-               if ( !$this->getIsRawMode() ) {
-                       return;
-               }
-               if ( $arr === null || $names === null || !is_array( $arr ) ) {
-                       ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
-               }
-               if ( !is_array( $names ) ) {
-                       $names = array( $names );
-               }
-               if ( !isset( $arr['_subelements'] ) ) {
-                       $arr['_subelements'] = $names;
-               } else {
-                       $arr['_subelements'] = array_merge( $arr['_subelements'], $names );
+       public function addValue( $path, $name, $value, $flags = 0 ) {
+               $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
+
+               if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
+                       $newsize = $this->size + self::valueSize( $value );
+                       if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
+                               /// @todo Add i18n message when replacing calls to ->setWarning()
+                               $msg = new ApiRawMessage( 'This result was truncated because it would otherwise ' .
+                                       ' be larger than the limit of $1 bytes', 'truncatedresult' );
+                               $msg->numParams( $this->maxSize );
+                               $this->errorFormatter->addWarning( 'result', $msg );
+                               return false;
+                       }
+                       $this->size = $newsize;
                }
+
+               self::setValue( $arr, $name, $value, $flags );
+               return true;
        }
 
        /**
-        * In case the array contains indexed values (in addition to named),
-        * give all indexed values the given tag name. This function MUST be
-        * called on every array that has numerical indexes.
-        * @param array $arr
-        * @param string $tag Tag name
+        * Remove an output value to the array by name.
+        * @param array &$arr To remove $value from
+        * @param string|int $name Index of $arr to remove
+        * @return mixed Old value, or null
         */
-       public function setIndexedTagName( &$arr, $tag ) {
-               // In raw mode, add the '_element', otherwise just ignore
-               if ( !$this->getIsRawMode() ) {
-                       return;
-               }
-               if ( $arr === null || $tag === null || !is_array( $arr ) || is_array( $tag ) ) {
-                       ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+       public static function unsetValue( array &$arr, $name ) {
+               $ret = null;
+               if ( isset( $arr[$name] ) ) {
+                       $ret = $arr[$name];
+                       unset( $arr[$name] );
                }
-               // Do not use setElement() as it is ok to call this more than once
-               $arr['_element'] = $tag;
+               return $ret;
        }
 
        /**
-        * Calls setIndexedTagName() on each sub-array of $arr
-        * @param array $arr
-        * @param string $tag Tag name
+        * Remove value from the output data at the given path.
+        *
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string|int|null $name Index to remove at $path.
+        *   If null, $path itself is removed.
+        * @param int $flags Flags used when adding the value
+        * @return mixed Old value, or null
         */
-       public function setIndexedTagName_recursive( &$arr, $tag ) {
-               if ( !is_array( $arr ) ) {
-                       return;
-               }
-               foreach ( $arr as &$a ) {
-                       if ( !is_array( $a ) ) {
-                               continue;
+       public function removeValue( $path, $name, $flags = 0 ) {
+               $path = (array)$path;
+               if ( $name === null ) {
+                       if ( !$path ) {
+                               throw new InvalidArgumentException( 'Cannot remove the data root' );
                        }
-                       $this->setIndexedTagName( $a, $tag );
-                       $this->setIndexedTagName_recursive( $a, $tag );
+                       $name = array_pop( $path );
+               }
+               $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
+               if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
+                       $newsize = $this->size - self::valueSize( $ret );
+                       $this->size = max( $newsize, 0 );
                }
+               return $ret;
        }
 
        /**
-        * Calls setIndexedTagName() on an array already in the result.
-        * Don't specify a path to a value that's not in the result, or
-        * you'll get nasty errors.
-        * @param array $path Path to the array, like addValue()'s $path
-        * @param string $tag
+        * Add an output value to the array by name and mark as META_CONTENT.
+        *
+        * @since 1.25
+        * @param array &$arr To add $value to
+        * @param string|int $name Index of $arr to add $value at.
+        * @param mixed $value
+        * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
         */
-       public function setIndexedTagName_internal( $path, $tag ) {
-               $data = &$this->mData;
-               foreach ( (array)$path as $p ) {
-                       if ( !isset( $data[$p] ) ) {
-                               $data[$p] = array();
-                       }
-                       $data = &$data[$p];
-               }
-               if ( is_null( $data ) ) {
-                       return;
+       public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
+               if ( $name === null ) {
+                       throw new InvalidArgumentException( 'Content value must be named' );
                }
-               $this->setIndexedTagName( $data, $tag );
+               self::setContentField( $arr, $name, $flags );
+               self::setValue( $arr, $name, $value, $flags );
        }
 
        /**
-        * Add value to the output data at the given path.
-        * Path can be an indexed array, each element specifying the branch at which to add the new
-        * value. Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value.
-        * If $path is null, the value will be inserted at the data root.
-        * If $name is empty, the $value is added as a next list element data[] = $value.
+        * Add value to the output data at the given path and mark as META_CONTENT
         *
-        * @param array|string|null $path
-        * @param string $name
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string|int $name See ApiResult::setValue()
         * @param mixed $value
         * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
-        *   This parameter used to be boolean, and the value of OVERRIDE=1 was specifically
-        *   chosen so that it would be backwards compatible with the new method signature.
         * @return bool True if $value fits in the result, false if not
-        *
-        * @since 1.21 int $flags replaced boolean $override
         */
-       public function addValue( $path, $name, $value, $flags = 0 ) {
-               $data = &$this->mData;
-               if ( $this->mCheckingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
-                       $newsize = $this->mSize + self::size( $value );
-                       $maxResultSize = $this->getConfig()->get( 'APIMaxResultSize' );
-                       if ( $newsize > $maxResultSize ) {
-                               $this->setWarning(
-                                       "This result was truncated because it would otherwise be larger than the " .
-                                               "limit of {$maxResultSize} bytes" );
-
-                               return false;
-                       }
-                       $this->mSize = $newsize;
-               }
-
-               $addOnTop = $flags & ApiResult::ADD_ON_TOP;
-               if ( $path !== null ) {
-                       foreach ( (array)$path as $p ) {
-                               if ( !isset( $data[$p] ) ) {
-                                       if ( $addOnTop ) {
-                                               $data = array( $p => array() ) + $data;
-                                               $addOnTop = false;
-                                       } else {
-                                               $data[$p] = array();
-                                       }
-                               }
-                               $data = &$data[$p];
-                       }
-               }
-
-               if ( !$name ) {
-                       // Add list element
-                       if ( $addOnTop ) {
-                               // This element needs to be inserted in the beginning
-                               // Numerical indexes will be renumbered
-                               array_unshift( $data, $value );
-                       } else {
-                               // Add new value at the end
-                               $data[] = $value;
-                       }
-               } else {
-                       // Add named element
-                       self::setElement( $data, $name, $value, $flags );
+       public function addContentValue( $path, $name, $value, $flags = 0 ) {
+               if ( $name === null ) {
+                       throw new InvalidArgumentException( 'Content value must be named' );
                }
-
-               return true;
+               $this->addContentField( $path, $name, $flags );
+               $this->addValue( $path, $name, $value, $flags );
        }
 
        /**
-        * Add a parsed limit=max to the result.
+        * Add the numeric limit for a limit=max to the result.
         *
+        * @since 1.25
         * @param string $moduleName
         * @param int $limit
         */
-       public function setParsedLimit( $moduleName, $limit ) {
+       public function addParsedLimit( $moduleName, $limit ) {
                // Add value, allowing overwriting
-               $this->addValue( 'limits', $moduleName, $limit, ApiResult::OVERRIDE );
+               $this->addValue( 'limits', $moduleName, $limit,
+                       ApiResult::OVERRIDE | ApiResult::NO_SIZE_CHECK );
        }
 
+       /**@}*/
+
+       /************************************************************************//**
+        * @name   Metadata
+        * @{
+        */
+
        /**
-        * Unset a value previously added to the result set.
-        * Fails silently if the value isn't found.
-        * For parameters, see addValue()
-        * @param array|null $path
-        * @param string $name
+        * Set the name of the content field name (META_CONTENT)
+        *
+        * @since 1.25
+        * @param array &$arr
+        * @param string|int $name Name of the field
+        * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
         */
-       public function unsetValue( $path, $name ) {
-               $data = &$this->mData;
-               if ( $path !== null ) {
-                       foreach ( (array)$path as $p ) {
-                               if ( !isset( $data[$p] ) ) {
-                                       return;
-                               }
-                               $data = &$data[$p];
-                       }
+       public static function setContentField( array &$arr, $name, $flags = 0 ) {
+               if ( isset( $arr[self::META_CONTENT] ) &&
+                       isset( $arr[$arr[self::META_CONTENT]] ) &&
+                       !( $flags & self::OVERRIDE )
+               ) {
+                       throw new RuntimeException(
+                               "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
+                                       " is already set as the content element"
+                       );
                }
-               $this->mSize -= self::size( $data[$name] );
-               unset( $data[$name] );
+               $arr[self::META_CONTENT] = $name;
        }
 
        /**
-        * Ensure all values in this result are valid UTF-8.
+        * Set the name of the content field name (META_CONTENT)
+        *
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string|int $name Name of the field
+        * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
         */
-       public function cleanUpUTF8() {
-               array_walk_recursive( $this->mData, array( 'ApiResult', 'cleanUp_helper' ) );
+       public function addContentField( $path, $name, $flags = 0 ) {
+               $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
+               self::setContentField( $arr, $name, $flags );
        }
 
        /**
-        * Callback function for cleanUpUTF8()
-        *
-        * @param string $s
+        * Causes the elements with the specified names to be output as
+        * subelements rather than attributes.
+        * @since 1.25 is static
+        * @param array &$arr
+        * @param array|string|int $names The element name(s) to be output as subelements
         */
-       private static function cleanUp_helper( &$s ) {
-               if ( !is_string( $s ) ) {
-                       return;
+       public static function setSubelementsList( array &$arr, $names ) {
+               if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
+                       $arr[self::META_SUBELEMENTS] = (array)$names;
+               } else {
+                       $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
                }
-               global $wgContLang;
-               $s = $wgContLang->normalize( $s );
        }
 
        /**
-        * Converts a Status object to an array suitable for addValue
-        * @param Status $status
-        * @param string $errorType
-        * @return array
+        * Causes the elements with the specified names to be output as
+        * subelements rather than attributes.
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param array|string|int $names The element name(s) to be output as subelements
         */
-       public function convertStatusToArray( $status, $errorType = 'error' ) {
-               if ( $status->isGood() ) {
-                       return array();
-               }
+       public function addSubelementsList( $path, $names ) {
+               $arr = &$this->path( $path );
+               self::setSubelementsList( $arr, $names );
+       }
 
-               $result = array();
-               foreach ( $status->getErrorsByType( $errorType ) as $error ) {
-                       $this->setIndexedTagName( $error['params'], 'param' );
-                       $result[] = $error;
+       /**
+        * Causes the elements with the specified names to be output as
+        * attributes (when possible) rather than as subelements.
+        * @since 1.25
+        * @param array &$arr
+        * @param array|string|int $names The element name(s) to not be output as subelements
+        */
+       public static function unsetSubelementsList( array &$arr, $names ) {
+               if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
+                       $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
                }
-               $this->setIndexedTagName( $result, $errorType );
-
-               return $result;
        }
 
-       public function execute() {
-               ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
+       /**
+        * Causes the elements with the specified names to be output as
+        * attributes (when possible) rather than as subelements.
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param array|string|int $names The element name(s) to not be output as subelements
+        */
+       public function removeSubelementsList( $path, $names ) {
+               $arr = &$this->path( $path );
+               self::unsetSubelementsList( $arr, $names );
        }
 
        /**
-        * Parse a 'continue' parameter and return status information.
-        *
-        * This must be balanced by a call to endContinuation().
-        *
-        * @since 1.24
-        * @param string|null $continue The "continue" parameter, if any
-        * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
-        * @param array $generatedModules Names of modules that depend on the generator
-        * @return array Two elements: a boolean indicating if the generator is done,
-        *   and an array of modules to actually execute.
+        * Set the tag name for numeric-keyed values in XML format
+        * @since 1.25 is static
+        * @param array &$arr
+        * @param string $tag Tag name
         */
-       public function beginContinuation(
-               $continue, array $allModules = array(), array $generatedModules = array()
-       ) {
-               $this->continueGeneratedModules = $generatedModules
-                       ? array_combine( $generatedModules, $generatedModules )
-                       : array();
-               $this->continuationData = array();
-               $this->generatorContinuationData = array();
-               $this->generatorParams = array();
-
-               $skip = array();
-               if ( is_string( $continue ) && $continue !== '' ) {
-                       $continue = explode( '||', $continue );
-                       $this->dieContinueUsageIf( count( $continue ) !== 2 );
-                       $this->generatorDone = ( $continue[0] === '-' );
-                       $skip = explode( '|', $continue[1] );
-                       if ( !$this->generatorDone ) {
-                               $this->generatorParams = explode( '|', $continue[0] );
-                       } else {
-                               // When the generator is complete, don't run any modules that
-                               // depend on it.
-                               $skip += $this->continueGeneratedModules;
-                       }
-               }
-
-               $this->continueAllModules = array();
-               $runModules = array();
-               foreach ( $allModules as $module ) {
-                       $name = $module->getModuleName();
-                       if ( in_array( $name, $skip ) ) {
-                               $this->continueAllModules[$name] = false;
-                               // Prevent spurious "unused parameter" warnings
-                               $module->extractRequestParams();
-                       } else {
-                               $this->continueAllModules[$name] = true;
-                               $runModules[] = $module;
-                       }
+       public static function setIndexedTagName( array &$arr, $tag ) {
+               if ( !is_string( $tag ) ) {
+                       throw new InvalidArgumentException( 'Bad tag name' );
                }
+               $arr[self::META_INDEXED_TAG_NAME] = $tag;
+       }
 
-               return array(
-                       $this->generatorDone,
-                       $runModules,
-               );
+       /**
+        * Set the tag name for numeric-keyed values in XML format
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string $tag Tag name
+        */
+       public function addIndexedTagName( $path, $tag ) {
+               $arr = &$this->path( $path );
+               self::setIndexedTagName( $arr, $tag );
        }
 
        /**
-        * Set the continuation parameter for a module
+        * Set indexed tag name on $arr and all subarrays
         *
-        * @since 1.24
-        * @param ApiBase $module
-        * @param string $paramName
-        * @param string|array $paramValue
-        * @throws MWException
+        * @since 1.25
+        * @param array &$arr
+        * @param string $tag Tag name
         */
-       public function setContinueParam( ApiBase $module, $paramName, $paramValue ) {
-               $name = $module->getModuleName();
-               if ( !isset( $this->continueAllModules[$name] ) ) {
-                       throw new MWException(
-                               "Module '$name' called ApiResult::setContinueParam but was not " .
-                               'passed to ApiResult::beginContinuation'
+       public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
+               if ( !is_string( $tag ) ) {
+                       throw new InvalidArgumentException( 'Bad tag name' );
+               }
+               $arr[self::META_INDEXED_TAG_NAME] = $tag;
+               foreach ( $arr as $k => &$v ) {
+                       if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
+                               self::setIndexedTagNameRecursive( $v, $tag );
+                       }
+               }
+       }
+
+       /**
+        * Set indexed tag name on $path and all subarrays
+        *
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string $tag Tag name
+        */
+       public function addIndexedTagNameRecursive( $path, $tag ) {
+               $arr = &$this->path( $path );
+               self::setIndexedTagNameRecursive( $arr, $tag );
+       }
+
+       /**
+        * Preserve specified keys.
+        *
+        * This prevents XML name mangling and preventing keys from being removed
+        * by self::stripMetadata().
+        *
+        * @since 1.25
+        * @param array &$arr
+        * @param array|string $names The element name(s) to preserve
+        */
+       public static function setPreserveKeysList( array &$arr, $names ) {
+               if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
+                       $arr[self::META_PRESERVE_KEYS] = (array)$names;
+               } else {
+                       $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
+               }
+       }
+
+       /**
+        * Preserve specified keys.
+        * @since 1.25
+        * @see self::setPreserveKeysList()
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param array|string $names The element name(s) to preserve
+        */
+       public function addPreserveKeysList( $path, $names ) {
+               $arr = &$this->path( $path );
+               self::setPreserveKeysList( $arr, $names );
+       }
+
+       /**
+        * Don't preserve specified keys.
+        * @since 1.25
+        * @see self::setPreserveKeysList()
+        * @param array &$arr
+        * @param array|string $names The element name(s) to not preserve
+        */
+       public static function unsetPreserveKeysList( array &$arr, $names ) {
+               if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
+                       $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
+               }
+       }
+
+       /**
+        * Don't preserve specified keys.
+        * @since 1.25
+        * @see self::setPreserveKeysList()
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param array|string $names The element name(s) to not preserve
+        */
+       public function removePreserveKeysList( $path, $names ) {
+               $arr = &$this->path( $path );
+               self::unsetPreserveKeysList( $arr, $names );
+       }
+
+       /**
+        * Set the array data type
+        *
+        * @since 1.25
+        * @param array &$arr
+        * @param string $type See ApiResult::META_TYPE
+        * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+        */
+       public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
+               if ( !in_array( $type, array( 'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp' ), true ) ) {
+                       throw new InvalidArgumentException( 'Bad type' );
+               }
+               $arr[self::META_TYPE] = $type;
+               if ( is_string( $kvpKeyName ) ) {
+                       $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
+               }
+       }
+
+       /**
+        * Set the array data type for a path
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string $type See ApiResult::META_TYPE
+        * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+        */
+       public function addArrayType( $path, $tag, $kvpKeyName = null ) {
+               $arr = &$this->path( $path );
+               self::setArrayType( $arr, $tag, $kvpKeyName );
+       }
+
+       /**
+        * Set the array data type recursively
+        * @since 1.25
+        * @param array &$arr
+        * @param string $type See ApiResult::META_TYPE
+        * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+        */
+       public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
+               self::setArrayType( $arr, $type, $kvpKeyName );
+               foreach ( $arr as $k => &$v ) {
+                       if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
+                               self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
+                       }
+               }
+       }
+
+       /**
+        * Set the array data type for a path recursively
+        * @since 1.25
+        * @param array|string|null $path See ApiResult::addValue()
+        * @param string $type See ApiResult::META_TYPE
+        * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+        */
+       public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
+               $arr = &$this->path( $path );
+               self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
+       }
+
+       /**@}*/
+
+       /************************************************************************//**
+        * @name   Utility
+        * @{
+        */
+
+       /**
+        * Test whether a key should be considered metadata
+        *
+        * @param string $key
+        * @return bool
+        */
+       public static function isMetadataKey( $key ) {
+               return substr( $key, 0, 1 ) === '_';
+       }
+
+       /**
+        * Apply transformations to an array, returning the transformed array.
+        *
+        * @see ApiResult::getResultData()
+        * @since 1.25
+        * @param array $data
+        * @param array $transforms
+        * @return array|object
+        */
+       protected static function applyTransformations( array $dataIn, array $transforms ) {
+               $strip = isset( $transforms['Strip'] ) ? $transforms['Strip'] : 'none';
+               if ( $strip === 'base' ) {
+                       $transforms['Strip'] = 'none';
+               }
+               $transformTypes = isset( $transforms['Types'] ) ? $transforms['Types'] : null;
+               if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
+               }
+
+               $metadata = array();
+               $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
+
+               if ( isset( $transforms['Custom'] ) ) {
+                       if ( !is_callable( $transforms['Custom'] ) ) {
+                               throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
+                       }
+                       call_user_func_array( $transforms['Custom'], array( &$data, &$metadata ) );
+               }
+
+               if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
+                       isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
+                       !isset( $metadata[self::META_KVP_KEY_NAME] )
+               ) {
+                       throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
+                               'ApiResult::META_KVP_KEY_NAME metadata item' );
+               }
+
+               // BC transformations
+               $boolKeys = null;
+               $forceKVP = false;
+               if ( isset( $transforms['BC'] ) ) {
+                       if ( !is_array( $transforms['BC'] ) ) {
+                               throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
+                       }
+                       if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
+                               $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
+                                       ? array_flip( $metadata[self::META_BC_BOOLS] )
+                                       : array();
+                       }
+
+                       if ( !in_array( 'no*', $transforms['BC'], true ) &&
+                               isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
+                       ) {
+                               $k = $metadata[self::META_CONTENT];
+                               $data['*'] = $data[$k];
+                               unset( $data[$k] );
+                               $metadata[self::META_CONTENT] = '*';
+                       }
+
+                       if ( !in_array( 'nosub', $transforms['BC'], true ) &&
+                               isset( $metadata[self::META_BC_SUBELEMENTS] )
+                       ) {
+                               foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
+                                       $data[$k] = array(
+                                               '*' => $data[$k],
+                                               self::META_CONTENT => '*',
+                                               self::META_TYPE => 'assoc',
+                                       );
+                               }
+                       }
+
+                       if ( isset( $metadata[self::META_TYPE] ) ) {
+                               switch ( $metadata[self::META_TYPE] ) {
+                                       case 'BCarray':
+                                       case 'BCassoc':
+                                               $metadata[self::META_TYPE] = 'default';
+                                               break;
+                                       case 'BCkvp':
+                                               $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
+                                               break;
+                               }
+                       }
+               }
+
+               // Figure out type, do recursive calls, and do boolean transform if necessary
+               $defaultType = 'array';
+               $maxKey = -1;
+               foreach ( $data as $k => &$v ) {
+                       $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
+                       if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
+                               if ( !$v ) {
+                                       unset( $data[$k] );
+                                       continue;
+                               }
+                               $v = '';
+                       }
+                       if ( is_string( $k ) ) {
+                               $defaultType = 'assoc';
+                       } elseif ( $k > $maxKey ) {
+                               $maxKey = $k;
+                       }
+               }
+               unset( $v );
+
+               // Determine which metadata to keep
+               switch ( $strip ) {
+                       case 'all':
+                       case 'base':
+                               $keepMetadata = array();
+                               break;
+                       case 'none':
+                               $keepMetadata = &$metadata;
+                               break;
+                       case 'bc':
+                               $keepMetadata = array_intersect_key( $metadata, array(
+                                       self::META_INDEXED_TAG_NAME => 1,
+                                       self::META_SUBELEMENTS => 1,
+                               ) );
+                               break;
+                       default:
+                               throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
+               }
+
+               // Type transformation
+               if ( $transformTypes !== null ) {
+                       if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
+                               $defaultType = 'assoc';
+                       }
+
+                       // Override type, if provided
+                       $type = $defaultType;
+                       if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
+                               $type = $metadata[self::META_TYPE];
+                       }
+                       if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
+                               empty( $transformTypes['ArmorKVP'] )
+                       ) {
+                               $type = 'assoc';
+                       } elseif ( $type === 'BCarray' ) {
+                               $type = 'array';
+                       } elseif ( $type === 'BCassoc' ) {
+                               $type = 'assoc';
+                       }
+
+                       // Apply transformation
+                       switch ( $type ) {
+                               case 'assoc':
+                                       $metadata[self::META_TYPE] = 'assoc';
+                                       $data += $keepMetadata;
+                                       return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
+
+                               case 'array':
+                                       ksort( $data );
+                                       $data = array_values( $data );
+                                       $metadata[self::META_TYPE] = 'array';
+                                       return $data + $keepMetadata;
+
+                               case 'kvp':
+                               case 'BCkvp':
+                                       $key = isset( $metadata[self::META_KVP_KEY_NAME] )
+                                               ? $metadata[self::META_KVP_KEY_NAME]
+                                               : $transformTypes['ArmorKVP'];
+                                       $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
+                                       $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
+
+                                       $ret = array();
+                                       foreach ( $data as $k => $v ) {
+                                               $item = array(
+                                                       $key => $k,
+                                                       $valKey => $v,
+                                               );
+                                               if ( $strip === 'none' ) {
+                                                       $item += array(
+                                                               self::META_PRESERVE_KEYS => array( $key ),
+                                                               self::META_CONTENT => $valKey,
+                                                               self::META_TYPE => 'assoc',
+                                                       );
+                                               }
+                                               $ret[] = $assocAsObject ? (object)$item : $item;
+                                       }
+                                       $metadata[self::META_TYPE] = 'array';
+
+                                       return $ret + $keepMetadata;
+
+                               default:
+                                       throw new UnexpectedValueException( "Unknown type '$type'" );
+                       }
+               } else {
+                       return $data + $keepMetadata;
+               }
+       }
+
+       /**
+        * Recursively remove metadata keys from a data array or object
+        *
+        * Note this removes all potential metadata keys, not just the defined
+        * ones.
+        *
+        * @since 1.25
+        * @param array|object $data
+        * @return array|object
+        */
+       public static function stripMetadata( $data ) {
+               if ( is_array( $data ) || is_object( $data ) ) {
+                       $isObj = is_object( $data );
+                       if ( $isObj ) {
+                               $data = (array)$data;
+                       }
+                       $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
+                               ? (array)$data[self::META_PRESERVE_KEYS]
+                               : array();
+                       foreach ( $data as $k => $v ) {
+                               if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
+                                       unset( $data[$k] );
+                               } elseif ( is_array( $v ) || is_object( $v ) ) {
+                                       $data[$k] = self::stripMetadata( $v );
+                               }
+                       }
+                       if ( $isObj ) {
+                               $data = (object)$data;
+                       }
+               }
+               return $data;
+       }
+
+       /**
+        * Remove metadata keys from a data array or object, non-recursive
+        *
+        * Note this removes all potential metadata keys, not just the defined
+        * ones.
+        *
+        * @since 1.25
+        * @param array|object $data
+        * @param array &$metadata Store metadata here, if provided
+        * @return array|object
+        */
+       public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
+               if ( !is_array( $metadata ) ) {
+                       $metadata = array();
+               }
+               if ( is_array( $data ) || is_object( $data ) ) {
+                       $isObj = is_object( $data );
+                       if ( $isObj ) {
+                               $data = (array)$data;
+                       }
+                       $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
+                               ? (array)$data[self::META_PRESERVE_KEYS]
+                               : array();
+                       foreach ( $data as $k => $v ) {
+                               if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
+                                       $metadata[$k] = $v;
+                                       unset( $data[$k] );
+                               }
+                       }
+                       if ( $isObj ) {
+                               $data = (object)$data;
+                       }
+               }
+               return $data;
+       }
+
+       /**
+        * Get the 'real' size of a result item. This means the strlen() of the item,
+        * or the sum of the strlen()s of the elements if the item is an array.
+        * @note Once the deprecated public self::size is removed, we can rename this back to a less awkward name.
+        * @param mixed $value
+        * @return int
+        */
+       private static function valueSize( $value ) {
+               $s = 0;
+               if ( is_array( $value ) ||
+                       is_object( $value ) && !is_callable( array( $value, '__toString' ) )
+               ) {
+                       foreach ( $value as $k => $v ) {
+                               if ( !self::isMetadataKey( $s ) ) {
+                                       $s += self::valueSize( $v );
+                               }
+                       }
+               } elseif ( is_scalar( $value ) ) {
+                       $s = strlen( $value );
+               }
+
+               return $s;
+       }
+
+       /**
+        * Return a reference to the internal data at $path
+        *
+        * @param array|string|null $path
+        * @param string $create
+        *   If 'append', append empty arrays.
+        *   If 'prepend', prepend empty arrays.
+        *   If 'dummy', return a dummy array.
+        *   Else, raise an error.
+        * @return array
+        */
+       private function &path( $path, $create = 'append' ) {
+               $path = (array)$path;
+               $ret = &$this->data;
+               foreach ( $path as $i => $k ) {
+                       if ( !isset( $ret[$k] ) ) {
+                               switch ( $create ) {
+                                       case 'append':
+                                               $ret[$k] = array();
+                                               break;
+                                       case 'prepend':
+                                               $ret = array( $k => array() ) + $ret;
+                                               break;
+                                       case 'dummy':
+                                               $tmp = array();
+                                               return $tmp;
+                                       default:
+                                               $fail = join( '.', array_slice( $path, 0, $i + 1 ) );
+                                               throw new InvalidArgumentException( "Path $fail does not exist" );
+                               }
+                       }
+                       if ( !is_array( $ret[$k] ) ) {
+                               $fail = join( '.', array_slice( $path, 0, $i + 1 ) );
+                               throw new InvalidArgumentException( "Path $fail is not an array" );
+                       }
+                       $ret = &$ret[$k];
+               }
+               return $ret;
+       }
+
+       /**@}*/
+
+       /************************************************************************//**
+        * @name   Deprecated
+        * @{
+        */
+
+       /**
+        * Call this function when special elements such as '_element'
+        * are needed by the formatter, for example in XML printing.
+        * @deprecated since 1.25, you shouldn't have been using it in the first place
+        * @since 1.23 $flag parameter added
+        * @param bool $flag Set the raw mode flag to this state
+        */
+       public function setRawMode( $flag = true ) {
+               // Can't wfDeprecated() here, since we need to set this flag from
+               // ApiMain for BC with stuff using self::getIsRawMode as
+               // "self::getIsXMLMode".
+               $this->isRawMode = $flag;
+       }
+
+       /**
+        * Returns true whether the formatter requested raw data.
+        * @deprecated since 1.25, you shouldn't have been using it in the first place
+        * @return bool
+        */
+       public function getIsRawMode() {
+               /// @todo: After Wikibase stops calling this, warn
+               return $this->isRawMode;
+       }
+
+       /**
+        * Get the result's internal data array (read-only)
+        * @deprecated since 1.25, use $this->getResultData() instead
+        * @return array
+        */
+       public function getData() {
+               /// @todo: Warn after fixing remaining callers: Wikibase, Gather
+               return $this->getResultData( null, array(
+                       'BC' => array(),
+                       'Types' => array(),
+                       'Strip' => $this->isRawMode ? 'bc' : 'all',
+               ) );
+       }
+
+       /**
+        * Disable size checking in addValue(). Don't use this unless you
+        * REALLY know what you're doing. Values added while size checking
+        * was disabled will not be counted (ever)
+        * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
+        */
+       public function disableSizeCheck() {
+               wfDeprecated( __METHOD__, '1.24' );
+               $this->checkingSize = false;
+       }
+
+       /**
+        * Re-enable size checking in addValue()
+        * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
+        */
+       public function enableSizeCheck() {
+               wfDeprecated( __METHOD__, '1.24' );
+               $this->checkingSize = true;
+       }
+
+       /**
+        * Alias for self::setValue()
+        *
+        * @since 1.21 int $flags replaced boolean $override
+        * @deprecated since 1.25, use self::setValue() instead
+        * @param array $arr To add $value to
+        * @param string $name Index of $arr to add $value at
+        * @param mixed $value
+        * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+        *    This parameter used to be boolean, and the value of OVERRIDE=1 was
+        *    specifically chosen so that it would be backwards compatible with the
+        *    new method signature.
+        */
+       public static function setElement( &$arr, $name, $value, $flags = 0 ) {
+               /// @todo: Warn after fixing remaining callers: Wikibase
+               return self::setValue( $arr, $name, $value, $flags );
+       }
+
+       /**
+        * Adds a content element to an array.
+        * Use this function instead of hardcoding the '*' element.
+        * @deprecated since 1.25, use self::setContentValue() instead
+        * @param array $arr To add the content element to
+        * @param mixed $value
+        * @param string $subElemName When present, content element is created
+        *  as a sub item of $arr. Use this parameter to create elements in
+        *  format "<elem>text</elem>" without attributes.
+        */
+       public static function setContent( &$arr, $value, $subElemName = null ) {
+               /// @todo: Warn after fixing remaining callers: Wikibase
+               if ( is_array( $value ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ': Bad parameter' );
+               }
+               if ( is_null( $subElemName ) ) {
+                       self::setContentValue( $arr, 'content', $value );
+               } else {
+                       if ( !isset( $arr[$subElemName] ) ) {
+                               $arr[$subElemName] = array();
+                       }
+                       self::setContentValue( $arr[$subElemName], 'content', $value );
+               }
+       }
+
+       /**
+        * Set indexed tag name on all subarrays of $arr
+        *
+        * Does not set the tag name for $arr itself.
+        *
+        * @deprecated since 1.25, use self::setIndexedTagNameRecursive() instead
+        * @param array $arr
+        * @param string $tag Tag name
+        */
+       public function setIndexedTagName_recursive( &$arr, $tag ) {
+               /// @todo: Warn after fixing remaining callers: Wikibase
+               if ( !is_array( $arr ) ) {
+                       return;
+               }
+               self::setIndexedTagNameOnSubarrays( $arr, $tag );
+       }
+
+       /**
+        * Set indexed tag name on all subarrays of $arr
+        *
+        * Does not set the tag name for $arr itself.
+        *
+        * @since 1.25
+        * @deprecated For backwards compatibility, do not use
+        * @todo: Remove after updating callers to use self::setIndexedTagNameRecursive
+        * @param array &$arr
+        * @param string $tag Tag name
+        */
+       public static function setIndexedTagNameOnSubarrays( array &$arr, $tag ) {
+               if ( !is_string( $tag ) ) {
+                       throw new InvalidArgumentException( 'Bad tag name' );
+               }
+               foreach ( $arr as $k => &$v ) {
+                       if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
+                               $v[self::META_INDEXED_TAG_NAME] = $tag;
+                               self::setIndexedTagNameOnSubarrays( $v, $tag );
+                       }
+               }
+       }
+
+       /**
+        * Alias for self::defineIndexedTagName()
+        * @deprecated since 1.25, use $this->addIndexedTagName() instead
+        * @param array $path Path to the array, like addValue()'s $path
+        * @param string $tag
+        */
+       public function setIndexedTagName_internal( $path, $tag ) {
+               /// @todo: Warn after fixing remaining callers: Wikibase, Gather
+               $this->addIndexedTagName( $path, $tag );
+       }
+
+       /**
+        * Alias for self::addParsedLimit()
+        * @deprecated since 1.25, use $this->addParsedLimit() instead
+        * @param string $moduleName
+        * @param int $limit
+        */
+       public function setParsedLimit( $moduleName, $limit ) {
+               wfDeprecated( __METHOD__, '1.25' );
+               $this->addParsedLimit( $moduleName, $limit );
+       }
+
+       /**
+        * Set the ApiMain for use by $this->beginContinuation()
+        * @since 1.25
+        * @deprecated for backwards compatibility only, do not use
+        * @param ApiMain $main
+        */
+       public function setMainForContinuation( ApiMain $main ) {
+               $this->mainForContinuation = $main;
+       }
+
+       /**
+        * Parse a 'continue' parameter and return status information.
+        *
+        * This must be balanced by a call to endContinuation().
+        *
+        * @since 1.24
+        * @deprecated since 1.25, use ApiContinuationManager instead
+        * @param string|null $continue
+        * @param ApiBase[] $allModules
+        * @param array $generatedModules
+        * @return array
+        */
+       public function beginContinuation(
+               $continue, array $allModules = array(), array $generatedModules = array()
+       ) {
+               /// @todo: Warn after fixing remaining callers: Gather
+               if ( $this->mainForContinuation->getContinuationManager() ) {
+                       throw new UnexpectedValueException(
+                               __METHOD__ . ': Continuation already in progress from ' .
+                               $this->mainForContinuation->getContinuationManager()->getSource()
                        );
                }
-               if ( !$this->continueAllModules[$name] ) {
-                       throw new MWException(
-                               "Module '$name' was not supposed to have been executed, but " .
-                               'it was executed anyway'
+
+               // Ugh. If $continue doesn't match that in the request, temporarily
+               // replace the request when creating the ApiContinuationManager.
+               if ( $continue === null ) {
+                       $continue = '';
+               }
+               if ( $this->mainForContinuation->getVal( 'continue', '' ) !== $continue ) {
+                       $oldCtx = $this->mainForContinuation->getContext();
+                       $newCtx = new DerivativeContext( $oldCtx );
+                       $newCtx->setRequest( new DerivativeRequest(
+                               $oldCtx->getRequest(),
+                               array( 'continue' => $continue ) + $oldCtx->getRequest()->getValues(),
+                               $oldCtx->getRequest()->wasPosted()
+                       ) );
+                       $this->mainForContinuation->setContext( $newCtx );
+                       $reset = new ScopedCallback(
+                               array( $this->mainForContinuation, 'setContext' ),
+                               array( $oldCtx )
                        );
                }
-               $paramName = $module->encodeParamName( $paramName );
-               if ( is_array( $paramValue ) ) {
-                       $paramValue = join( '|', $paramValue );
+               $manager = new ApiContinuationManager(
+                       $this->mainForContinuation, $allModules, $generatedModules
+               );
+               $reset = null;
+
+               $this->mainForContinuation->setContinuationManager( $manager );
+
+               return array(
+                       $manager->isGeneratorDone(),
+                       $manager->getRunModules(),
+               );
+       }
+
+       /**
+        * @since 1.24
+        * @deprecated since 1.25, use ApiContinuationManager instead
+        * @param ApiBase $module
+        * @param string $paramName
+        * @param string|array $paramValue
+        */
+       public function setContinueParam( ApiBase $module, $paramName, $paramValue ) {
+               wfDeprecated( __METHOD__, '1.25' );
+               if ( $this->mainForContinuation->getContinuationManager() ) {
+                       $this->mainForContinuation->getContinuationManager()->addContinueParam(
+                               $module, $paramName, $paramValue
+                       );
                }
-               $this->continuationData[$name][$paramName] = $paramValue;
        }
 
        /**
-        * Set the continuation parameter for the generator module
-        *
         * @since 1.24
+        * @deprecated since 1.25, use ApiContinuationManager instead
         * @param ApiBase $module
         * @param string $paramName
         * @param string|array $paramValue
         */
        public function setGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
-               $name = $module->getModuleName();
-               $paramName = $module->encodeParamName( $paramName );
-               if ( is_array( $paramValue ) ) {
-                       $paramValue = join( '|', $paramValue );
+               wfDeprecated( __METHOD__, '1.25' );
+               if ( $this->mainForContinuation->getContinuationManager() ) {
+                       $this->mainForContinuation->getContinuationManager()->addGeneratorContinueParam(
+                               $module, $paramName, $paramValue
+                       );
                }
-               $this->generatorContinuationData[$name][$paramName] = $paramValue;
        }
 
        /**
         * Close continuation, writing the data into the result
-        *
         * @since 1.24
+        * @deprecated since 1.25, use ApiContinuationManager instead
         * @param string $style 'standard' for the new style since 1.21, 'raw' for
         *   the style used in 1.20 and earlier.
         */
        public function endContinuation( $style = 'standard' ) {
+               /// @todo: Warn after fixing remaining callers: Gather
+               if ( !$this->mainForContinuation->getContinuationManager() ) {
+                       return;
+               }
+
                if ( $style === 'raw' ) {
-                       $key = 'query-continue';
-                       $data = array_merge_recursive(
-                               $this->continuationData, $this->generatorContinuationData
-                       );
+                       $data = $this->mainForContinuation->getContinuationManager()->getRawContinuation();
+                       if ( $data ) {
+                               $this->addValue( null, 'query-continue', $data, self::ADD_ON_TOP | self::NO_SIZE_CHECK );
+                       }
                } else {
-                       $key = 'continue';
-                       $data = array();
-                       $batchcomplete = false;
+                       $this->mainForContinuation->getContinuationManager()->setContinuationIntoResult( $this );
+               }
+       }
 
-                       $finishedModules = array_diff(
-                               array_keys( $this->continueAllModules ),
-                               array_keys( $this->continuationData )
-                       );
+       /**
+        * No-op, this is now checked on insert.
+        * @deprecated since 1.25
+        */
+       public function cleanUpUTF8() {
+               wfDeprecated( __METHOD__, '1.25' );
+       }
 
-                       // First, grab the non-generator-using continuation data
-                       $continuationData = array_diff_key(
-                               $this->continuationData, $this->continueGeneratedModules
-                       );
-                       foreach ( $continuationData as $module => $kvp ) {
-                               $data += $kvp;
-                       }
+       /**
+        * Get the 'real' size of a result item. This means the strlen() of the item,
+        * or the sum of the strlen()s of the elements if the item is an array.
+        * @deprecated since 1.25, no external users known and there doesn't seem
+        *  to be any case for such use over just checking the return value from the
+        *  add/set methods.
+        * @param mixed $value
+        * @return int
+        */
+       public static function size( $value ) {
+               wfDeprecated( __METHOD__, '1.25' );
+               return self::valueSize( $value );
+       }
 
-                       // Next, handle the generator-using continuation data
-                       $continuationData = array_intersect_key(
-                               $this->continuationData, $this->continueGeneratedModules
-                       );
-                       if ( $continuationData ) {
-                               // Some modules are unfinished: include those params, and copy
-                               // the generator params.
-                               foreach ( $continuationData as $module => $kvp ) {
-                                       $data += $kvp;
-                               }
-                               $data += array_intersect_key(
-                                       $this->getMain()->getRequest()->getValues(),
-                                       array_flip( $this->generatorParams )
-                               );
-                       } elseif ( $this->generatorContinuationData ) {
-                               // All the generator-using modules are complete, but the
-                               // generator isn't. Continue the generator and restart the
-                               // generator-using modules
-                               $this->generatorParams = array();
-                               foreach ( $this->generatorContinuationData as $kvp ) {
-                                       $this->generatorParams = array_merge(
-                                               $this->generatorParams, array_keys( $kvp )
-                                       );
-                                       $data += $kvp;
-                               }
-                               $finishedModules = array_diff(
-                                       $finishedModules, $this->continueGeneratedModules
-                               );
-                               $batchcomplete = true;
-                       } else {
-                               // Generator and prop modules are all done. Mark it so.
-                               $this->generatorDone = true;
-                               $batchcomplete = true;
-                       }
+       /**
+        * Converts a Status object to an array suitable for addValue
+        * @deprecated since 1.25, use ApiErrorFormatter::arrayFromStatus()
+        * @param Status $status
+        * @param string $errorType
+        * @return array
+        */
+       public function convertStatusToArray( $status, $errorType = 'error' ) {
+               /// @todo: Warn after fixing remaining callers: CentralAuth
+               return $this->errorFormatter->arrayFromStatus( $status, $errorType );
+       }
 
-                       // Set 'continue' if any continuation data is set or if the generator
-                       // still needs to run
-                       if ( $data || !$this->generatorDone ) {
-                               $data['continue'] =
-                                       ( $this->generatorDone ? '-' : join( '|', $this->generatorParams ) ) .
-                                       '||' . join( '|', $finishedModules );
-                       }
+       /**
+        * Alias for self::addIndexedTagName
+        *
+        * A bunch of extensions were updated for an earlier version of this
+        * extension which used this name.
+        * @deprecated For backwards compatibility, do not use
+        * @todo: Remove after updating callers to use self::addIndexedTagName
+        */
+       public function defineIndexedTagName( $path, $tag ) {
+               return $this->addIndexedTagName( $path, $tag );
+       }
 
-                       if ( $batchcomplete ) {
-                               $this->addValue( null, 'batchcomplete', '', ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
-                       }
+       /**
+        * Alias for self::stripMetadata
+        *
+        * A bunch of extensions were updated for an earlier version of this
+        * extension which used this name.
+        * @deprecated For backwards compatibility, do not use
+        * @todo: Remove after updating callers to use self::stripMetadata
+        */
+       public static function removeMetadata( $data ) {
+               return self::stripMetadata( $data );
+       }
+
+       /**
+        * Alias for self::stripMetadataNonRecursive
+        *
+        * A bunch of extensions were updated for an earlier version of this
+        * extension which used this name.
+        * @deprecated For backwards compatibility, do not use
+        * @todo: Remove after updating callers to use self::stripMetadataNonRecursive
+        */
+       public static function removeMetadataNonRecursive( $data, &$metadata = null ) {
+               self::stripMetadataNonRecursive( $data, $metadata );
+       }
+
+       /**
+        * @deprecated For backwards compatibility, do not use
+        * @todo: Remove after updating callers
+        */
+       public static function transformForBC( array $data ) {
+               return self::applyTransformations( $data, array(
+                       'BC' => array(),
+               ) );
+       }
+
+       /**
+        * @deprecated For backwards compatibility, do not use
+        * @todo: Remove after updating callers
+        */
+       public static function transformForTypes( $data, $options = array() ) {
+               $transforms = array(
+                       'Types' => array(),
+               );
+               if ( isset( $options['assocAsObject'] ) ) {
+                       $transforms['Types']['AssocAsObject'] = $options['assocAsObject'];
                }
-               if ( $data ) {
-                       $this->addValue( null, $key, $data, ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+               if ( isset( $options['armorKVP'] ) ) {
+                       $transforms['Types']['ArmorKVP'] = $options['armorKVP'];
                }
+               if ( !empty( $options['BC'] ) ) {
+                       $transforms['BC'] = array( 'nobool', 'no*', 'nosub' );
+               }
+               return self::applyTransformations( $data, $transforms );
        }
+
+       /**@}*/
 }
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
index 783a39b..2896231 100644 (file)
@@ -111,7 +111,7 @@ class ApiRevisionDelete extends ApiBase {
                // @codingStandardsIgnoreEnd
 
                $data['items'] = array_values( $data['items'] );
-               $result->setIndexedTagName( $data['items'], 'i' );
+               ApiResult::setIndexedTagName( $data['items'], 'i' );
                $result->addValue( null, $this->getModuleName(), $data );
        }
 
@@ -121,12 +121,12 @@ class ApiRevisionDelete extends ApiBase {
                );
                $errors = $this->formatStatusMessages( $status->getErrorsByType( 'error' ) );
                if ( $errors ) {
-                       $this->getResult()->setIndexedTagName( $errors, 'e' );
+                       ApiResult::setIndexedTagName( $errors, 'e' );
                        $ret['errors'] = $errors;
                }
                $warnings = $this->formatStatusMessages( $status->getErrorsByType( 'warning' ) );
                if ( $warnings ) {
-                       $this->getResult()->setIndexedTagName( $warnings, 'w' );
+                       ApiResult::setIndexedTagName( $warnings, 'w' );
                        $ret['warnings'] = $warnings;
                }
 
@@ -146,14 +146,14 @@ class ApiRevisionDelete extends ApiBase {
                                $message = array( 'message' => $msg->getKey() );
                                if ( $msg->getParams() ) {
                                        $message['params'] = $msg->getParams();
-                                       $result->setIndexedTagName( $message['params'], 'p' );
+                                       ApiResult::setIndexedTagName( $message['params'], 'p' );
                                }
                        } else {
                                $message = array( 'message' => $m['message'] );
                                $msg = wfMessage( $m['message'] );
                                if ( isset( $m['params'] ) ) {
                                        $message['params'] = $m['params'];
-                                       $result->setIndexedTagName( $message['params'], 'p' );
+                                       ApiResult::setIndexedTagName( $message['params'], 'p' );
                                        $msg->params( $m['params'] );
                                }
                        }
index f28e610..d466112 100644 (file)
@@ -37,12 +37,15 @@ class ApiRsd extends ApiBase {
                $result->addValue( null, 'version', '1.0' );
                $result->addValue( null, 'xmlns', 'http://archipelago.phrasewise.com/rsd' );
 
-               $service = array( 'apis' => $this->formatRsdApiList() );
-               ApiResult::setContent( $service, 'MediaWiki', 'engineName' );
-               ApiResult::setContent( $service, 'https://www.mediawiki.org/', 'engineLink' );
-               ApiResult::setContent( $service, Title::newMainPage()->getCanonicalURL(), 'homePageLink' );
+               $service = array(
+                       'apis' => $this->formatRsdApiList(),
+                       'engineName' => 'MediaWiki',
+                       'engineLink' => 'https://www.mediawiki.org/',
+                       'homePageLink' => Title::newMainPage()->getCanonicalURL(),
+               );
 
-               $result->setIndexedTagName( $service['apis'], 'api' );
+               ApiResult::setSubelementsList( $service, array( 'engineName', 'engineLink', 'homePageLink' ) );
+               ApiResult::setIndexedTagName( $service['apis'], 'api' );
 
                $result->addValue( null, 'service', $service );
        }
@@ -123,7 +126,8 @@ class ApiRsd extends ApiBase {
                        );
                        $settings = array();
                        if ( isset( $info['docs'] ) ) {
-                               ApiResult::setContent( $settings, $info['docs'], 'docs' );
+                               $settings['docs'] = $info['docs'];
+                               ApiResult::setSubelementsList( $settings, 'docs' );
                        }
                        if ( isset( $info['settings'] ) ) {
                                foreach ( $info['settings'] as $setting => $val ) {
@@ -133,12 +137,12 @@ class ApiRsd extends ApiBase {
                                                $xmlVal = $val;
                                        }
                                        $setting = array( 'name' => $setting );
-                                       ApiResult::setContent( $setting, $xmlVal );
+                                       ApiResult::setContentValue( $setting, 'value', $xmlVal );
                                        $settings[] = $setting;
                                }
                        }
                        if ( count( $settings ) ) {
-                               $this->getResult()->setIndexedTagName( $settings, 'setting' );
+                               ApiResult::setIndexedTagName( $settings, 'setting' );
                                $data['settings'] = $settings;
                        }
                        $outputData[] = $data;
@@ -157,4 +161,9 @@ class ApiFormatXmlRsd extends ApiFormatXml {
        public function getMimeType() {
                return 'application/rsd+xml';
        }
+
+       public static function recXmlPrint( $name, $value, $indent, $attributes = array() ) {
+               unset( $attributes['_idx'] );
+               return parent::recXmlPrint( $name, $value, $indent, $attributes );
+       }
 }
diff --git a/includes/api/ApiSerializable.php b/includes/api/ApiSerializable.php
new file mode 100644 (file)
index 0000000..70e93a6
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Created on Feb 25, 2015
+ *
+ * Copyright © 2015 Brad Jorsch "bjorsch@wikimedia.org"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 interface allows for overriding the default conversion applied by
+ * ApiResult::validateValue().
+ *
+ * @note This is currently an informal interface; it need not be explicitly
+ *   implemented, as long as the method is provided. This allows for extension
+ *   code to maintain compatibility with older MediaWiki while still taking
+ *   advantage of this where it exists.
+ *
+ * @ingroup API
+ * @since 1.25
+ */
+interface ApiSerializable {
+       /**
+        * Return the value to be added to ApiResult in place of this object.
+        *
+        * The returned value must not be an object, and must pass
+        * all checks done by ApiResult::validateValue().
+        *
+        * @return mixed
+        */
+       public function serializeForApiResult();
+}
index dec64cc..e41ee07 100644 (file)
@@ -46,7 +46,8 @@ class ApiSetNotificationTimestamp extends ApiBase {
                $params = $this->extractRequestParams();
                $this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
 
-               $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+               $continuationManager = new ApiContinuationManager( $this, array(), array() );
+               $this->setContinuationManager( $continuationManager );
 
                $pageSet = $this->getPageSet();
                if ( $params['entirewatchlist'] && $pageSet->getDataSource() !== null ) {
@@ -176,11 +177,12 @@ class ApiSetNotificationTimestamp extends ApiBase {
                                }
                        }
 
-                       $apiResult->setIndexedTagName( $result, 'page' );
+                       ApiResult::setIndexedTagName( $result, 'page' );
                }
                $apiResult->addValue( null, $this->getModuleName(), $result );
 
-               $apiResult->endContinuation();
+               $this->setContinuationManager( null );
+               $continuationManager->setContinuationIntoResult( $apiResult );
        }
 
        /**
index 78a4971..74ae05a 100644 (file)
@@ -514,20 +514,20 @@ class ApiUpload extends ApiBase {
                                        'filetype' => $verification['finalExt'],
                                        'allowed' => array_values( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) )
                                );
-                               $this->getResult()->setIndexedTagName( $extradata['allowed'], 'ext' );
+                               ApiResult::setIndexedTagName( $extradata['allowed'], 'ext' );
 
                                $msg = "Filetype not permitted: ";
                                if ( isset( $verification['blacklistedExt'] ) ) {
                                        $msg .= join( ', ', $verification['blacklistedExt'] );
                                        $extradata['blacklisted'] = array_values( $verification['blacklistedExt'] );
-                                       $this->getResult()->setIndexedTagName( $extradata['blacklisted'], 'ext' );
+                                       ApiResult::setIndexedTagName( $extradata['blacklisted'], 'ext' );
                                } else {
                                        $msg .= $verification['finalExt'];
                                }
                                $this->dieUsage( $msg, 'filetype-banned', 0, $extradata );
                                break;
                        case UploadBase::VERIFICATION_ERROR:
-                               $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
+                               ApiResult::setIndexedTagName( $verification['details'], 'detail' );
                                $this->dieUsage( 'This file did not pass file verification', 'verification-error',
                                        0, array( 'details' => $verification['details'] ) );
                                break;
@@ -559,7 +559,7 @@ class ApiUpload extends ApiBase {
                if ( $warnings ) {
                        // Add indices
                        $result = $this->getResult();
-                       $result->setIndexedTagName( $warnings, 'warning' );
+                       ApiResult::setIndexedTagName( $warnings, 'warning' );
 
                        if ( isset( $warnings['duplicate'] ) ) {
                                $dupes = array();
@@ -567,7 +567,7 @@ class ApiUpload extends ApiBase {
                                foreach ( $warnings['duplicate'] as $dupe ) {
                                        $dupes[] = $dupe->getName();
                                }
-                               $result->setIndexedTagName( $dupes, 'duplicate' );
+                               ApiResult::setIndexedTagName( $dupes, 'duplicate' );
                                $warnings['duplicate'] = $dupes;
                        }
 
@@ -696,7 +696,7 @@ class ApiUpload extends ApiBase {
                                        );
                                }
 
-                               $this->getResult()->setIndexedTagName( $error, 'error' );
+                               ApiResult::setIndexedTagName( $error, 'error' );
                                $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
                        }
                        $result['result'] = 'Success';
index cf8ed5a..3ccdde2 100644 (file)
@@ -63,8 +63,8 @@ class ApiUserrights extends ApiBase {
                );
 
                $result = $this->getResult();
-               $result->setIndexedTagName( $r['added'], 'group' );
-               $result->setIndexedTagName( $r['removed'], 'group' );
+               ApiResult::setIndexedTagName( $r['added'], 'group' );
+               ApiResult::setIndexedTagName( $r['removed'], 'group' );
                $result->addValue( null, $this->getModuleName(), $r );
        }
 
index 09638f3..6a585d4 100644 (file)
@@ -44,7 +44,8 @@ class ApiWatch extends ApiBase {
 
                $params = $this->extractRequestParams();
 
-               $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+               $continuationManager = new ApiContinuationManager( $this, array(), array() );
+               $this->setContinuationManager( $continuationManager );
 
                $pageSet = $this->getPageSet();
                // by default we use pageset to extract the page to work on.
@@ -69,7 +70,7 @@ class ApiWatch extends ApiBase {
                                $r = $this->watchTitle( $title, $user, $params );
                                $res[] = $r;
                        }
-                       $this->getResult()->setIndexedTagName( $res, 'w' );
+                       ApiResult::setIndexedTagName( $res, 'w' );
                } else {
                        // dont allow use of old title parameter with new pageset parameters.
                        $extraParams = array_keys( array_filter( $pageSet->extractRequestParams(), function ( $x ) {
@@ -92,7 +93,9 @@ class ApiWatch extends ApiBase {
                        $res = $this->watchTitle( $title, $user, $params, true );
                }
                $this->getResult()->addValue( null, $this->getModuleName(), $res );
-               $this->getResult()->endContinuation();
+
+               $this->setContinuationManager( null );
+               $continuationManager->setContinuationIntoResult( $this->getResult() );
        }
 
        private function watchTitle( Title $title, User $user, array $params,
index 4055386..ee66238 100644 (file)
        "apihelp-dumpfm-description": "Output data in PHP's <code>var_dump()</code> format (pretty-print in HTML).",
        "apihelp-json-description": "Output data in JSON format.",
        "apihelp-json-param-callback": "If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.",
-       "apihelp-json-param-utf8": "If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences.",
+       "apihelp-json-param-utf8": "If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences. Default when <var>formatversion</var> is not <kbd>1</kbd>.",
+       "apihelp-json-param-ascii": "If specified, encodes all non-ASCII using hexadecimal escape sequences. Default when <var>formatversion</var> is <kbd>1</kbd>.",
+       "apihelp-json-param-formatversion": "Output formatting:\n;1:Backwards-compatible format (XML-style booleans, <samp>*</samp> keys for content nodes, etc.).\n;2:Experimental modern format. Details may change!\n;latest:Use the latest format (currently <kbd>2</kbd>), may change without warning.",
        "apihelp-jsonfm-description": "Output data in JSON format (pretty-print in HTML).",
        "apihelp-none-description": "Output nothing.",
        "apihelp-php-description": "Output data in serialized PHP format.",
+       "apihelp-php-param-formatversion": "Output formatting:\n;1:Backwards-compatible format (XML-style booleans, <samp>*</samp> keys for content nodes, etc.).\n;2:Experimental modern format. Details may change!\n;latest:Use the latest format (currently <kbd>2</kbd>), may change without warning.",
        "apihelp-phpfm-description": "Output data in serialized PHP format (pretty-print in HTML).",
        "apihelp-rawfm-description": "Output data with the debugging elements in JSON format (pretty-print in HTML).",
        "apihelp-txt-description": "Output data in PHP's <code>print_r()</code> format.",
index d097477..5efe91d 100644 (file)
        "api-help-flag-writerights": "Este módulo requiere permisos de escritura.",
        "api-help-flag-mustbeposted": "Este módulo solo acepta solicitudes POST.",
        "api-help-flag-generator": "Este módulo puede utilizarse como un generador.",
+       "api-help-source": "Fuente: $1",
+       "api-help-source-unknown": "Fuente: <span class=\"apihelp-unknown\">desconocida</span>",
+       "api-help-license": "Licencia: [[$1|$2]]",
+       "api-help-license-noname": "Licencia: [[$1|Ver enlace]]",
+       "api-help-license-unknown": "Licencia: <span class=\"apihelp-unknown\">desconocida</span>",
        "api-help-parameters": "{{PLURAL:$1|Parámetro|Parámetros}}:",
        "api-help-param-deprecated": "En desuso.",
        "api-help-param-required": "Este parámetro es obligatorio.",
index 065ced3..dcc27e7 100644 (file)
        "apihelp-paraminfo-param-pagesetmodule": "Obter información sobre o módulo pageset (proporcionando títulos= e amigos).",
        "apihelp-paraminfo-param-formatmodules": "Lista dos nomes de módulo de formato (valores do parámetro <var>formato</var>). No canto use <var>$1modules</var>.",
        "apihelp-paraminfo-example-1": "Amosar información para <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, e <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+       "apihelp-parse-description": "Analiza o contido e devolve o resultado do analizador.\n\nVexa varios módulos propostos de <kbd>[[Special:ApiHelp/query|action=query]]</kbd> para obter información sobre a versión actual dunha páxina.\n\nHai varias formas de especificar o texto a analizar:\n# Especificar unha páxina ou revisión, usando <var>$1page</var>, <var>$1pageid</var>, ou <var>$1oldid</var>.\n# Especificando contido explícitamente, usando <var>$1text</var>, <var>$1title</var>, and <var>$1contentmodel</var>.\n# Especificando só un resumo a analizar. <var>$1prop</var> debe ter un valor baleiro.",
        "apihelp-parse-param-title": "Título da páxina á que pertence o texto. Se non se indica, debe especificarse <var>$1contentmodel</var>, e [[API]] usarase como o título.",
        "apihelp-parse-param-text": "Texto a analizar. Use <var>$1title</var> ou <var>$1contentmodel</var> para controlar o modelo de contido.",
        "apihelp-parse-param-summary": "Resumo a analizar.",
        "api-help-flag-writerights": "Este módulo precisa permisos de escritura.",
        "api-help-flag-mustbeposted": "Este módulo só acepta peticións POST.",
        "api-help-flag-generator": "Este módulo pode usarse como xenerador.",
+       "api-help-source": "Fonte: $1",
+       "api-help-source-unknown": "Fonte: <span class=\"apihelp-unknown\">descoñecida</span>",
+       "api-help-license": "Licenza: [[$1|$2]]",
+       "api-help-license-noname": "Licenza: [[$1|Ver ligazón]]",
+       "api-help-license-unknown": "Licenza: <span class=\"apihelp-unknown\">descoñecida</span>",
        "api-help-parameters": "{{PLURAL:$1|Parámetro|Parámetros}}:",
        "api-help-param-deprecated": "Obsoleto.",
        "api-help-param-required": "Este parámetro é obrigatorio.",
index 905a7f5..a68efc4 100644 (file)
@@ -19,6 +19,7 @@
        "apihelp-main-param-uselang": "メッセージの翻訳に使用する言語です。コードの一覧は <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> に <kbd>siprop=languages</kbd> を付けることで取得できます。<kbd>user</kbd> を指定することで現在の利用者の個人設定の言語を、<kbd>content</kbd> を指定することでこのウィキの本文の言語を使用することもできます。",
        "apihelp-block-description": "利用者をブロックします。",
        "apihelp-block-param-user": "ブロックする利用者名、IPアドレスまたはIPレンジ。",
+       "apihelp-block-param-expiry": "有効期限。相対的 (例: <kbd>5 months</kbd> または <kbd>2 weeks</kbd>) または絶対的 (e.g. <kbd>2014-09-18T12:34:56Z</kbd>) どちらでも構いません。<kbd>infinite</kbd>, <kbd>indefinite</kbd>, もしくは <kbd>never</kbd> と設定した場合, 無期限ブロックとなります。",
        "apihelp-block-param-reason": "ブロックの理由。",
        "apihelp-block-param-anononly": "匿名利用者のみブロックします(つまり、このIPアドレスからの匿名での編集を不可能にします)。",
        "apihelp-block-param-nocreate": "アカウントの作成を禁止します。",
@@ -65,6 +66,7 @@
        "apihelp-edit-param-unwatch": "そのページを現在の利用者のウォッチリストから除去します。",
        "apihelp-edit-param-token": "このトークンは常に最後のパラメーターとして、または少なくとも $1text パラメーターより後に送信されるべきです。",
        "apihelp-edit-example-edit": "ページを編集",
+       "apihelp-edit-example-prepend": "<kbd>_&#95;NOTOC_&#95;</kbd> をページの先頭に挿入する。",
        "apihelp-emailuser-description": "利用者に電子メールを送信します。",
        "apihelp-emailuser-param-target": "送信先の利用者名。",
        "apihelp-emailuser-param-text": "電子メールの本文。",
        "apihelp-help-example-query": "2つの下位モジュールのヘルプ",
        "apihelp-imagerotate-example-simple": "<kbd>File:Example.png</kbd> を <kbd>90</kbd> 度回転させる。",
        "apihelp-imagerotate-example-generator": "<kbd>Category:Flip</kbd> 内のすべての画像を <kbd>180</kbd> 度回転させる。",
+       "apihelp-import-param-summary": "ページ取り込みの要約。",
        "apihelp-import-param-xml": "XMLファイルをアップロード",
        "apihelp-import-param-rootpage": "このページの下位ページとしてインポートする。",
        "apihelp-import-example-import": "[[meta:Help:Parserfunctions]] をすべての履歴とともに名前空間100 にインポートする。",
        "apihelp-opensearch-param-limit": "返す結果の最大数。",
        "apihelp-opensearch-param-namespace": "検索する名前空間。",
        "apihelp-opensearch-param-suggest": "<var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> が false の場合、何もしません。",
+       "apihelp-opensearch-param-format": "出力する形式。",
+       "apihelp-opensearch-example-te": "<kbd>Te</kbd> から始まるページを検索する。",
        "apihelp-paraminfo-description": "API モジュールに関する情報を取得します。",
+       "apihelp-paraminfo-param-modules": "モジュールの名前のリスト (<var>action</var> および <var>format</var> パラメーターの値, または <kbd>main</kbd>). <kbd>+</kbd> を使用して下位モジュールを指定できます。",
        "apihelp-patrol-description": "ページまたは版を巡回済みにします。",
        "apihelp-patrol-param-revid": "巡回済みにする版ID。",
        "apihelp-patrol-example-rcid": "最近の更新を巡回",
        "apihelp-query+alldeletedrevisions-param-excludeuser": "この利用者による版を一覧表示しない。",
        "apihelp-query+alldeletedrevisions-param-namespace": "この名前空間に含まれるページのみを一覧表示します。",
        "apihelp-query+alldeletedrevisions-example-ns-main": "標準名前空間にある削除された最初の50版を一覧表示する。",
+       "apihelp-query+allmessages-param-args": "メッセージ中に展開される引数。",
+       "apihelp-query+allmessages-param-filter": "この文字列を含んだ名前のメッセージのみを返す。",
+       "apihelp-query+allmessages-param-customised": "変更された状態のメッセージのみを返す。",
+       "apihelp-query+allmessages-param-lang": "返すメッセージの言語。",
+       "apihelp-query+allmessages-example-ipb": "<kbd>ipb-</kbd> で始まるメッセージを表示する。",
+       "apihelp-query+allmessages-example-de": "ドイツ語のメッセージ <kbd>august</kbd> および <kbd>mainpage</kbd> を表示する。",
+       "apihelp-query+allusers-param-activeusers": "最近 $1 {{PLURAL:$1|日間}}のアクティブな利用者のみを一覧表示する。",
+       "apihelp-query+allusers-example-Y": "<kbd>Y</kbd> で始まる利用者を一覧表示する。",
+       "apihelp-query+backlinks-example-simple": "<kbd>Main page<kbd> へのリンクを表示する。",
+       "apihelp-query+backlinks-example-generator": "<kbd>Main page<kbd> にリンクしているページの情報を取得する。",
+       "apihelp-query+blocks-example-simple": "ブロックを一覧表示する。",
+       "apihelp-query+blocks-example-users": "利用者<kbd>Alice</kbd> および <kbd>Bob</kbd> のブロックを一覧表示する。",
+       "apihelp-query+categories-example-simple": "ページ <kbd>Albert Einstein</kbd> が属しているカテゴリの一覧を取得する。",
+       "apihelp-query+categories-example-generator": "ページ <kbd>Albert Einstein</kbd> で使われているすべてのカテゴリに関する情報を取得する。",
+       "apihelp-query+categoryinfo-description": "与えられたカテゴリに関する情報を返します。",
+       "apihelp-query+categoryinfo-example-simple": "<kbd>Category:Foo</kbd> および <kbd>Category:Bar</kbd> に関する情報を取得する。",
        "apihelp-query+categorymembers-example-simple": "<kbd>Category:Physics</kbd> に含まれる最初の10ページを取得する。",
        "apihelp-query+categorymembers-example-generator": "<kbd>Category:Physics</kbd> に含まれる最初の10ページのページ情報を取得する。",
        "apihelp-query+contributors-example-simple": "<kbd>Main Page</kbd> への投稿者を表示する。",
        "apihelp-query+iwbacklinks-example-generator": "[[wikibooks:Test]] へリンクしているページの情報を取得する。",
        "apihelp-query+langbacklinks-example-simple": "[[:fr:Test]] へリンクしているページを取得する。",
        "apihelp-query+langbacklinks-example-generator": "[[:fr:Test]] へリンクしているページの情報を取得する。",
+       "apihelp-query+links-param-namespace": "この名前空間へのリンクのみ表示する。",
+       "apihelp-query+links-param-limit": "返すリンクの数。",
+       "apihelp-query+links-example-simple": "<kbd>Main Page</kbd> からのリンクを取得する。",
+       "apihelp-query+links-example-generator": "<kbd>Main Page</kbd> からリンクされているページに関する情報を取得する。",
+       "apihelp-query+links-example-namespaces": "<kbd>Main Page</kbd> からの {{ns:user}} および {{ns:template}} 名前空間へのリンクを取得する。",
        "apihelp-revisiondelete-description": "版の削除および復元を行います。",
        "apihelp-revisiondelete-param-reason": "削除または復元の理由。",
        "apihelp-revisiondelete-example-revision": "<kbd>Main Page</kbd> の版 <kbd>12345</kbd> の本文を隠す。",
index 1e41336..0b19d91 100644 (file)
@@ -89,6 +89,7 @@
        "apihelp-userrights-param-reason": "Grond fir d'Ännerung.",
        "apihelp-watch-example-watch": "D'Säit <kbd>Haaptsäit</kbd> iwwerwaachen.",
        "api-help-source": "Quell: $1",
+       "api-help-source-unknown": "Quell: <span class=\"apihelp-unknown\">onbekannt</span>",
        "api-help-license": "Lizenz: [[$1|$2]]",
        "api-help-license-noname": "LiZenz: [[$1|Kuckt de Link]]",
        "api-help-license-unknown": "Lizenz: <span class=\"apihelp-unknown\">onbekannt</span>",
index 2d09844..d2c6ec9 100644 (file)
        "apihelp-json-description": "{{doc-apihelp-description|json|seealso=* {{msg-mw|apihelp-jsonfm-description}}}}",
        "apihelp-json-param-callback": "{{doc-apihelp-param|json|callback}}",
        "apihelp-json-param-utf8": "{{doc-apihelp-param|json|utf8}}",
+       "apihelp-json-param-ascii": "{{doc-apihelp-param|json|ascii}}",
+       "apihelp-json-param-formatversion": "{{doc-apihelp-param|json|formatversion}}",
        "apihelp-jsonfm-description": "{{doc-apihelp-description|jsonfm|seealso=* {{msg-mw|apihelp-json-description}}}}",
        "apihelp-none-description": "{{doc-apihelp-description|none}}",
        "apihelp-php-description": "{{doc-apihelp-description|php|seealso=* {{msg-mw|apihelp-phpfm-description}}}}",
+       "apihelp-php-param-formatversion": "{{doc-apihelp-param|json|formatversion}}",
        "apihelp-phpfm-description": "{{doc-apihelp-description|phpfm|seealso=* {{msg-mw|apihelp-php-description}}}}",
        "apihelp-rawfm-description": "{{doc-apihelp-description|rawfm|seealso=* {{msg-mw|apihelp-raw-description}}}}",
        "apihelp-txt-description": "{{doc-apihelp-description|txt|seealso=* {{msg-mw|apihelp-txtfm-description}}}}",
index e533d79..2ccfd92 100644 (file)
@@ -4,7 +4,8 @@
                        "Mahairod",
                        "Okras",
                        "Eakarpov",
-                       "Kaganer"
+                       "Kaganer",
+                       "Mariya"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Документация]]\n* [[mw:API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n</div>\n<strong>Статус:</strong> Все отображаемые на этой странице функции должны работать, однако API находится в статусе активной разработки, и может измениться в любой момент. Подпишитесь на  [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом \"MediaWiki-API-Error\", после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:API:Errors_and_warnings|API: Ошибки и предупреждения]].",
@@ -44,6 +45,9 @@
        "apihelp-query+recentchanges-example-simple": "Список последних изменений.",
        "apihelp-upload-example-url": "Загрузить через URL",
        "api-help-main-header": "Главный модуль",
+       "api-help-source": "Источник: $1",
+       "api-help-source-unknown": "Источник: <span class=\"apihelp-unknown\">unknown</span>",
+       "api-help-license": "Лицензия: [[$1|$2]]",
        "api-help-parameters": "Параметр{{PLURAL:$1||ы}}:",
        "api-help-param-deprecated": "Устаревший.",
        "api-help-param-required": "Этот параметр является обязательным.",
index 722803b..1670966 100644 (file)
        "apihelp-query+allcategories-param-limit": "要返回多少个类别。",
        "apihelp-query+allcategories-param-prop": "要获取的属性:\n;size:在分类中添加页面数。\n;hidden:标记由_&#95;HIDDENCAT_&#95;隐藏的分类。",
        "apihelp-query+allcategories-example-size": "列出分类及其含有多少页面的信息。",
+       "apihelp-query+alldeletedrevisions-description": "列举由一位用户或在一个名字空间中所有已删除的修订。",
        "apihelp-query+alldeletedrevisions-paraminfo-useronly": "只可以与<var>$3user</var>一起使用。",
        "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "不能与<var>$3user</var>一起使用。",
        "apihelp-query+alldeletedrevisions-param-start": "枚举的起始时间戳。",
        "apihelp-query+allpages-param-maxsize": "限于至多这么多字节的页面。",
        "apihelp-query+allpages-param-prtype": "仅限于受保护页面。",
        "apihelp-query+allpages-param-limit": "返回的总计页面数。",
+       "apihelp-query+allpages-param-dir": "罗列所采用的方向。",
        "apihelp-query+allpages-example-B": "显示以字母<kbd>B</kbd>开头的页面的列表。",
        "apihelp-query+allpages-example-generator": "显示有关4个以字母<kbd>T</kbd>开头的页面的信息。",
        "apihelp-query+allpages-example-generator-revisions": "显示前2个以<kbd>Re</kbd>开头的非重定向页面的内容。",
        "apihelp-query+redirects-param-limit": "返回多少重定向。",
        "apihelp-query+redirects-example-simple": "获取至[[Project:首页]]的重定向列表",
        "apihelp-query+redirects-example-generator": "获取所有重定向至[[首页]]的信息",
+       "apihelp-query+revisions-paraminfo-singlepageonly": "可能只能与单一页面使用(模式#2)。",
        "apihelp-query+revisions-example-content": "获得带内容的数据,用于标题<kbd>API</kbd>和<kbd>Main Page</kbd>的最近修订。",
        "apihelp-query+revisions-example-last5": "获取<kbd>Main Page</kbd>的最近5次修订。",
        "apihelp-query+revisions-example-first5": "获取<kbd>Main Page</kbd>的前5次修订。",
        "api-help-flag-writerights": "此模块需要写入权限。",
        "api-help-flag-mustbeposted": "此模块只允许POST请求。",
        "api-help-flag-generator": "此模块可作为发生器使用。",
+       "api-help-source": "来源:$1",
+       "api-help-source-unknown": "来源:<span class=\"apihelp-unknown\">未知</span>",
+       "api-help-license": "许可协议:[[$1|$2]]",
+       "api-help-license-noname": "许可协议:[[$1|参见链接]]",
+       "api-help-license-unknown": "许可协议:<span class=\"apihelp-unknown\">未知</span>",
        "api-help-parameters": "{{PLURAL:$1|参数}}:",
        "api-help-param-deprecated": "不推荐使用。",
        "api-help-param-required": "这个参数是必须的。",
index b648ce0..565d159 100644 (file)
@@ -23,7 +23,7 @@
  * Item class for a logging table row with its associated change tags.
  * @todo Abstract out a base class for this and RevDelLogItem, similar to the
  * RevisionItem class but specifically for log items.
- * @since 1.26
+ * @since 1.25
  */
 class ChangeTagsLogItem extends RevisionItemBase {
        public function getIdField() {
index ad274d9..fe80695 100644 (file)
@@ -21,7 +21,7 @@
 
 /**
  * Stores a list of taggable log entries.
- * @since 1.26
+ * @since 1.25
  */
 class ChangeTagsLogList extends ChangeTagsList {
        public function getType() {
index 8225be4..e90a1b4 100644 (file)
@@ -21,7 +21,7 @@
 
 /**
  * Item class for a live revision table row with its associated change tags.
- * @since 1.26
+ * @since 1.25
  */
 class ChangeTagsRevisionItem extends RevisionItem {
        /**
index c51d605..842d327 100644 (file)
@@ -21,7 +21,7 @@
 
 /**
  * Stores a list of taggable revisions.
- * @since 1.26
+ * @since 1.25
  */
 class ChangeTagsRevisionList extends ChangeTagsList {
        public function getType() {
index 1939e06..ae2d995 100644 (file)
@@ -540,11 +540,11 @@ class MWDebug {
                MWDebug::log( 'MWDebug output complete' );
                $debugInfo = self::getDebugInfo( $context );
 
-               $result->setIndexedTagName( $debugInfo, 'debuginfo' );
-               $result->setIndexedTagName( $debugInfo['log'], 'line' );
-               $result->setIndexedTagName( $debugInfo['debugLog'], 'msg' );
-               $result->setIndexedTagName( $debugInfo['queries'], 'query' );
-               $result->setIndexedTagName( $debugInfo['includes'], 'queries' );
+               ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
+               ApiResult::setIndexedTagName( $debugInfo['log'], 'line' );
+               ApiResult::setIndexedTagName( $debugInfo['debugLog'], 'msg' );
+               ApiResult::setIndexedTagName( $debugInfo['queries'], 'query' );
+               ApiResult::setIndexedTagName( $debugInfo['includes'], 'queries' );
                $result->addValue( null, 'debuginfo', $debugInfo );
        }
 
index 27b930a..e163a9b 100644 (file)
@@ -4,7 +4,8 @@
                        "Glavkos",
                        "Protnet",
                        "ZaDiak",
-                       "Astralnet"
+                       "Astralnet",
+                       "Geraki"
                ]
        },
        "config-desc": "Το πρόγραμμα εγκατάστασης για το MediaWiki",
        "config-install-interwiki-list": "Αδυναμία ανάγνωσης του αρχείου <code>interwiki.list</code>.",
        "config-help": "βοήθεια",
        "mainpagetext": "<strong>To MediaWiki εγκαταστάθηκε με επιτυχία.</strong>",
-       "mainpagedocfooter": "ΠεÏ\81ιÏ\83Ï\83Ï\8cÏ\84εÏ\81εÏ\82 Ï\80ληÏ\81οÏ\86οÏ\81ίεÏ\82 Ï\83Ï\87εÏ\84ικά Î¼Îµ Ï\84η Ï\87Ï\81ήÏ\83η ÎºÎ±Î¹ Î¼Îµ Ï\84η Ï\81Ï\8dθμιÏ\83η Ï\80αÏ\81αμέÏ\84Ï\81Ï\89ν Î¸Î± Î²Ï\81είÏ\84ε Ï\83Ï\84οÏ\85Ï\82 Ï\83Ï\85νδέÏ\83μοÏ\85Ï\82: [//meta.wikimedia.org/wiki/MediaWiki_localisation Î\9fδηγίεÏ\82 Î³Î¹Î± Ï\84Ï\81οÏ\80οÏ\80οίηÏ\83η Ï\84οÏ\85 Ï\80εÏ\81ιβάλλονÏ\84οÏ\82 ÎµÏ\81γαÏ\83ίαÏ\82] ÎºÎ±Î¹ [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Î\95γÏ\87ειÏ\81ίδιο Ï\87Ï\81ήÏ\83Ï\84η]."
+       "mainpagedocfooter": "ΣÏ\85μβοÏ\85λεÏ\85Ï\84είÏ\84ε Ï\84ο [//meta.wikimedia.org/wiki/Help:Contents Î\95γÏ\87ειÏ\81ίδιο Ï\87Ï\81ήÏ\83Ï\84η] Î³Î¹Î± Ï\80ληÏ\81οÏ\86οÏ\81ίεÏ\82 Ï\83Ï\87εÏ\84ικά Î¼Îµ Ï\84η Ï\87Ï\81ήÏ\83η Ï\84οÏ\85 Î»Î¿Î³Î¹Ï\83μικοÏ\8d wiki.\n\n== Î\9eεκινÏ\8eνÏ\84αÏ\82 ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Î\9aαÏ\84άλογοÏ\82 Ï\81Ï\85θμίÏ\83εÏ\89ν]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Î\9bίÏ\83Ï\84α Ï\84αÏ\87Ï\85δÏ\81ομείοÏ\85 ÎµÎºÎ´Ï\8cÏ\83εÏ\89ν MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Î¤Î¿Ï\80ικοÏ\80οιήÏ\83Ï\84ε Ï\84ο MediaWiki Î³Î¹Î± Ï\84η Î³Î»Ï\8eÏ\83Ï\83α Ï\83αÏ\82]"
 }
index 12d1e82..bf68fdd 100644 (file)
@@ -117,7 +117,7 @@ class ResourceLoaderImage {
         * @param ResourceLoaderContext $context Any context
         * @return string
         */
-       protected function getPath( ResourceLoaderContext $context ) {
+       public function getPath( ResourceLoaderContext $context ) {
                $desc = $this->descriptor;
                if ( is_string( $desc ) ) {
                        return $this->basePath . '/' . $desc;
index bf6a7dd..7efdb26 100644 (file)
@@ -303,6 +303,49 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                return false;
        }
 
+       /**
+        * Get the definition summary for this module.
+        *
+        * @param ResourceLoaderContext $context
+        * @return array
+        */
+       public function getDefinitionSummary( ResourceLoaderContext $context ) {
+               $summary = parent::getDefinitionSummary( $context );
+               foreach ( array(
+                       'localBasePath',
+                       'images',
+                       'variants',
+                       'prefix',
+                       'selectorWithoutVariant',
+                       'selectorWithVariant',
+               ) as $member ) {
+                       $summary[$member] = $this->{$member};
+               };
+               return $summary;
+       }
+
+       /**
+        * Get the last modified timestamp of this module.
+        *
+        * @param ResourceLoaderContext $context Context in which to calculate
+        *     the modified time
+        * @return int UNIX timestamp
+        */
+       public function getModifiedTime( ResourceLoaderContext $context ) {
+               $files = array();
+               foreach ( $this->getImages() as $name => $image ) {
+                       $files[] = $image->getPath( $context );
+               }
+
+               $files = array_values( array_unique( $files ) );
+               $filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
+
+               return max(
+                       $filesMtime,
+                       $this->getDefinitionMtime( $context )
+               );
+       }
+
        /**
         * Extract a local base path from module definition information.
         *
index 4b1fcae..14850ff 100644 (file)
@@ -24,7 +24,7 @@
  * A lot of this is copied out of SpecialRevisiondelete.
  *
  * @ingroup SpecialPage
- * @since 1.26
+ * @since 1.25
  */
 class SpecialEditTags extends UnlistedSpecialPage {
        /** @var bool Was the DB modified in this request */
index cdf26ee..ccd793c 100644 (file)
        "missingcommentheader": "'''یادا سالما:''' سیز یورومونوز اوچون بیر قونو/باشلیق یازمامیسینیز.\n«{{int:savearticle}}»-ی تیک‌لاسازسا، دَییشیکلیگینیز، قونو/باشلیق-ی اولمایاراق قئید اولوناجاق‌دیر.",
        "summary-preview": "قیسا اؤن‌گؤستریش:",
        "subject-preview": "قونو/باشلیق اؤن‌گؤستریشی:",
+       "previewerrortext": "سیزین دَییشدیرمه‌لرینیزین اؤن‌گؤسترمه‌سینده بیر خطا قاباغا گلدی.",
        "blockedtitle": "ایستیفاده‌چی باغلانیب",
        "blockedtext": "' 'ایستیفاده‌چی آدی و یا آی پی عنوانینیز قاباغی باغلانیب دیر.'\n\nسیزی باغلایان$ 1. الیله اولوب دیر \nباغلاماق سببی:' $ 2.\n\n* باغلانمانین باشلانان زامانی: $ 8\n* باغلانمانین قورتولان زامانی: $ 6\n* باغلانما مدتی: $ 7\n\nگؤستریلن سببه گؤره ائنگئللئنمئنیزین اویغون اولمادیغینی دوشونورسونوزسه، $ 1 یا دا باشقا بیر [[{{MediaWiki:Grouppage-sysop}}|مدیر]]  ایله بو وضعیتی گؤروشه بیلرسینیز. [[Special:Preferences|ترجیح لرینیز]] قیسمینده اعتبارلی بیر ائ-پوچت اونوانی گیرمئدیسئنیز \"ایستیفاده‌چییه ائ-پوچت گؤندر\" خصوصیتینی ایستیفاده ائده، ترجیهلرینیز ایمیل عنوانینیزی علاوه ایمیل گؤندرمک حقوقونا صاحب اولاجاقسینیز.\nبو آنکی باغلانما عنوانینیز $ 3، ائنگئللئنمئ نؤمره‌نیز # $ 5.\nبیر ایداره‌چی‌لر وضعیتینیز حاقیندا معلومات آلماق ایستدیگینیزده و یا هر هانسی بیر سورگودا بو معلومات‌لار لازیم اولا‌جاق، خاهیش ائدیریک نوت ائدین.",
        "autoblockedtext": "\n' 'ایستیفاده‌چی آدی و یا آی پی عنوانینیز قاباغی باغلانیب دیر.'\n\nسیزی باغلایان$ 1. الیله اولوب دیر \nباغلاماق سببی:' $ 2.\n\n* باغلانمانین باشلانان زامانی: $ 8\n* باغلانمانین قورتولان زامانی: $ 6\n* باغلانما مدتی: $ 7\n\nگؤستریلن سببه گؤره ائنگئللئنمئنیزین اویغون اولمادیغینی دوشونورسونوزسه، $ 1 یا دا باشقا بیر [[{{MediaWiki:Grouppage-sysop}}|مدیر]]  ایله بو وضعیتی گؤروشه بیلرسینیز. [[Special:Preferences|ترجیح لرینیز]] قیسمینده اعتبارلی بیر ائ-پوچت اونوانی گیرمئدیسئنیز \"ایستیفاده‌چییه ائ-پوچت گؤندر\" خصوصیتینی ایستیفاده ائده، ترجیهلرینیز ایمیل عنوانینیزی علاوه ایمیل گؤندرمک حقوقونا صاحب اولاجاقسینیز.\nبو آنکی باغلانما عنوانینیز $ 3، ائنگئللئنمئ نؤمره‌نیز # $ 5.\nبیر ایداره‌چی‌لر وضعیتینیز حاقیندا معلومات آلماق ایستدیگینیزده و یا هر هانسی بیر سورگودا بو معلومات‌لار لازیم اولا‌جاق، خاهیش ائدیریک نوت ائدین.",
        "undo-summary-username-hidden": "گیزلی ایستیفاده‌چی ایله ائدیلمیش $1 نوسخه‌سینی قایتارماق",
        "cantcreateaccounttitle": "حساب یارادماق اولمور",
        "cantcreateaccount-text": "بو ای پی عنوانین‌دان ('$1) ایستیفاده‌چی حسابی یارادیلماسی [[User:$3|$3]] طرفین‌دن انگللنمیش‌دیر.\n\n$3 طرفین‌دن وئریلن سبب '$2",
+       "cantcreateaccount-range-text": "'''$1''' آی‌پی آدرس آرالیغیندان حساب یارانماق، [[User:$3|$3]] ایشلدنی طرفیندن یاساقلانیب‌دیر. سیزین‌ده آی‌پی آدرسیز ('''$4''') بو آرادادیر.\n\n$3 طرفین‌دن وئریلن سبب بودور: «$2»",
        "viewpagelogs": "بو صحیفه‌نین قئیدلرینه باخ",
        "nohistory": "بو صحیفه اوچون دَییشدیرمه گئچمیشی یوخدور.",
        "currentrev": "سون نوسخه",
        "rev-deleted-event": "(قئيد سیلیندی)",
        "rev-deleted-user-contribs": "[ایستیفاده‌چی آدی و يا ای-پی اونوانی سیلیندی - ديَیشیکلیک چالیشمالاردان چیخاریلدی]",
        "rev-deleted-text-permission": "بو ديَیشیکلیک بو صحیفه‌دن '''سیلینیب دیر.'''",
+       "rev-suppressed-text-permission": "بو صفحه نوسخه‌سی <strong>باسدیریلیب‌دیر</strong>.\nآرتیق بیلگیلری [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} باسدیری قئیدین‌دن] تاپا بیلرسیز.",
        "rev-deleted-text-unhide": "بو صحیفه رئویزیونو 'سیلینمیش.\n[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیاهه سیلینمش] دئتال‌لاری بیلر.\nبیر خیدمتله اولا‌راق اگر داوام ائتسه‌نیز [$1 بو رئویزیونو هله گؤره بیلرسینیز].",
        "rev-suppressed-text-unhide": "صحیفه‌نین بو نوسخه سی سیلینیب.\nمومکون‌دور کی، بونون سببی [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیلمه قئیدلرینده]گؤستریلمیش‌دیر.\nسیز ایداره‌چی اولدوغونوزا گؤره سیلینن [$1 بو وئرسیانی] نظردن کئچیره بیلرسینیز.",
        "rev-deleted-text-view": "صحیفه‌نین بو نوسخه سی سیلینیب.\nسیز ایداره‌چی اولدوغونوزا گؤره سیلینن بو وئرسیانی نظردن کئچیره بیلرسینیز. مومکون‌دور کی، سیلینمه‌نین سببی [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیلمه قئیدلرینده] گؤستریلمیش‌دیر.",
        "gender-unknown": "ترجیح وئریرم بیلیندیرمییم",
        "gender-male": "کیشی",
        "gender-female": "قادین",
-       "prefs-help-gender": "اÛ\8cستگÙ\87 Û\8cاغÙ\84Û\8c: Û\8cازÛ\8cÙ\84Û\8cÙ\85â\80\8cÙ\84اØ\8c Ø¯Ù\88زگÙ\88Ù\86 Ø¬Ù\86سÛ\8cتÙ\87 Ø§Û\8cÙ\84Ú¯Û\8câ\80\8cÙ\84Û\8c Ø¢Ø¯Ø±Ø³ Ù\88ئرÙ\85Ú© Ø§Ù\88Ú\86Ù\88Ù\86 Ø§Û\8cØ´Ù\86یر.\nبو بیلگی، عمومی اولاجاق‌دیر.",
+       "prefs-help-gender": "بÙ\88 ØªØ±Ø¬Û\8cØ­ Ø§Û\8cستگÙ\87 Ø¨Ø§ØºÙ\84Û\8câ\80\8cدÛ\8cر.\nبÙ\88Ù\86Ù\88Ù\86 Ù\85Û\8cÙ\82دارÛ\8cØ\8c Ø³Û\8cزÛ\8c Ø¨Ø§Ø´Ù\82اÙ\84ارا Ø¯Ù\88زگÙ\88Ù\86 Ø¢Ø¯Ø±Ø³ Ù\88ئرÙ\85Ú© Ø§Ù\88Ú\86Ù\88Ù\86 Ø§Û\8cØ´Ù\84Ù\86Ù\87â\80\8cجکدیر.\nبو بیلگی، عمومی اولاجاق‌دیر.",
        "email": "ایمیل",
        "prefs-help-realname": "اصلی آد ایختیاری دیر.\nاگر اونو وئرماغی سئچسز، سیزین ایشلرینیزی سیزه مونتسب ائدن‌ده، بو اصلی آد ایشلنه‌جک‌دیر.",
        "prefs-help-email": "ایمیل آدرسی ایستگه باغلی‌دیر، آنجاق رمزینیزی اونوتدوغونوز واخت، سیزه يئنی رمز گؤندرمگه گرکلی‌دیر.",
        "trackingcategories-msg": "ایزله‌مک بؤلومو",
        "trackingcategories-name": "مئساژ آدی",
        "trackingcategories-desc": "بؤلمه ایچری آلماق معیارلاری",
+       "noindex-category-desc": "بو صفحه، ایچینده <code><nowiki>__NOINDEX__</nowiki></code> سؤزجوک اولدوغونا گؤره و بونو ایجازه وئرن آدفضاسیندا اولدوغونا گؤره، بوتلارلا ایندِکس اولونماییب‌دیر.",
+       "index-category-desc": "بو صفحه، ایچینده <code><nowiki>__INDEX__</nowiki></code> سؤزجوک اولدوغونا گؤره و بونو ایجازه وئرن آدفضاسیندا اولدوغونا گؤره، بوتلارلا ایندِکس اولونوب‌دور، اوحال‌داکی عموماً اولونمازدیر.",
+       "post-expand-template-inclusion-category-desc": "شابلونلاری گئنیشلندیرن‌دن سونرا، صفحه‌نین اؤلچو <code>$wgMaxArticleSize</code>-دن چوخ اولور، بونا گؤره شابلونلار گئنیشلندیریلمه‌ییبلر.",
+       "post-expand-template-argument-category-desc": "بیر شابلون آرگومانینی (اوچ آکولاد آرالی بیر شِی، میثال <code>{{{Foo}}}</code>) گئنیشلندیرن‌دن سونرا، صفحه‌نین اؤلچو <code>$wgMaxArticleSize</code>-دن چوخ اولور.",
+       "trackingcategories-nodesc": "آچیقلاما یوخدور.",
        "trackingcategories-disabled": "بؤلمه باغلانیب‌دیر.",
        "mailnologin": "گؤندرمه آدرسی یوخدور",
        "mailnologintext": "باشقا ایستیفاده‌چیلره ایمیل گؤندرک اوچون، [[Special:UserLogin|گیریش]] ائدیب و [[Special:Preferences|ترجیحلر]]ینیزده گئچرلی ایمیل آدرسی وئرمه‌لیسینیز.",
        "emailccsubject": "سیزین $1-ه مئساژینیزین کوپی‌سی: $2",
        "emailsent": "ایمیل گؤنده‌ریلدی",
        "emailsenttext": "ایمیل مئساژینیز گئنده‌ریلدی.",
-       "emailuserfooter": "بو ایمیل، {{SITENAME}}-ده «ایستیفاده‌چی‌یه ایمیل گؤندر» ایمکانی ایله، $1-دن $2-ه گؤنده‌ریلیب‌دیر.",
+       "emailuserfooter": "بو ایمیل، {{SITENAME}}-ده «{{int:emailpage}}» ایمکانی ایله، $1-دن $2-ه گؤندریلیب‌دیر.",
        "usermessage-summary": "مئساژ گئنده‌ریلدی.",
        "usermessage-editor": "سیستِم مئساژ گؤندَرَنی",
        "watchlist": "ایزله‌دیکلر",
        "mywatchlist": "ایزله‌دیکلر",
        "watchlistfor2": "$1 اوچون $2",
        "nowatchlist": "ایزلمه سیاهینیز بؤش‌دور.",
-       "watchlistanontext": "Ù\84Ø·Ù\81اÙ\8bØ\8c Ø§Û\8cزÙ\84دÛ\8cÚ¯Û\8cÙ\86Û\8cز ØµØ­Û\8cÙ\81Ù\87â\80\8cÙ\84رÛ\8c Ú¯Ø¤Ø±Ù\85Ú© Ù\88 Û\8cا Ø±Ø¦Ø¯Ø§Ú©ØªÙ\87 Ø§Ø¦ØªÙ\85Ú© Ø§Ù\88Ú\86Ù\88Ù\86 $1.",
+       "watchlistanontext": "Ù\84Ø·Ù\81اÙ\8bØ\8c Ø§Û\8cزÙ\84دÛ\8cÚ¯Û\8cÙ\86Û\8cز ØµØ­Û\8cÙ\81Ù\87â\80\8cÙ\84رÛ\8c Ú¯Ø¤Ø±Ù\85Ú© Ù\88 Û\8cا Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\85Ú© Ø§Ù\88Ú\86Ù\88Ù\86 Ú¯Û\8cرÛ\8cØ´ Ø§Ø¦Ø¯Û\8cÙ\86.",
        "watchnologin": "داخیل اولمامیسینیز",
        "addwatch": "ایزلمه سیاهی‌سینا علاوه ائت",
        "addedwatchtext": "\"[[:$1]]\" صحیفه‌سی [[Special:Watchlist|ایزله‌دیکلرینیزه]] آرتیریلدی. بو صحیفه‌ده و ایلگیلی دانیشیق صحیفه‌سین‌ده‌کی بوتون دییشیکلیکلر اوردا گؤستریله‌جکلر.",
        "addedwatchtext-short": "«$1» صفحه‌سی سیزین ایزله‌دیلرینیزه آرتیریلدی.",
        "removewatch": "بو صحیفنی ایزلدیگیم صحیفه‌لر سیاهی‌سین‌دان چیخار",
        "removedwatchtext": "\"[[:$1]]\" صحیفه‌سی [[Special:Watchlist|ایزلمه سیاهینیزدان]] چیخاریلدی.",
+       "removedwatchtext-short": "«$1» صفحه‌سی سیزین ایزلدیگینیز صفحه‌لریندن سیلیندی.",
        "watch": "ایزله",
        "watchthispage": "بو صفحه‌نی ایزله",
        "unwatch": "ایزله‌مه",
        "unwatchthispage": "صحیفه ایزلمیی دایان‌دیر",
        "notanarticle": "مضمون صحیفه‌سی دئییل",
        "notvisiblerev": "باشقا ایستیفادی‌چی‌نین سون دییشیک‌لیگی سیلینیب",
-       "watchlist-details": "دانیشیق صفحه‌لرینی سایمایاراق، {{PLURAL:$1|$1 صفحه‌نی}} ایزله‌ییرسینیز.",
+       "watchlist-details": "داÙ\86Û\8cØ´Û\8cÙ\82 ØµÙ\81Ø­Ù\87â\80\8cÙ\84رÛ\8cÙ\86Û\8c Ø¢Û\8cرÛ\8c Ø³Ø§Û\8cÙ\85اÛ\8cاراÙ\82Ø\8c {{PLURAL:$1|$1 ØµÙ\81Ø­Ù\87â\80\8cÙ\86Û\8c}} Ø§Û\8cزÙ\84Ù\87â\80\8cÛ\8cÛ\8cرسÛ\8cÙ\86Û\8cز.",
        "wlheader-enotif": "ایمیل ایله بیلدیریش آچیلیب‌دیر.",
        "wlheader-showupdated": "سون گؤروشونوزدن سونرا ائدیلن دییشیکلیکلر '''قالین''' گؤستریلیبدیلر.",
-       "wlnote": "آشاغیداکی {{PLURAL:$1|'''$1''' ديَیشیک‌لیک|'''$1'''ديَیشیک‌لیک}}  سون {{PLURAL:$2|ساعتدا|'''$2''' ساعتدا}} ائدیلمیشدیر.$3، $4",
+       "wlnote": "آشاغیداکی {{PLURAL:$1|بیر ديَیشیک‌لیک|<strong>$1</strong> ديَیشیک‌لیک}}  سون {{PLURAL:$2|ساعتدا|<strong>$2</strong> ساعتدا}} ائدیلمیشدیر. $3، $4",
        "wlshowlast": "سون $1 ساعات $2 گون گؤستر",
        "watchlist-options": "ایزله‌دیکلر سئچمه‌لری",
        "watching": "ایزله‌نیلیر...",
        "enotif_lastvisited": "سونونجو زیارتینیزدن ایندیدک اولان بوتون دییشیک‌لیک‌لری گؤرمک اوچون باخین: $1.",
        "enotif_lastdiff": "بو دییشیک‌لیگی گؤرمک اوچون $1 صحیفه‌سینه باخین.",
        "enotif_anon_editor": "قئیدیات‌سیز ایستیفاده‌چی $1",
-       "enotif_body": "حؤرمتلی $WATCHINGUSERNAME,\n\n{{SITENAME}} وئب-سايتینداکی $PAGETITLE آدلی صحیفه‌‌ $PAGETITLE تاریخینده $PAGEEDITOR طرفیندن $CHANGEDORCREATED. صحیفه‌‌نین سونونجو وئرسیياسینا باخماق اوچون $PAGETITLE_URL کئچیدیندن ایستیفاده ائدین. \n\n$نئwپاگئ \n\nديَیشیکلیگی ائدن ایستیفاده‌چی‌نین ایضاهی: $PAGESUMMARY $PAGEMINOREDIT\n\nصحیفه‌‌نی ديَیشدیرن ایستیفاده‌چی‌نین علاقه‌‌ معلوماتلاری: \nائ-پوچت:$PAGEEDITOR_EMAIL\nویکی:$PAGEEDITOR_WIKI\n\nسیز حاقیندا صؤحبت گئدن صحیفه‌‌يه باخانادک صحیفه‌‌دکی دیگر ديَیشیکلیکلرله باغلی باشقا بیلدیریش مکتوبو آلماياجاقسینیز. سیز همچی‌نین، ایزله‌مه سیياهینیزداکی بوتون صحیفه‌‌لرله باغلی بیلدیریش معلوماتلارینی سیله بیلرسینیز. \n\n               {{SITENAME}} سايتی‌نین خبردارلیق سیستمی. \n\n-- \nایزله‌مه سیياهیسی‌نین تنزیمله‌مه‌لرینی ديَیشمک اوچون: \n{{canonicalurl:Special:Watchlist/edit}}\n\nياردیم و تکلیفلرینیز اوچون: \n$HELPPAGE",
+       "enotif_body": "حؤرمتلی $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nدَییشدیرنین قیساسی: $PAGESUMMARY $PAGEMINOREDIT\n\nدَییشدیرن‌له تماس تاپماق:\nایمیل: $PAGEEDITOR_EMAIL\nویکی: $PAGEEDITOR_WIKI\n\nگلن دفعه سایتا گیریش ائدیب و بو صفحه‌یه باخمایانا کیمی، داها آیری خبر سیزه گؤندریلمه‌یه‌جک. سیز هم‌ده ایزلدیکلرینیزده، بوتون صفحه‌لرین خبر یوللاماق ترجیحینی دَییشدیره بیلرسیز.\n\nسیزین یولداش {{SITENAME}} خبر سیستِمی\n\n--\nایمیل خبرلندیرمه ترجیحلرینی دَییشدیرمک اوچون بورا باخین:\n{{canonicalurl:{{#special:Preferences}}}}\n\nایزله‌دیکلر لیستینین ترجیحلرینی دَییشدیرمک اوچون بورا باخین:\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nبو صفحه‌نی ایزله‌دیکلر لیستین‌دن سیلمک اوچون بورا باخین:\n$UNWATCHURL\n\nنظر وئرماق و آرتیق یاردیم:\n$HELPPAGE",
        "created": "یارادیلیب",
        "changed": "ديَیشدی",
        "deletepage": "صحیفه‌‌نی سیل",
        "exbeforeblank": "سیلینمه‌دن اوولکی مزمون: '$1",
        "delete-confirm": "سیل $1",
        "delete-legend": "سیل",
-       "historywarning": "'خبردارلیق:' سیلینه‌جک صحیفه‌نین تاریخچه‌سینده قئید اولونموش $1 {{PLURAL::$1|دییشدیر|دییشدیرمه}} وار:",
+       "historywarning": "<strong>خبردارلیق:</strong> سیلینه‌جک صحیفه‌نین تاریخچه‌سینده قئید اولونموش $1 {{PLURAL:$1|دییشدیرمه}} وار:",
        "confirmdeletetext": "بو صحیفه و یا فایل بوتون تاریخچه‌سی ایله بیرلیکده بیردفه‌لیک سیلینه‌جک. بونو [[{{MediaWiki:Policy-url}}|قایدا‌لارا]] اویغون ائتدیگینیزی و عملیاتین نتیجه‌لرینی باشا دوشدوگونوزو تسدیق ائدین.",
        "actioncomplete": "چالیشما سوناچاتدی",
        "actionfailed": "چالیشما اوغورسوز اولدو",
        "deletecomment": "ندن:",
        "deleteotherreason": "باشقا/آرتیق دلیل:",
        "deletereasonotherlist": "باشقا سبب",
-       "deletereason-dropdown": "*ساس سیلمه سببی\n** یازان ایستیی\n** یازانلار حقوق پوزونتوسو\n** واندالیزم",
+       "deletereason-dropdown": "*معمول سیلمک سببلری\n** اِسپم\n** خرابکارلیق\n** یازانلار حقوق پوزونتوسو\n** یازان ایستگی\n** خراب یول‌لاندیرما",
        "delete-edit-reasonlist": "سیلمک دلیل‌لرینی دَییشدیر",
        "delete-toobig": "بو صحیفه، $1 {{PLURAL:$1 | دنه دییشیک‌لیک | دنه دییشیک‌لیک}} ایله چوخ اوزون بیر کئچمیشه مالیک‌دیر.\nبئله صحیفه‌لرین سیلینمه‌سی، {{SITENAME}} سایتینی پوزماماق اوچون مهدودلاشدیریلماقدا‌دیر.",
        "delete-warning-toobig": "بو صحیفه‌‌نین بؤيوک بیر ديَیشیکلیک کئچمیشی وار، $1 {{PLURAL:$1|نسخه| نسخه}} اوزرینده. \nبونو سیلمک {{SITENAME}} عملیاتلارینی مخدل‌ائده‌بیلیر؛ \nدیقتله داوام ائدین.",
        "deleteprotected": "سیز بو صفحه‌نی، قورونماغینا گؤره، سیله بیلنمزسیز.",
+       "deleting-backlinks-warning": "'''اخطار:''' بو سیلمگه قصدینیز اولان صفحه‌یه، [[Special:WhatLinksHere/{{FULLPAGENAME}}|باشقا صفحه‌لر]] باغلانتی وئریب یا اونو اؤزلرین‌ده ایشلدیب‌لر.",
        "rollback": "اوولکی نوسخه لر",
        "rollbacklink": "قایتار",
        "rollbacklinkcount": "گیتیرلمه $1  {{PLURAL:$1|دییشمک |دییشمک}} دییشدیرمه",
        "alreadyrolled": "[[User:$2|$2]] ([[User talk:$2|موزاکیره]] {{int:pipe-separator}} [[Special:Contributions/$2| {{int:contribslink}}]]) طرفین‌دن [[:$1]] صحیفه‌سینده ائدیلمیش سون دییشیک‌لیک گئرییه آلینا بیلمیر؛\nباشقا بیری صحیفه‌ده دییشیک‌لیک ائتدی یا دا صحیفنی گئرییه آلدی.\n\nسون دییشیک‌لیگی ائدن: [[User:$3|$3]] ([[User talk:$3|تالک]] {{int:pipe-separator}} [[Special:Contributions/$3| {{int:contribslink}}]] ).",
        "editcomment": "دییشیک‌لیک خلاصه‌سی: ''\" $1''\" ایدی.",
        "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|دانیشیق]]) طرفین‌دن ائدیلمیش دییشیک‌لیک‌لر [[User:$1|$1]] طرفین‌دن ائدیلمیش دییشیک‌لیک‌لره قایتاریلدی.",
-       "revertpage-nouser": "[[User:$1|$1]] ایله ائدیلمیش سون نوسخه‌یه، بیر گیزلی ایستیفاده‌چی طرفین‌دن قایتاریلان دییشیکلیک‌لر",
+       "revertpage-nouser": "{{GENDER:$1|[[User:$1|$1]]}} ایله ائدیلمیش سون نوسخه‌یه، بیر گیزلی ایشلدن طرفین‌دن قایتاریلان دییشیکلیک‌لر",
        "rollback-success": "$1 طرفین‌دن ائدیلمیش دییشدیر‌لر گئری قایتاریلدی؛ $2 طرفین‌دن یارادیلمیش سون وئرسیا برپا اولوندو.",
        "sessionfailure-title": "گیریش خطاسی",
        "sessionfailure": "گیریش اوتورومونوزلا ایلگی‌لی بیر سورون وار گیبی گؤرونویور؛\nبو ائیلئم، اوتوروم گاسپینا کارشی اؤنلئم اولاراک ایپتال ائدیلدی.\nلوتفن \"گئری\" گیدین و گئلدیغینیز سایفایی یئنی‌دئن یوکلئیین، سونرا تئکرار دئنئیین.",
        "protect-locked-blocked": "صحیفه‌نین بلوک‌لو اولدوغو مدتده سیز محافظه سویه‌سینی دییشه بیلمزسینیز.\n'$1 صحیفه‌سینده حال-حاضردا ائده بیلجیینیز عملیات‌لار بون‌لاردیر:",
        "protect-locked-dblock": "وئریلن‌لر بازاسی کیلیدلی اولدوغو اوچون محافظه سویه‌سی دییشیله بیلمز.\n'$1 صحیفه‌سینده حال-حاضردا ائده بیلجیینیز عملیات‌لار بون‌لاردیر:",
        "protect-locked-access": "سیزین حسابینیزین محافظه سویه‌سینی دییشمه‌یه ایختیاری یوخ‌دور.\n'$1 صحیفه‌سینده حال-حاضردا ائده بیلجیینیز عملیات‌لار بون‌لاردیر:",
-       "protect-cascadeon": "بو صحیفه محافظه‌لی‌دیر، چونکی بو صحیفه {{PLURAL:$1|باشقا بیر}} صحیفه‌دن کاسکاد محافظه ائدیلمیش‌دیر. سیز بو صحیفه‌نین محافظه سویه‌سینی دییش‌دیره بیلرسینیز، بو کاسکاد محافظه‌یه تأثیر ائتمه‌یه‌جک.",
+       "protect-cascadeon": "بو صحیفه محافظه‌لی‌دیر، چونکی بو صفحه {{PLURAL:$1|باشقا بیر}} صفحه‌دن کاسکاد محافظه ائدیلمیش‌دیر. سیز بو صفحه‌نین محافظه سویه‌سینی دییشدیره بیلرسینیز، بو کاسکاد محافظه‌یه تأثیر ائتمه‌یه‌جک.",
        "protect-default": "بوتون ایستیفاده‌چی‌لره ایجازه وئر",
        "protect-fallback": "یالنیز «$1» ایجازه‌سی اولان ایستیفاده‌چیلره ایجازه وئر",
        "protect-level-autoconfirmed": "یالنیز اوتوماتیک دوغرولانان ایستیفاده‌چیلره ایجازه وئر",
        "namespace": "آد فضاسی:",
        "invert": "سئچیلنی دؤندر",
        "tooltip-invert": "بو قوتونی علامتله یین تا انتخاب اولان آد فضا سینین ایچری صحیفه لری دییشیک لیک لری(و اوبیری علامتلنمیش فضالار) گیزله نه آدی",
+       "tooltip-whatlinkshere-invert": "سئچیلمیش آدفضاسیندان اولان باغلانتیلاری گیزلتمک اوچون بو قوتویا نیشان قویون.",
        "namespace_association": "علاقه‌لی آد ساحه‌سی",
        "tooltip-namespace_association": "بو قوتونو علامت له ین یالنیز آد بحث فضاسی یا مرتبط اولان آد فضاسی ایله انتخاب اولا",
        "blanknamespace": "(آنا)",
        "contributions-title": "$1 ایستیفاده‌چی چالیشمالاری",
        "mycontris": "چالیشمالار",
        "contribsub2": "{{GENDER:$3|$1}} اوچون ($2)",
+       "contributions-userdoesnotexist": "«$1» ایشلدن حسابی ثبت اولونماییب‌دیر.",
        "nocontribs": "بو موشخصاتا اویغون دییشدیر تاپیلمادی",
        "uctop": "(ایندیکی)",
        "month": "بو آی‌دان (و اؤنجه‌سی):",
        "sp-contributions-newbies-sub": "یئنی ایستیفاده‌چی‌لر اوچون",
        "sp-contributions-newbies-title": "یئنی حساب‌لار اوچون ایستیفاده‌چی فالیت‌لری",
        "sp-contributions-blocklog": "باغلاما قئیدلری",
+       "sp-contributions-suppresslog": "باسدیریلمیش ایشلدن فعالیت‌لری",
        "sp-contributions-deleted": "سیلینمیش ایستیفاده‌چی چالیشمالاری",
        "sp-contributions-uploads": "یوکله‌نَنلر",
        "sp-contributions-logs": "قئیدلر",
        "sp-contributions-search": "چالیشمالاری آختار",
        "sp-contributions-username": "آی‌پی آدرسی ویا ایستیفاده‌چی آدی:",
        "sp-contributions-toponly": "تکجه سون نوسخه اولان دییشیکلری گؤستر",
+       "sp-contributions-newonly": "یالنیز صفحه یاراتماق دَییشیکلیکلرینی گؤستر",
        "sp-contributions-submit": "آختار",
        "whatlinkshere": "بو صفحه‌یه باغلانتی‌لار",
        "whatlinkshere-title": "«$1»-ه باغلانان صحیفه‌لر",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] باغلاندی.<br />\nباخ [[Special:BlockList|آی پی باغلانماق سیاهی‌سی]] باغلانمیش آی پ-لر.",
        "ipb-blockingself": "اؤزونوزو باغلاماق حالیندا سیز.! بونو ائتمک ایستدیگینیزدن اطمینانییز وار می؟",
        "ipb-confirmhideuser": "ایستیفادچینی باغلاماق و دییشدیر سیاهی‌سین‌دان اونون آدینی سیلمک اوزرسینیز. بونو ائتمک ایستدیگینیزدن اطمینانیز وار می؟",
+       "ipb-confirmaction": "بونو ائتمک‌دن آرخایین اولساز، دیب‌ده‌کی «{{int:ipb-confirm}}» قوتوسونا نیشان قویون.",
        "ipb-edit-dropdown": "باغلاما سبب‌لرینی دییشدیر",
        "ipb-unblock-addr": "$1 آچیلدی",
        "ipb-unblock": "ایستیفاده چی نین یا دا آی پی نین آچیلماسی",
        "unblocked": "[[User:$1|$1]]  - نین بلوکو گؤتورولدو",
        "unblocked-range": "$1-نین بلوکو گؤتورولدو",
        "unblocked-id": "$1-نین بلوکو گؤتورولدو",
+       "unblocked-ip": "[[Special:Contributions/$1|$1]] قاداغاسی قالدیریلدی.",
        "blocklist": "بلوکلانمیش ایستیفاده‌چیلر",
        "ipblocklist": "باغلانمیش ایستیفاده‌چیلر",
        "ipblocklist-legend": "بلوکلانمیش ایستیفاده‌چینی آختار",
        "change-blocklink": "بلوکلامانی ديَیشدیر",
        "contribslink": "چالیشمالار",
        "emaillink": "ایمیل گؤندر",
-       "autoblocker": "آوتوماتیک اولا‌راق باغلانمیسینیز. چونکی، قیسا مدت اول سیزین ای پی-عنوانینیز \"آوتوماتیک اولا‌راق بلوکلانمیسینیز. چونکی، قیسا مدت اول سیزین ایپ-اونوانینیز \"[[User:$1|$1]]\" طرفین‌دن ایستیفاده ائدیلمیش‌دیر.\n$1 آدلی ایستیفاده‌چی‌نین باغلانما سببی: \"$2\"",
+       "autoblocker": "یاخینلیقدا سیزین آی‌پی آدرسیز «[[User:$1|$1]]» ایله ایشلدیلمک اوچون، اوتوماتیک اولاراق باغلانیلمیسیز.\n$1-ین وئریلمیش باغلانماق سببی: «$2»",
        "blocklogpage": "باغلاما قئیدلری",
        "blocklog-showlog": "بو ایستیفاده‌چی داها اول بلوکلانمیش‌دیر. بلوکلاما گونده‌لیگی رئفئرانس اوچون آشاغیدا گؤستریلیب:",
        "blocklog-showsuppresslog": "بو ایستیفاده‌چی داها اول باغلانمیش‌دیر. باغلانما گونده‌لیگی رئفئرانس اوچون آشاغیدا گؤستریلیب:",
        "range_block_disabled": "ایداره‌چیلره دیاپازونو بلوکلاماق قاداغاندیر.",
        "ipb_expiry_invalid": "بیتمه واختی سهودیر",
        "ipb_expiry_temp": "گیزلی ایستیفاده‌چی آدی بلوکلاما‌لاری مدت‌سیز اولما‌لی‌دیر.",
-       "ipb_hide_invalid": "اÛ\8cستÛ\8cÙ\81ادÙ\87â\80\8cÚ\86Û\8c Ù\87ئسابÛ\8cÙ\86Ù\84Ù\86 Ú¯Û\8cزÙ\84دÛ\8cÙ\84Ù\85Ù\87â\80\8cسÛ\8c Ù\82ئÛ\8cرÛ\8c\85Ù\88Ù\85Ú©Ù\88Ù\86â\80\8cدÙ\88رØ\9b Ø­Ø¯Ø¯Ù\86 Ú\86Ù\88Ø® Ø±Ø¦Ø¯Ø§Ú©Øªه‌سی وار.",
+       "ipb_hide_invalid": "بÙ\88 Ø­Ø³Ø§Ø¨ Ø¨Ø§Ø³Ø¯Û\8cرÛ\8cÙ\84اÙ\86Ù\85ادÛ\8cØ\9b Ø§Ù\88Ù\86Ù\88 {{PLURAL:$1|بÛ\8cردÙ\86|$1-دÙ\86}} Ú\86Ù\88Ø® Ø¯Ù\8eÛ\8cÛ\8cشدÛ\8cرÙ\85ه‌سی وار.",
        "ipb_already_blocked": "\"$1\" آرتیق بلوکلانیب",
        "ipb-needreblock": "$1 آرتیق باغلانیب.\nباغلاما شرط‌لرینی دییشمک ایستییرسینیز؟",
        "ipb-otherblocks-header": "آیری {{PLURAL:$1|باغلانماا|باغلاما‌لار}}",
        "thumbnail_image-missing": "بئله گؤرونور کی، $1 فایلی یوخ‌دور",
        "import": "صحیفه‌لری ایدخال ائت",
        "importinterwiki": "آیری ویکی‌دن ایچری گتیرمک",
-       "import-interwiki-text": "ایچه کؤچورمک اوچون بیر wiki و صحیفه باش‌لیغی سئچین.\nرئویزیون تاریخ‌لری و یازارلارین آدلاری قورونا‌جاق.\nبوتون ویکیلئراراسی ایچه کؤچورمه حرکت‌لری [[Special:Log/import|ایچه کؤچورمه گوندلیگینده]] یازیلماقدا‌دیر.",
+       "import-interwiki-text": "ایچری گتیرمک اوچون بیر ویکی و صفحه باشلیغی سئچین.\nنوسخه تاریخلری و یازارلارین آدلاری قورونا‌جاق.\nآیری ویکیلردن بوتون ایچری گتیرمه‌لر، [[Special:Log/import|ایچری گتیرمک ثبت‌لرین‌ده]] یازیلیر.\nبوتون ویکیلئراراسی ایچه کؤچورمه حرکت‌لری [[Special:Log/import|ایچه کؤچورمه گوندلیگینده]] یازیلماقدا‌دیر.",
        "import-interwiki-sourcewiki": "قایناق ویکی:",
        "import-interwiki-sourcepage": "قایناق صفحه:",
        "import-interwiki-history": "صحیفه‌نین دییشمه تاریخچه‌لری‌نین هامی‌سینی کؤچور",
        "importuploaderrortemp": "ایچه کؤچورولن فایلین یوکلنمه‌سی اوغورسوز اولدو.\nمووققتی فایل ایتکین.",
        "import-parse-failure": "اکس ام ال ایچری کؤچورمه ییغماسی موفقیت‌سیز",
        "import-noarticle": "یوکلمگه صحیفه یوخدور!",
-       "import-nonewrevisions": "بوتون نوسخه لر اول‌دن ایچه کؤچورولموش.",
+       "import-nonewrevisions": "هئچ نوسخه ایچری گتیریلمه‌دی (هامیسی اؤنجه‌دن واریدیلار، یوخسا خطا قاباغا گلماغا گؤره آتلانیلدیلار).",
        "xml-error-string": "$1 $2 سترینده، $3 سوتونوندا ($4 بایت): $5",
        "import-upload": "XML-وئریلنی یوکله",
        "import-token-mismatch": "سئانس معلومات‌لارینین ایتیریلمه‌سی. لطفاً، یئنی‌دن جهد ائدین.",
        "import-invalid-interwiki": "گؤستریلن ویکی‌دن کؤچورمک مومکون دئییل",
-       "import-error-edit": "\"$1\" صحیفه‌سی ایدخال ائدیله بیلینمیر، چونکی اونو دییشمک سلاهیتینیز یوخ‌دور.",
-       "import-error-create": "\"$1\" صحیفه‌سی آچیلمیر، چونکی اونو یاراتماق سلاهیتینیز یوخ‌دور.",
-       "import-error-interwiki": "صحیفه \"$1\" داخیل ائدیلممیش‌دیر. چونکی اونون خاریجی باغلانتی سی (interwiki) یئری توتولوب و رئزرو اولوب.",
-       "import-error-special": "صحیفه «$1» ایچری توکمه اولنمادی، نیه کی بیر اجازه سیز آد ین فضاسینا تعلوقو وار.",
-       "import-error-invalid": "صحÛ\8cÙ\81Ù\87 \"$1\" Ø§Ø¹ØªØ¨Ø§Ø± Ø³Û\8cز Ø§Ù\88Ù\84Ù\85اÙ\82 Ø¯Ù\84Û\8cÙ\84 Ø§Ù\88Ú\86Ù\88Ù\86 Ø¢Ø¯Û\8c Ø¯Ø§Ø®Û\8cÙ\84 Ø§Ù\88Ù\84Ù\86Ù\85Ù\88ر.",
+       "import-error-edit": "سیزین «$1» صفحه‌سینی دَییشدیرمک ایجازه‌نیز اولمادیغینا گؤره، بو صفحه ایچری گتیریلمه‌دی.",
+       "import-error-create": "سیزین «$1» صفحه‌سینی یاراتماق ایجازه‌نیز اولمادیغینا گؤره، بو صفحه ایچری گتیریلمه‌دی.",
+       "import-error-interwiki": "«$1» صفحه‌نین آدی، خاریجی باغلانتی (interwiki) قورماغا رِزِرو اولدوغو اوچون، ایچری گتیریلمه‌دی.",
+       "import-error-special": "«$1» صفحه، صفحه‌لره ایجازه وئرمه‌ین اؤزل بیر آدفضاسیندا یئر آلدیغینا گؤره، ایچری گتیریلمه‌دی.",
+       "import-error-invalid": "بÙ\88 Ù\88Û\8cÚ©Û\8câ\80\8cدÙ\87 Ø¢Ø¯Û\8c Ú¯Ø¦Ú\86رسÛ\8cز Ø§Ù\88Ù\84دÙ\88غÙ\88Ù\86ا Ú¯Ø¤Ø±Ù\87Ø\8c Â«$1» ØµÙ\81Ø­Ù\87â\80\8cسÛ\8c Ø§Û\8cÚ\86رÛ\8c Ú¯ØªÛ\8cرÛ\8cÙ\84Ù\85Ù\87â\80\8cدÛ\8c.",
        "import-error-unserialize": "«$1» صحیفه‌سینین $2 نوسخه‌سی سِریالیزه‌لیقدان چیخاردیلانمادی. بو نوسخه، $4 کیمی سِریالیزه اولان $3 مودِلینی ایشلدمگه بیلدیریلدی.",
        "import-options-wrong": "{{PLURAL:$2|جزئیات| جزئیات}} یانلیش: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "وئریلن کؤک صحیفه‌‌سی اعتبارسیز آددیر.",
        "import-rootpage-nosubpage": "آد فضا سی  \"$1\" آنا باسئ ٔآلت صحیفه اوچون اجازه وئرمیر.",
        "importlogpage": "چیخاریلما گونده‌لیگی",
        "importlogpagetext": "باشقا ویکیلردن، دَییشیکلیک گئچمیشلریله بیرلیک‌ده گتیریلمیش صحیفه‌لر.",
-       "import-logentry-upload-detail": "{{PLURAL:$1|بیر|$1}} نوسخه",
-       "import-logentry-interwiki-detail": "$2-دن {{PLURAL:$1|بیر|$1}} نوسخه",
+       "import-logentry-upload-detail": "{{PLURAL:$1|بیر|$1}} نوسخه ایچری گتیریلدی",
+       "import-logentry-interwiki-detail": "$2-دن {{PLURAL:$1|بیر|$1}} نوسخه ایچری گتیریلدی",
        "javascripttest": "جاوااسکریپت تِستی",
        "javascripttest-pagetext-noframework": "بو صحیفه، جاوااسکریپت تِستلرینی ایشلدمگه ساخلانیلیب‌دیر.",
        "javascripttest-pagetext-unknownframework": "تانینمامیش تِست ائتمه سیستِمی «$1».",
        "pageinfo-length": "صحیفه‌‌ اوزونلوغو (بايت)",
        "pageinfo-article-id": "صحیفه آی‌دی-سی",
        "pageinfo-language": "صحیفه مضمونونون دیلی",
-       "pageinfo-robot-policy": "آختارÛ\8cØ´ Ø³Û\8cستÙ\85Û\8cÙ\86 Ø¯Ù\88رÙ\88Ù\85Ù\88",
+       "pageinfo-robot-policy": "بÙ\88تÙ\84ارÙ\84ا Ø§Û\8cÙ\86دÙ\90کسÙ\84Ù\86Û\8cر",
        "pageinfo-robot-index": "ایجازه‌لی",
        "pageinfo-robot-noindex": "ایجازه‌سیز",
        "pageinfo-watchers": "صحیفه‌نین تاماشا‌چی سایی",
        "newimages-summary": "بو خصوصی صحیفه، ان سون یوک‌لنن فایل‌لاری گؤستریر.",
        "newimages-legend": "سۆزگَج",
        "newimages-label": "فایلین (و یا اونون بیر حیسه‌سی‌نین) آدی:",
+       "newimages-showbots": "بوت یوکله‌مه‌لرینی گؤستر",
        "noimages": "هئچ نیی گؤرممک.",
        "ilsubmit": "آختار",
        "bydate": "تاریخین اوستوندن",
        "version-libraries": "نصب اولونموش کیتابخانا",
        "version-libraries-library": "کیتاب‌ائوی",
        "version-libraries-version": "نوسخه‌",
-       "redirect": "فایل، ایستیفاده‌چی یا نوسخه ID-سی ایله یول‌لاندیرما",
+       "redirect": "فایل، ایستیفاده‌چی، صفحه یا نوسخه آی‌دی-سی ایله یول‌لاندیرما",
        "redirect-legend": "بیر فایل یا صحیفه‌یه یول‌لاندیرما",
-       "redirect-summary": "بو اؤزل صحیفه، بیر فایلا (فایل آدی ایله)، صحیفه‌یه (نوسخه ID-سی ایله) یا ایستیفاده‌چی صحیفه‌سینه (ایستیفاده‌چی نومره ID-سی ایله) یول‌لاندیریر.",
+       "redirect-summary": "بو اؤزل صحیفه، بیر فایلا (فایل آدی ایله)، صفحه‌یه (نوسخه یا صفحه آی‌دی-سی ایله) یا ایستیفاده‌چی صفحه‌سینه (ایستیفاده‌چی نومره آی‌دی-سی ایله) یول‌لاندیریر. ایشلتمک: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]]، [[{{#Special:Redirect}}/revision/328429]]، یا [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "گئت",
        "redirect-lookup": "آختار:",
        "redirect-value": "دَگَر:",
        "fileduplicatesearch-result-n": "«$1» فایلینین، {{PLURAL:$2|بیر|$2}} عینی کوپیسی واردیر.",
        "fileduplicatesearch-noresults": "\"$1\" آدیندا فایل تاپیلمادی.",
        "specialpages": "اؤزل صفحه‌لر",
-       "specialpages-note": "* نورمال اؤزل صحیفه‌لر.\n* <span class=\"mw-specialpagerestricted\">محدودلاشدیریلمیش اؤزل صحیفه‌لر.</span>",
+       "specialpages-note": "* نورمال اؤزل صفحه‌لر.\n* <span class=\"mw-specialpagerestricted\">محدودلاشدیریلمیش اؤزل صفحه‌لر.</span>",
        "specialpages-group-maintenance": "جاری مروزه‌لر",
        "specialpages-group-other": "دیگر خصوصی صحیفه‌لر",
        "specialpages-group-login": "گیریش / حساب یاراد",
        "compare-revision-not-exists": "بَلیرتدیگینیز نوسخه یوخدور.",
        "dberr-problems": "عوذر ایسته‌ییریک! بو سایت‌دا تِکنیکی ایشکال‌لار واردیر.",
        "dberr-again": "بیر نئچه دقیقه دؤزوب سونرا یئنی‌دن یوکله‌یین.",
-       "dberr-info": "(دیتابیس خیدمت‌چیسی‌یه باغلانماق اولونمادی: $1)",
+       "dberr-info": "(دیتابیسه باغلانماق اولونمادی: $1)",
        "dberr-usegoogle": "بو آرادا، گوگل‌ده آختارابیلرسینیز.",
        "dberr-outofdate": "دیقت ائدین کی اوردا بیزیم سایتیمیزین ایندِکسی کؤهنه اولا بیلر.",
        "dberr-cachederror": "بو ایسته‌نیلن صحیفه‌نین بیر کَش اولونموش کوپی‌سیدیر و کؤهنه اولا بیلر.",
index b2a7002..1676227 100644 (file)
        "exif-whitepoint": "Къайн тӀадаман бос",
        "exif-primarychromaticities": "Коьрта беснийн бос",
        "exif-referenceblackwhite": "Ӏаьржа а къай а тӀадамийн меттиг",
-       "exif-datetime": "Файл хийцина терахь а хан",
+       "exif-datetime": "Файлан хийцам бина терахь а, хан а",
        "exif-imagedescription": "Суьртан цӏе",
        "exif-make": "Камера арахоьцург",
        "exif-model": "Камеран модель",
        "exif-exposuretime-format": "$1 ($2) чура",
        "exif-fnumber": "Диафрагмин дукхалла",
        "exif-exposureprogram": "Экспозицин программа",
+       "exif-isospeedratings": "ISO серло хааялар",
        "exif-shutterspeedvalue": "APEX чура дешнаш",
        "exif-aperturevalue": "APEX чура оьз",
        "exif-exposurebiasvalue": "Сурт доккхуш яла оьшу серло меттаяло",
index cb9e4cc..5f1e17c 100644 (file)
        "page-rss-feed": "«$1» - RSS хăю",
        "page-atom-feed": "«$1» - Atom хăю",
        "red-link-title": "$1 (хальлĕхе çырман)",
-       "nstab-main": "Страницă",
+       "nstab-main": "Страница",
        "nstab-user": "Хутшăнакан страници",
        "nstab-media": "Мультимеди",
        "nstab-special": "Ятарлă страницă",
        "unusedtemplates": "Усă курман шаблонсем",
        "unusedtemplatestext": "Ку страница çинче страницăсенче усă курман «Шаблон» ятлă ятсен уçлăхне шутне кĕрекен страницăсене куратăр.",
        "unusedtemplateswlh": "ытти каçăсем",
-       "randompage": "Ä\82нÑ\81Ä\83Ñ\80Ñ\82Ñ\80ан Ð¸Ð»Ð½Ä\9b Ñ\81Ñ\82Ñ\80аниÑ\86Ä\83",
+       "randompage": "Ä\82нÑ\81Ä\83Ñ\80Ñ\82Ñ\80ан Ð»ÐµÐºÐ½Ä\95 Ñ\81Ñ\82Ñ\80аниÑ\86а",
        "randomincategory-category": "Категори:",
        "randomredirect": "Ăнсăртран илнĕ куçару",
        "statistics": "Статистика",
index 78c64a3..62ca344 100644 (file)
        "disclaimers": "Αποποίηση ευθυνών",
        "disclaimerpage": "Project:Γενική αποποίηση",
        "edithelp": "Βοήθεια σχετικά με την επεξεργασία",
+       "helppage-top-gethelp": "Βοήθεια",
        "mainpage": "Αρχική σελίδα",
        "mainpage-description": "Αρχική σελίδα",
        "policy-url": "Project:Πολιτική",
        "portal": "Πύλη κοινότητας",
        "portal-url": "Project:Πύλη κοινότητας",
-       "privacy": "Πολιτική ιδιωτικού απορρήτου",
-       "privacypage": "Project:Πολιτική ιδιωτικού απορρήτου",
+       "privacy": "Πολιτική ιδιωτικότητας",
+       "privacypage": "Project:Πολιτική ιδιωτικότητας",
        "badaccess": "Σφάλμα άδειας",
        "badaccess-group0": "Δεν επιτρέπεται να εκτελέσετε την ενέργεια που ζητήσατε.",
        "badaccess-groups": "Η ενέργεια που ζητήσατε είναι περιορισμένη σε χρήστες που ανήκουν {{PLURAL:$2|στην ομάδα|σε μία από τις ομάδες}}: $1.",
        "wrongpassword": "Ο κωδικός που πληκτρολογήσατε είναι λανθασμένος. Παρακαλούμε προσπαθήστε ξανά.",
        "wrongpasswordempty": "Ο κωδικός πρόσβασης που εισάχθηκε ήταν κενός. Παρακαλούμε προσπαθήστε ξανά.",
        "passwordtooshort": "Οι κωδικοί πρέπει να περιέχουν τουλάχιστον {{PLURAL:$1|1 χαρακτήρα|$1 χαρακτήρες}}.",
+       "passwordtoolong": "Οι κωδικοί πρόσβασης δεν μπορούν να υπερβαίνουν {{PLURAL:$1|τον 1 χαρακτήρα|τους $1 χαρακτήρες}}.",
        "password-name-match": "Ο κωδικός σου θα πρέπει να είναι διαφορετικός από το όνομα χρήστη σου.",
        "password-login-forbidden": "Η χρήση αυτού του ονόματος χρήστη και συνθηματικού έχουν  απαγορευτεί.",
        "mailmypassword": "Επαναφορά κωδικού",
        "missingcommentheader": "'''Υπενθύμιση:''' Δεν έχετε γράψει ένα θέμα/επικεφαλίδα για αυτό το σχόλιο.\nΑν κάνετε πάλι κλικ στο κουμπί \"{{int:savearticle}}\", η επεξεργασία σας θα αποθηκευτεί χωρίς θέμα ή επικεφαλίδα.",
        "summary-preview": "Προεπισκόπηση σύνοψης:",
        "subject-preview": "Προεπισκόπηση θέματος/επικεφαλίδας:",
+       "previewerrortext": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια για να κάνετε προεπισκόπηση των αλλαγών σας.",
        "blockedtitle": "Ο χρήστης έχει υποστεί φραγή.",
        "blockedtext": "'''Το όνομα χρήστη σας ή η διεύθυνση IP σας έχει υποστεί φραγή.'''\n\nΗ φραγή έγινε από τον/την $1.\nΗ αιτιολογία που δόθηκε είναι: ''$2''.\n\n* Έναρξη φραγής: $8\n* Λήξη φραγής: $6\n* Η φραγή προορίζεται για το χρήστη: $7\n\nΜπορείτε να απευθυνθείτε στον/στην $1 ή σε κάποιον άλλον [[{{MediaWiki:Grouppage-sysop}}|διαχειριστή]] για να συζητήσετε τη φραγή.\nΔεν μπορείτε να χρησιμοποιήσετε την δυνατότητα «αποστολή e-mail σε αυτό το χρήστη» εκτός αν μια έγκυρη ηλεκτρονική διεύθυνση έχει οριστεί στις [[Special:Preferences|προτιμήσεις χρήστη]] σας.\nΗ τρέχουσα διεύθυνση IP σας είναι $3, και ο αριθμός αναγνώρισης της φραγής είναι #$5.\nΠαρακαλούμε περιλαμβάνετε οποιοδήποτε ή και τα δύο από αυτά σε οποιαδήποτε ερωτήματα σας.",
        "autoblockedtext": "Η διεύθυνση IP σας έχει υποστεί φραγή αυτόματα επειδή χρησιμοποιήθηκε από έναν άλλο χρήστη, ο οποίος και αποκλείστηκε από τον/την $1.\nΗ αιτία που δόθηκε είναι ο εξής:\n\n:''$2''\n\n* Έναρξη φραγής: $8\n* Λήξη φραγής: $6\n* Επιδιωκόμενος αποκλεισμένος: $7\n\nΜπορείτε να επικοινωνήσετε με τον/την $1 ή με έναν από τους άλλους [[{{MediaWiki:Grouppage-sysop}}|διαχειριστές]] για να συζητήσετε τη φραγή.\n\nΣημειώστε ότι δεν μπορείτε να χρησιμοποιήσετε το χαρακτηριστικό \"στείλτε e-mail σε αυτό τον χρήστη\" εκτός αν έχετε μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου καταχωρημένη στις [[Special:Preferences|προτιμήσεις χρήστη]] σας.\n\nΗ τρέχουσα διεύθυνση IP σας είναι $3, και ο αριθμός αναγνώρισης της φραγής σας είναι #$5. Παρακαλώ συμπεριλάβετε τις παραπάνω λεπτομέρειες σε όποια ερωτήματα κάνετε.",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
        "content-json-empty-object": "Κενό αντικείμενο",
+       "content-json-empty-array": "Κενός πίνακα",
        "duplicate-args-category": "Σελίδες που χρησιμοποιούν διπλές παραμέτρους σε κλήσεις προτύπων",
        "duplicate-args-category-desc": "Η σελίδα περιέχει κλήσεις πρότυπων που χρησιμοποιούν διπλές παραμέτρους, όπως <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> or <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "expensive-parserfunction-warning": "Προειδοποίηση: Αυτή η σελίδα περιέχει πάρα πολύ ακριβό αναλυτή λειτουργικών κλήσεων.\n\nΠρέπει να περιέχει λιγότερες από $2 {{PLURAL:$2|κλήση|κλήσεις}}, τώρα {{PLURAL:$1|υπάρχει $1 κλήση|υπάρχουν $1 κλήσεις}}.",
        "revdelete-selected-text": "{{PLURAL:$1|Επιλεγμένη έκδοση|Επιλεγμένες εκδόσεις}} της [[:$2]]:",
        "revdelete-selected-file": "{{PLURAL:$1|Επιλεγμένη έκδοση αρχείου|Επιλεγμένες εκδόσεις αρχείου}} του [[:$2]]:",
        "logdelete-selected": "{{PLURAL:$1|Επιλεγμένο γεγονός αρχείου καταγραφής|Επιλεγμένα γεγονότα αρχείου καταγραφής}}:",
+       "revdelete-text-text": "Οι διαγραμμένες αναθεωρήσεις θα εξακολουθούν να εμφανίζονται στο ιστορικό της σελίδας, αλλά τα μέρη του περιεχομένου τους θα είναι απροσπέλαστα για το κοινό.",
+       "logdelete-text": "Οι διαγραμμένες καταγραφές ενεργειών θα εξακολουθούν να εμφανίζονται στις σελίδες καταγραφών, αλλά μέρη του περιεχομένου τους, θα είναι απροσπέλαστα για το κοινό.",
+       "revdelete-text-others": "Άλλοι διαχειριστές θα εξακολουθεί να είναι σε θέση να αποκτήσουν πρόσβαση στο κρυφό περιεχόμενο και για να αναιρέσουν τη διαγραφή, εκτός αν τίθενται πρόσθετοι περιορισμοί.",
        "revdelete-confirm": "Παρακαλούμε επιβεβαιώστε ότι σκοπεύετε να το κάνετε αυτό, ότι αντιλαμβάνεσθε τις συνέπειες, και ότι το κάνετε σύμφωνα με την [[{{MediaWiki:Policy-url}}|πολιτική]].",
        "revdelete-suppress-text": "Η καταστολή μπορεί να χρησιμοποιηθεί <strong> μόνο </strong> για τις ακόλουθες περιπτώσεις:\n* Ενδεχόμενη συκοφαντική δυσφήμιση\n* Ακατάλληλες προσωπικές πληροφορίες\n*: <em>διευθύνσεις κατοικίας και αριθμοί τηλεφώνου, αριθμοί ταυτότητας, κλπ. </em>",
        "revdelete-legend": "Θέστε περιορισμούς ορατότητας",
index 567e6c2..af51a24 100644 (file)
        "listfiles-delete": "kustuta",
        "listfiles-summary": "Sellel erileheküljel näidatakse kõiki üles laaditud faile.",
        "listfiles_search_for": "Nimeotsing:",
+       "listfiles-userdoesnotexist": "Kasutajakonto \"$1\" pole registreeritud.",
        "imgfile": "fail",
        "listfiles": "Piltide loend",
        "listfiles_thumb": "Pisipilt",
index 84e393c..0734b74 100644 (file)
@@ -28,7 +28,8 @@
                        "תומר ט",
                        "Matanya",
                        "GilCahana",
-                       "Ldorfman"
+                       "Ldorfman",
+                       "LaG roiL"
                ]
        },
        "tog-underline": "סימון קישורים בקו תחתי:",
        "go": "הצגה",
        "searcharticle": "לדף",
        "history": "היסטוריית הדף",
-       "history_short": "×\94×\99ס×\98×\95ר×\99×\99ת ×\94×\93×£",
+       "history_short": "×\94×\99ס×\98×\95ר×\99×\94",
        "updatedmarker": "עודכן מאז ביקורך האחרון",
        "printableversion": "גרסת הדפסה",
        "permalink": "קישור קבוע",
        "history-feed-description": "היסטוריית הגרסאות של הדף הזה בוויקי",
        "history-feed-item-nocomment": "$1 ב־$2",
        "history-feed-empty": "הדף המבוקש לא נמצא.\nייתכן שהוא נמחק, או ששמו שונה.\nבאפשרותך לנסות [[Special:Search|לחפש]] דפים רלוונטיים חדשים.",
+       "history-edit-tags": "עריכת תגים של גרסאות נבחרות",
        "rev-deleted-comment": "(תקציר העריכה הוסר)",
        "rev-deleted-user": "(שם המשתמש הוסר)",
        "rev-deleted-event": "(פרטים מהיומן הוסרו)",
        "right-sendemail": "שליחת דואר אלקטרוני למשתמשים אחרים",
        "right-passwordreset": "צפייה בדואר אלקטרוני של איפוס סיסמה",
        "right-managechangetags": "יצירת ומחיקת [[Special:Tags|תגיות]] מבסיס הנתונים",
+       "right-applychangetags": "החלת [[Special:Tags|תגים]] יחד עם שינויים",
+       "right-changetags": "הוספת והסרה של [[Special:Tags|תגים]] בגרסאות מסוימות וברשומות יומן",
        "newuserlogpage": "יומן רישום משתמשים",
        "newuserlogpagetext": "זהו יומן המכיל הרשמות של משתמשים.",
        "rightslog": "יומן תפקידים",
        "action-editmyprivateinfo": "לערוך את המידע הפרטי שלך",
        "action-editcontentmodel": "לערוך את מודל התוכן של דף",
        "action-managechangetags": "ליצור ולמחוק תגיות מבסיס הנתונים",
+       "action-applychangetags": "להחיל תגים לשינויים שלכם",
+       "action-changetags": "להוסיף ולהסיר תגים שרירותיים בגרסאות מסוימות וברשומות יומן",
        "nchanges": "{{PLURAL:$1|שינוי אחד|$1 שינויים}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|מאז ביקורך האחרון}}",
        "enhancedrc-history": "היסטוריה",
        "logempty": "אין פריטים תואמים ביומן.",
        "log-title-wildcard": "חיפוש כותרות המתחילות באותיות אלה",
        "showhideselectedlogentries": "הצגת/הסתרת פעולות היומן שנבחרו",
+       "log-edit-tags": "עריכת תגים או רשומות יומן נבחרות",
        "allpages": "כל הדפים",
        "nextpage": "הדף הבא ($1)",
        "prevpage": "הדף הקודם ($1)",
        "tooltip-n-portal": "אודות המיזם, כיצד תוכלו לעזור, היכן למצוא דברים",
        "tooltip-n-currentevents": "מציאת מידע רקע על האירועים האחרונים",
        "tooltip-n-recentchanges": "רשימת השינויים האחרונים באתר",
-       "tooltip-n-randompage": "צפ×\99×\99×\94 ×\91×\93×£ ×ª×\95×\9b×\9f אקראי",
+       "tooltip-n-randompage": "×\98×¢×\99נת ×\93×£ אקראי",
        "tooltip-n-help": "עזרה בשימוש באתר",
        "tooltip-t-whatlinkshere": "רשימת כל הדפים המקושרים לכאן",
        "tooltip-t-recentchangeslinked": "השינויים האחרונים שבוצעו בדפים המקושרים מדף זה",
        "patrol-log-page": "יומן שינויים בדוקים",
        "patrol-log-header": "יומן זה מציג גרסאות שנבדקו.",
        "log-show-hide-patrol": "$1 יומן שינויים בדוקים",
+       "log-show-hide-tag": "$1 יומן התגים",
        "deletedrevision": "מחק גרסה ישנה $1",
        "filedeleteerror-short": "שגיאה במחיקת הקובץ: $1",
        "filedeleteerror-long": "שגיאות שאירעו בעת מחיקת הקובץ:\n\n$1",
        "tags-deactivate-reason": "הסבר:",
        "tags-deactivate-not-allowed": "לא ניתן לבטל את הפעלת התגית \"$1\".",
        "tags-deactivate-submit": "ביטול הפעלה",
+       "tags-apply-no-permission": "אין לך הרשאה להחיל תגי שינוי עם השינויים שלך.",
+       "tags-apply-not-allowed-one": "לא ניתן להחיל את התג \"$1\" ידנית.",
+       "tags-apply-not-allowed-multi": "אי־אפשר להחיל את {{PLURAL:$2|התג הבא|התגים הבאים}} ידנית: $1",
+       "tags-update-no-permission": "אין לך הרשאה להוסיף תגי שינוי לגרסאות מסוימות א רשומות יומן או להסיר אותם.",
+       "tags-update-add-not-allowed-one": "אי־אפשר להוסיף את התג \"$1\" ידנית.",
+       "tags-update-add-not-allowed-multi": "אי־אפשר להוסיף את {{PLURAL:$2|התג הבא|התגים הבאים}}: $1",
+       "tags-update-remove-not-allowed-one": "לא ניתן להסיר את התג \"$1\".",
+       "tags-update-remove-not-allowed-multi": "אי־אפשר להסיר את {{PLURAL:$2|התג הבא|התגים הבאים}} ידנית: $1",
+       "tags-edit-title": "עריכת תגים",
        "comparepages": "השוואת דפים",
        "compare-page1": "דף 1",
        "compare-page2": "דף 2",
index b0ed3d2..3b11ca5 100644 (file)
@@ -60,7 +60,8 @@
                        "Phoenix303",
                        "Steinsplitter",
                        "Macofe",
-                       "Ankita-ks"
+                       "Ankita-ks",
+                       "Sahilrathod"
                ]
        },
        "tog-underline": "कड़ियाँ अधोरेखन:",
@@ -70,7 +71,7 @@
        "tog-extendwatchlist": "केवल हालिया ही नहीं, बल्कि सभी परिवर्तनों को दिखाने के लिए ध्यानसूची को विस्तारित करें",
        "tog-usenewrc": "हाल में हुए परिवर्तनों में और ध्यानसूची में परिवर्तनों को पृष्ठ अनुसार समूहों में बाँटें",
        "tog-numberheadings": "शीर्षक स्व-क्रमांकित करें",
-       "tog-showtoolbar": "समà¥\8dपादन à¤\94à¤\9c़ारपट्टी दिखाएँ",
+       "tog-showtoolbar": "समà¥\8dपादन à¤\89पà¤\95रण पट्टी दिखाएँ",
        "tog-editondblclick": "दुगुने क्लिक पर पृष्ठ संपादित करें",
        "tog-editsectiononrightclick": "अनुभाग शीर्षक पर दायाँ क्लिक करने पर अनुभाग सम्पादित करें",
        "tog-watchcreations": "मेरे द्वारा निर्मित पृष्ठों और मेरी अपलोड की फ़ाइलों को मेरी ध्यानसूची में जोड़ें",
index 5071eb7..80a17fd 100644 (file)
        "content-model-text": "luty tekst",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
+       "content-json-empty-object": "Pródzny objekt",
+       "content-json-empty-array": "Prózdna pólna wariabla",
        "expensive-parserfunction-warning": "Warnowanje: Tuta strona wobsahuje přewjele parserowych wołanjow.\n\nDyrbjała mjenje hač $2 {{PLURAL:$2|wołanje|wołanjej|wołanja|wołanjow}} měć, {{PLURAL:$1|je nětko $1 wołanje|stej nětko $1 wołanjej|su nětko $1 wołanja|je nětko $1 wołanjow}}.",
        "expensive-parserfunction-category": "Strony, kotrež tajke parserowe funkcije přehusto wołaja, kotrež serwer poćežuja.",
        "post-expand-template-inclusion-warning": "Warnowanje: Wulkosć zapřijatych předłohow je přewulka. Někotre předłohi so njezapřijmu.",
        "notextmatches": "Žane strony z wotpowědowacym tekstom",
        "prevn": "{{PLURAL:$1|předchadny $1|předchadnej $1|předchadne $1|předchadnych $1}}",
        "nextn": "{{PLURAL:$1|přichodny $1|přichodnej $1|přichodne $1|přichodnych $1}}",
+       "prev-page": "předchadna strona",
+       "next-page": "přichodna strona",
        "prevn-title": "{{PLURAL:$1|Předchadny wuslědk|Předchadnej $1 wuslědkaj|Předchadne $1 wuslědki|Předchadnych $1 wuslědkow}}",
        "nextn-title": "{{PLURAL:$1|Přichodny wuslědk|Přichodnej $1 wuslědkaj|Přichodne $1 wuslědki|Přichodnych $1 wuslědkow}}",
        "shown-title": "$1 {{PLURAL:$1|wuslědk|wuslědkaj|wuslědki|wuslědkow}} na stronu pokazać",
        "search-result-category-size": "{{PLURAL:$1|1 čłon|$1 čłonaj|$1 čłonojo|$1 čłonow}} ({{PLURAL:$2|1 podkategorija|$2 podkategoriji|$2 podkategorije|$2 podkategorijow}}, {{PLURAL:$3|1 dataja|$3 dataji|$3 dataje|$3 datajow}})",
        "search-redirect": "(Daleposrědkowanje $1)",
        "search-section": "(wotrězk $1)",
+       "search-category": "(kategorija $1)",
        "search-file-match": "(wotpowěduje datajowemu wobsahej)",
        "search-suggest": "Měnješe ty $1?",
        "search-interwiki-caption": "Sotrowske projekty",
        "prefs-personal": "Wužiwarski profil",
        "prefs-rc": "Aktualne změny",
        "prefs-watchlist": "Wobkedźbowanki",
+       "prefs-editwatchlist": "Wobkedźbowanki wobdźěłać",
+       "prefs-editwatchlist-label": "Zapiski we wobkedźbowankach wobdźěłać",
+       "prefs-editwatchlist-edit": "Titule w twojich wobkedźbowankach sej wobhladać a wotstronić",
+       "prefs-editwatchlist-raw": "Hrube wobkedźbowanki wobdźěłać",
+       "prefs-editwatchlist-clear": "Wobkedźbowanki wotstronić",
        "prefs-watchlist-days": "Ličba dnjow, kotrež maja so we wobkedźbowankach pokazać:",
        "prefs-watchlist-days-max": "Maksimalnje $1 {{PLURAL:$1|dźeń|dnjej|dny|dnjow}}",
        "prefs-watchlist-edits": "Ličba změnow, kotrež maja so we wobkedźbowankach pokazać:",
        "right-protect": "Škitowe schodźenki změnić a z kaskadami škitane strony wobdźěłać",
        "right-editprotected": "Strony wobdźěłać, kotrež su přez \"{{int:protect-level-sysop}}\" škitane",
        "right-editsemiprotected": "Strony wobdźěłać, kotrež su přez \"{{int:protect-level-autoconfirmed}}\" škitane",
+       "right-editcontentmodel": "Wobsahowy model strony wobdźěłać",
        "right-editinterface": "Wužiwarski powjerch wobdźěłać",
        "right-editusercssjs": "Dataje CSS a JS druhich wužiwarjow wobdźěłać",
        "right-editusercss": "Dataje CSS druhich wužiwarjow wobdźěłać",
        "right-override-export-depth": "Strony inkluziwnje wotkazanych stronow hač do hłubokosće 5 eksportować",
        "right-sendemail": "Druhim wužiwarjam e-mejl pósłać",
        "right-passwordreset": "E-mejlki za wróćostajenje hesłow sej wobhladać",
+       "right-managechangetags": "[[Special:Tags|markěrowanja]] wutworić a z datoweje banki zhašeć",
        "newuserlogpage": "Protokol nowych wužiwarjow",
        "newuserlogpagetext": "To je protokol wutworjenja nowych wužiwarskich kontow.",
        "rightslog": "Protokol zrjadowanja wužiwarskich prawow",
        "action-viewmywatchlist": "Sej swójske wobkedźbowanki wobhladać",
        "action-viewmyprivateinfo": "twoje priwatne informacije sej wobhladać",
        "action-editmyprivateinfo": "twoje priwatne informacije wobdźěłać",
+       "action-editcontentmodel": "wobsahowy model strony wobdźěłać",
+       "action-managechangetags": "markěrowanja wutworić a z datoweje banki zhašeć",
        "nchanges": "$1 {{PLURAL:$1|změna|změnje|změny|změnow}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|wot poslednjeho wopyta}}",
        "enhancedrc-history": "historija",
        "listfiles-delete": "zhašeć",
        "listfiles-summary": "Tuta specialna strona pokazuje wšě nahrate dataje.",
        "listfiles_search_for": "Za mjenom wobraza pytać:",
+       "listfiles-userdoesnotexist": "Wužiwarske konto \"$1\" zregistrowane njeje.",
        "imgfile": "dataja",
        "listfiles": "Lisćina datajow",
        "listfiles_thumb": "Wobrazk",
        "pager-older-n": "{{PLURAL:$1|starši 1|staršej $1|starše $1|staršich $1}}",
        "suppress": "Dohladowanje",
        "querypage-disabled": "Tuta specialna strona je z wukonowych přičinow znjemóžnjena.",
+       "apihelp": "API-pomoc",
+       "apihelp-no-such-module": "Modul \"$1\" njeje so namakał.",
        "booksources": "Pytanje po ISBN",
        "booksources-search-legend": "Žórła za knihi pytać",
        "booksources-search": "Pytać",
index 409267a..021bd91 100644 (file)
        "right-purge": "oldal gyorsítótárának ürítése megerősítés nélkül",
        "right-autoconfirmed": "Nem érinti az IP-alapú szerkesztéskorlátozás",
        "right-bot": "automatikus folyamatként való kezelés",
-       "right-nominornewtalk": "felhasználói lapok nem apró szerkesztésével megjelenik az új üzenet szöveg",
+       "right-nominornewtalk": "felhasználói lapok apró szerkesztésével nem jelenik meg az új üzenet értesítés",
        "right-apihighlimits": "nagyobb mennyiségű lekérdezés az API-n keresztül",
        "right-writeapi": "a szerkesztő-API használata",
        "right-delete": "lapok törlése",
index 919be17..f970484 100644 (file)
        "listfiles_size": "Mezinbûn",
        "listfiles_description": "Danasîn",
        "listfiles_count": "Guherto",
+       "listfiles-latestversion-yes": "Erê",
+       "listfiles-latestversion-no": "Na",
        "file-anchor-link": "Wêne",
        "filehist": "Dîroka daneyê",
        "filehist-help": "Ji bo dîtina guhertoya wê demê bişkoka dîrokê bitikîne.",
        "unusedtemplates": "Şablonên nayên bikaranîn",
        "unusedtemplateswlh": "lînkên din",
        "randompage": "Rûpeleke ketober",
+       "randomincategory-category": "Kategorî:",
        "randomredirect": "Beralîkirina ketober",
        "statistics": "Statîstîk",
        "statistics-header-pages": "Statîstîkên rûpelê",
        "statistics-files": "Wêneyên barkirî",
        "statistics-users": "[[Special:ListUsers|Bikarhênerên tomarkirî]]",
        "statistics-users-active": "Bikarhênerên çalak",
+       "pageswithprop-submit": "Biçe",
        "doubleredirects": "Beralîkirinên ducarî",
        "double-redirect-fixed-move": "Cihê [[$1]] hatiye guhertin, ew niha beralîkirina [[$2]] ye.",
        "brokenredirects": "Beralîkirinên xerabûyî",
        "htmlform-submit": "Tomar bike",
        "htmlform-reset": "Guherandinan vegerîne",
        "htmlform-selectorother-other": "Yên din",
+       "htmlform-no": "Na",
+       "htmlform-yes": "Erê",
        "logentry-delete-delete": "$1 rûpela $3 jê bir",
        "revdelete-content-hid": "naverok veşartî ye",
        "revdelete-uname-hid": "navê bikarhêneriyê yê veşartî",
        "logentry-newusers-create": "$1 hesabekî bikarhêneriyê çêkir",
        "rightsnone": "(tune)",
        "revdelete-summary": "kurteyê biguherîne",
+       "feedback-back": "Paşve",
        "feedback-cancel": "Betal bike",
        "feedback-close": "Çêbû",
+       "feedback-error-title": "Çewtî",
        "feedback-message": "Peyam:",
        "feedback-subject": "Mijar:",
        "feedback-thanks-title": "Spas!",
index 770b705..3746426 100644 (file)
        "filedelete-archive-read-only": "نشونگه مال دیارکردن ($1) د لا سرور قاول نیسنن نئ.",
        "previousdiff": "← ويرايشت كۈهنه تر",
        "nextdiff": "ويرايشت تازه تر",
+       "mediawarning": "'''هشدار''': شایت ای جانیا د خوش رازینه یا گن داشتوئه.\nشایت وا اجرا وه انجومیار شما آسیو دینه.",
        "imagemaxsize": "انازه عسگ:<br /><em>(سی شرح جانیا بلگه یا)</em>",
        "thumbsize": "انازه بن کلکی:",
        "widthheight": "$1 × $2",
        "file-info-png-looped": "حلقه دار",
        "file-info-png-repeat": "$1 بازی کرده{{جمی:$1|وخت|وختیا}}",
        "file-info-png-frames": "$1 {{PLURAL:$1|فریم|فریمیا}}",
+       "file-no-thumb-animation": "'''د ویر داشتوئیت: سی مشگلیا فنی پیش نمایشت جانیا وه حال و بار جمشت دار نشو دئه نبوئه.'''",
+       "file-no-thumb-animation-gif": "'''د ویر داشتوئیت: سی مشگلیا فنی پیش نمایشت جانیایا GIF چی یه وه حال و بار جمشت دار نشو دئه نبوئه.'''",
        "newimages": "عسگدونی جانیایا تازه",
        "imagelisttext": "د هار نومگه <strong>$1</strong> {{PLURAL:$1|جانیا|جانیایا}} اماییه جا بیه$2.",
        "newimages-summary": "ای بلگه یا ویجه همه جانیایا سوار بیه نه نشو می ئین.",
        "namespacesall": "همه شو",
        "monthsall": "همه",
        "confirmemail": "پشت راس کردن تیرنشون انجومانامه",
+       "confirmemail_noemail": "شما د بلگه [[Special:Preferences|ترجیحات کاریاری]] خوتو یه گل تیرنشون انجومانامه نامعتور نه دئیته.",
+       "confirmemail_text": "ای ویکی، شما نه مژبور می که وه پشت راسکاری تیرنشون انجومانامه خوتو، دما د یه که خدمات انجومانامه نه وه کار د ایچه وه کار بئیریت می که.دگمه هاری نه کنشتیار بکیت تا یه گل انجومانامه پشت راسکاری سی تیرنشون انجومانامه شما کل بوئه. ای انجومانامه د ور گرته یه گل رازینه ئه. هوم پیوند نه د دوارته نیئر خوتو واز بکیت تا تیرنشون انجومانامه تو پشت راسکاری با.",
+       "confirmemail_pending": "یه گل رازینه پشت راسکاری د دماتر وه شکل انجومانامه سی شما کلی بیه. ار د ای آخریا حساو خوتونه واز کردیته شایت بد نبا که دما یه که یه گل رازینه هنی بهایت چن دیقه آهره داری بکیت تا شایت انجومانامه دمایی وه تو برسه.",
        "confirmemail_send": "کل کردن رازینه پشت راس کاری",
        "confirmemail_sent": "انجومانامه پشت راس کردن کل بیه.",
+       "confirmemail_oncreate": "یه گل رازینه پشت راسکاری د دماتر وه شکل انجومانامه سی شما کلی بیه.\nسی اومائن وامین د سامونه نمیها ای رازینه وارد بکیت، ولی سی ره وندیاری امکانات وابسه د انجومانامه د ای ویکی ونه میهایت.",
+       "confirmemail_sendfailed": "کل کردن انجومانامه پشت راسکاری انجومگر نبی.\nتیرنشون انجومانامه نه د لحاظ بیین نیسسه یا گن وارسی بکیت.\n\nجواو سامونه کل کردن انجومانامه: $1",
+       "confirmemail_invalid": "رازینه پشت راسکاری نامعتوره. \nشایت وه تموم بیه با.",
        "confirmemail_needlogin": "لطف بکید $1 نه سی تیرنشون انجومانامه تو پشت راس بکید.",
        "confirmemail_success": "تیرنشون انجومانامه تو پشت راس بیه.\nشایت شما ایسه بهایت [[Special:چی یه گل کاریار|بیایت وامین]]و د ویکی لذت بوریت",
        "confirmemail_loggedin": "تیرنشون انجومانامه شما ایسه پشت راس بیه.",
        "confirmemail_subject": "{{SITENAME}} تیرنشون انجومانامه پشت راست کردن",
+       "confirmemail_body": "یه نفر، شایت خوتو، د تیرنشون آی‌ پی $1 حساو کاریاری وا نوم «$2» و ای تیرنشون انجومانامه نه {{SITENAME}} ره وندیاری کرده.\n\nسی پشت راسکاری یه که ای حساو د راستکی مال شمانه و هنی سی کنشتیاری انجومانامه {{SITENAME}} هوم پیوند هاری نه د دوارته نیئر خوتو واز بکیت:\n\n$3\n\nار شما ای حساو کاریاری نه ثوت *نکردیته*، لطف بکیت هوم پیوند هاری نه واز بکیت تا پشت راسکاری تیرنشون انجومانامه انجومشیو بوئه:\n\n$5\n\nای رازینه پشت راسکاری د ویرگار $4 تموم موئه.",
+       "confirmemail_body_changed": "یه نفر، شایت خوتو، د تیرنشون آی‌ پی $1 حساو کاریاری وا نوم «$2» و ای تیرنشون انجومانامه نه {{SITENAME}} ره وندیاری کرده.\n\nسی پشت راسکاری یه که ای حساو د راستکی مال شمانه و هنی سی کنشتیاری انجومانامه {{SITENAME}} هوم پیوند هاری نه د دوارته نیئر خوتو واز بکیت:\n\n$3\n\nار ای حساو کاریاری مال شما *نئ*، لطف بکیت هوم پیوند هاری نه واز بکیت تا پشت راسکاری تیرنشون انجومانامه انجومشیو بوئه:\n\n$5\n\nای رازینه پشت راسکاری د ویرگار $4 تموم موئه.",
+       "confirmemail_body_set": "\nیه نفر، شایت خوتو، د تیرنشون آی‌ پی $1 حساو کاریاری وا نوم «$2» و ای تیرنشون انجومانامه نه {{SITENAME}} ره وندیاری کرده.\n\nسی پشت راسکاری یه که ای حساو د راستکی مال شمانه و هنی سی کنشتیاری انجومانامه {{SITENAME}} هوم پیوند هاری نه د دوارته نیئر خوتو واز بکیت:\n\n$3\n\nار شما ای حساو کاریاری نه ثوت *نکردیته*، لطف بکیت هوم پیوند هاری نه واز بکیت تا پشت راسکاری تیرنشون انجومانامه انجومشیو بوئه:\n\n$5\n\nای رازینه پشت راسکاری د ویرگار $4 تموم موئه.",
        "confirmemail_invalidated": "پشت راس کنی انجومانامه انجوم شیو بیه",
        "invalidateemail": "انجومشیو کردن پشت راس کردن انجومانامه",
        "scarytranscludedisabled": "[پرگنجایشت کاری مینجا ویکی کنشتکار نئ]",
        "scarytranscludefailed-httpstatus": "[واحونی چوئه سی $1 انجومگر نبی: خطا اچ‌ تی‌ تی‌ پی $2]",
        "scarytranscludetoolong": "[یو آر ال فره گپه]",
        "deletedwhileediting": "<strong>زئنار:</strong>ای بلگه د او گاتی که شما شرو د ویرایشت کردیته پاکسا بیه!",
+       "confirmrecreate": "کاریار [[User:$1|$1]] ([[User talk:$1|چک چنه]]) ای گوتار نه نها یه که شما شرو د ویرایشتش کردیته سی دلیل هاری پاکسا کرده:\n: ''$2''\nلطف بکیت پشت راسکاری بکیت که میهایت د نو ای گوتار نه دروس بکیت.",
+       "confirmrecreate-noreason": "کاریار [[User:$1|$1]] ([[User talk:$1|چک چنه]]) ای گوتار نه نها یه که شما شرو د ویرایشتش کردیته.\nلطف بکیت پشت راسکاری بکیت که میهایت د نو ای گوتار نه دروس بکیت.",
        "recreate": "د نو راس کردن",
        "confirm_purge_button": "خوئه",
        "confirm-purge-top": "میهایت کش ای بلگه نا پاک بکیت؟",
+       "confirm-purge-bottom": "حالی کردن مینجاگر یه گل بلگه باعث موئه که آخری نسقه وه دیاری بکه.",
        "confirm-watch-button": "خوئه",
        "confirm-watch-top": "ای بلگه نه د سیل برگتو اضاف می کید؟",
        "confirm-unwatch-button": "خوئه",
        "autoredircomment": "بلگه واگردونی بیه سی[[$1]]",
        "autosumm-new": "راست کردن بلگه وه دس \"$1\"",
        "autosumm-newblank": "بلگه حالی دروس بیه",
+       "lag-warn-normal": "شایت آلشتیا تازه تر د $1 {{PLURAL:$1|ثانیه|ثانیه یا}} د ای نومگه دیاری نکن.",
+       "lag-warn-high": "شایت سی واپس رئتن فره رسینه جا، آلشتیا تازه تر د $1 {{PLURAL:$1|ثانیه|ثانیه یا}} د ای نومگه دیاری نکرده بان.",
        "watchlistedit-normal-title": "ویرایشت سیل برگ",
        "watchlistedit-normal-legend": "ؤرداشتن سرونیا د سیل برگ",
+       "watchlistedit-normal-explain": "داسونایی که هان د نومگه سیل برگه شما د هار اومائنه.\nسی پاکسا کردن داسون جعوه کناری وه نه نشودار بکیت و دگمه «{{int:Watchlistedit-normal-submit}}» نه بپورنیت.\nشما همچنی می تونیت [[Special:EditWatchlist/raw|نومگه خام نه ویرایشت بکیت]].",
        "watchlistedit-normal-submit": "ؤرداشتن سرونیا",
+       "watchlistedit-normal-done": "$1 داسون د نومگه سیل برگ شما پاکسا {{PLURAL:$1|بی|بیین}}:",
        "watchlistedit-raw-title": "سیل برگ نه ردیفی ویرایشت کو",
        "watchlistedit-raw-legend": "سیل برگ نه ردیفی ویرایشت کو",
+       "watchlistedit-raw-explain": "داسونایی که هان د نومگه سیل برگ شما هان د هار، و شما می تونیت چیایی نه پاکسا یا اضاف بکیت؛ هر چی واس د یه گل خط جگا بوئه.\nدس آخر،ری دگمه «{{int:Watchlistedit-raw-submit}}» کلیک بکیت.\nد ویر داشتوئیت که شما می تونیت د [[Special:EditWatchlist|ویرایشتگر استاندارد نومگه سیل برگ]] نم وه کار بئیریت.",
        "watchlistedit-raw-titles": "داسون:",
        "watchlistedit-raw-submit": "وه هنگوم سازی سیل برگ",
        "watchlistedit-raw-done": "سیل برگتون وه هنگوم سازی بیه.",
        "watchlistedit-raw-added": "$1 داسون وه دماگریا اضافه {{PLURAL:$1|بی|بیین}}:",
+       "watchlistedit-raw-removed": "$1 داسون پاکسا {{PLURAL:$1|بی|بیین}}:",
        "watchlistedit-clear-title": "سیل برگ دروس بیه",
        "watchlistedit-clear-legend": "پاک کردن سیل برگ",
+       "watchlistedit-clear-explain": "همه داسونا د نومگه سیل برگ شما پاکسا موئه.",
        "watchlistedit-clear-titles": "داسون:",
        "watchlistedit-clear-submit": "پاک کردن سیل برگ(وه سی همیشه هئ!)",
        "watchlistedit-clear-done": "سیل برگتون وه پاک بیه.",
+       "watchlistedit-clear-removed": "$1 داسون پاکسا {{PLURAL:$1|بی|بیین}}:",
        "watchlistedit-too-many": "ایچه بلگه یا فره ای سی نشو دئن هئ.",
        "watchlisttools-clear": "پاک کردن سیل برگ",
        "watchlisttools-view": "آلشتیا مرتوط نه بوینیت",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|چک چنه]])",
        "timezone-utc": "UTC",
        "duplicate-defaultsort": "زنهار کلیت پیش فرض جور بیه $2 تازه ای یا کلید پیش فرض جوربیه $1 رد بیه.",
+       "duplicate-displaytitle": "<strong>هشدار:</strong> نشو دئن داسون\" $2 \"باعث باطل بیین نشو دئن داسون \" $1 \" موئه.",
+       "invalid-indicator-name": "<strong>خطا:</strong>خصوصیات جادیارکنیا حال و بار بلگه <code>name</code> نباید حالی بان.",
        "version": "نسقه",
        "version-extensions": "دمادیسیا پورسه",
        "version-skins": "پوسه یا پورسه بیه",
        "api-error-http": "خطا مینجایی:نبوئه د رسینه جا وصل بوئیت.",
        "api-error-illegal-filename": "نوم جانیا اجازه دئه نئ.",
        "api-error-mustbeloggedin": "شما سی سوارکردن فایلیا با بیایت وامین",
+       "api-error-stashnosuchfilekey": "جانیا کلیتی که شما میهاستیت د وه دسرسی داشتوئیت، نیئش.",
        "api-error-timeout": "رسینه جا د گات تیه وه را بیین جواوی نده.",
        "api-error-unclassified": "یه گل خطا نادیار ری ون کرده.",
        "api-error-unknown-code": "خطا نادیار:\"$1\".",
index 2c4029d..8a9977f 100644 (file)
        "rev-deleted-user-contribs": "[nazwa użytkownika lub adres IP usunięte – edycja ukryta we wkładzie]",
        "rev-deleted-text-permission": "Ta wersja strony została '''usunięta'''.\nSzczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze usunięć].",
        "rev-suppressed-text-permission": "Ta wersja strony została <strong>ukryta</strong>.\nSzczegóły można odnaleźć w [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} rejestrze ukryć].",
-       "rev-deleted-text-unhide": "Ta wersja strony została '''usunięta'''.\nSzczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze usunięć].\nJeśli chcesz możesz [$1 obejrzeć tę wersję].",
+       "rev-deleted-text-unhide": "Ta wersja strony została <strong>usunięta</strong>.\nSzczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze usunięć].\nJeśli chcesz, możesz [$1 obejrzeć tę wersję].",
        "rev-suppressed-text-unhide": "Ta wersja strony została '''utajniona'''.\nSzczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} rejestrze utajniania].\nJeśli chcesz możesz [$1 obejrzeć tę wersję].",
-       "rev-deleted-text-view": "Ta wersja strony została '''usunięta'''.\nJeśli chcesz możesz ją obejrzeć. Szczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze usunięć].",
+       "rev-deleted-text-view": "Ta wersja strony została <strong>usunięta</strong>.\nJeśli chcesz, możesz ją obejrzeć. Szczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze usunięć].",
        "rev-suppressed-text-view": "Ta wersja strony została '''utajniona'''.\nJeśli chcesz możesz ją obejrzeć. Szczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} rejestrze utajniania].",
        "rev-deleted-no-diff": "Nie możesz zobaczyć porównania wersji, ponieważ jedna z nich została '''usunięta'''.\nSzczegółowe informacje mogą znajdować się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze usunięć].",
        "rev-suppressed-no-diff": "Nie można wyświetlić różnic, ponieważ jedna z wersji została '''usunięta'''.",
index 5cec26e..5bdc5af 100644 (file)
        "showhideselectedversions": "ټاکلې بڼې ښکاره کول/پټول",
        "editundo": "ناکړ",
        "diff-empty": "(بې توپيره)",
+       "diff-multi-sameuser": "({{PLURAL:$1|Oيو وسطي ريويژن|$1 intermediate revisions}} by the يو شان کارن نه ښکاره کوي)",
        "searchresults": "د پلټنې پايلې",
        "searchresults-title": "د \"$1\" د پلټنې پايلې",
        "titlematches": "د مخ سرليک ورسره ورته دی",
        "invert": "ټاکنې سرچپه کول",
        "tooltip-invert": "په ټاکلو نومتشيالونو کې (او اړونده نومتشيال کې که په نښه شوی وي) د مخونو بدلونونو د پټولو لپاره دا بکس په نښه کړئ",
        "namespace_association": "مل نومتشيال",
+       "tooltip-namespace_association": "په دې خانه کې ټيک نښان ولګوئ که غواړئ خبري اتري او نيم سپيس د ټاکلي نيم سپيس سره کړئ",
        "blanknamespace": "(آرنی)",
        "contributions": "{{GENDER:$1|کارن}} ونډې",
        "contributions-title": "د $1 کارن ونډې",
        "external_image_whitelist": " #دا کرښه چې څنگه ده، همداسې پرېږدۍ<pre>\n#لاندې د منظمو اصطلاحگانو ټوټې (يوازې هغه برخه چې د // په مېنځ کې ليکلې) ځای پر ځای کړی\n#دا به د باندنيو انځورونو د يو آر اېل (hotlinked) سره مطابقه شي \n#هغه څه چې مطابقت لري هغه به د انځورونو په توگه ښکاره شي، کوم چې مطابقت نلري نو يوازې د انځور تړنه به ښکاره کېږي\n#هغه کرښې چې په # پيل کېږي د تبصرو په توگه په نظر کې نيول کېږي\n#دا کرښې د غټو تورو او وړو تورو سره حساسې نه دي\n\n#ټولې regex ټوټې د دغې کرښې نه پورته ځای پر ځای کړی. دا کرښه چې څنگه ده، همداسې يې پرېږدۍ</pre>",
        "tag-filter": "[[Special:Tags|نښلن]] چاڼگر:",
        "tag-filter-submit": "چاڼگر",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2)",
        "tags-display-header": "د بدلون په لړليکونو کې ښکارېدنه",
        "tags-description-header": "د مانا بشپړه څرگندونه",
        "tags-source-header": "سرچينه",
index 11777bc..d2da2b1 100644 (file)
        "logempty": "Used as warning when there are no items to show.",
        "log-title-wildcard": "* Appears in: [[Special:Log]]\n* Description: A check box to enable prefix search option",
        "showhideselectedlogentries": "Text of the button which brings up the [[mw:RevisionDelete|RevisionDelete]] menu on [[Special:Log]].",
-       "log-edit-tags": "Text of button used to access change tagging interface. For more information on tags see [[mw:Manual:Tags]].",        "allpages": "{{doc-special|AllPages}}\nFirst part of the navigation bar for the special page [[Special:AllPages]] and [[Special:PrefixIndex]].\nThe other parts are {{msg-mw|Prevpage}} and {{msg-mw|Nextpage}}.\n{{Identical|All pages}}",
+       "log-edit-tags": "Text of button used to access change tagging interface. For more information on tags see [[mw:Manual:Tags]].",
        "allpages": "{{doc-special|AllPages}}\nFirst part of the navigation bar for the special page [[Special:AllPages]] and [[Special:PrefixIndex]].\nThe other parts are {{msg-mw|Prevpage}} and {{msg-mw|Nextpage}}.\n{{Identical|All pages}}",
        "allpages-summary": "{{doc-specialpagesummary|allpages}}",
        "nextpage": "Third part of the navigation bar for the special page [[Special:AllPages]] and [[Special:PrefixIndex]]. $1 is a page title. The other parts are {{msg-mw|Allpages}} and {{msg-mw|Prevpage}}.\n\n{{Identical|Next page}}",
        "tags-edit-add": "Heading beneath which the user picks which tags to add to the revision or log entry.",
        "tags-edit-remove": "Heading beneath which the user picks which tags to remove from the revision or log entry.",
        "tags-edit-remove-all-tags": "Check box label that the user selects when they want to remove all the tags from the revision or log entry.",
+       "tags-edit-chosen-placeholder": "Placeholder text on the jQuery Chosen input box where users can select zero or more tags.",
+       "tags-edit-chosen-no-results": "Message displayed by the jQuery Chosen input box when the user enters a string which doesn't match a known tag.\n\nDue to technical limitations, the user's input is not passed as a parameter to this message. The string the user entered is wrapped in quotation marks (\") and appended to the end of this string.",
        "tags-edit-reason": "{{Identical|Reason}}",
        "tags-edit-revision-submit": "Text of the submission button of the edit tag form for revisions.\n\nSee also:\n* {{msg-mw|tags-edit-logentry-submit}}",
        "tags-edit-logentry-submit": "Text of the submission button of the edit tag form for log entries.\n\nSee also:\n* {{msg-mw|tags-edit-revision-submit}}",
        "tags-edit-nooldid-title": "Title for an error message ({{msg-mw|tags-edit-nooldid-text}}) for the edit tag form.",
        "tags-edit-nooldid-text": "Error message for the edit tag form.\n\nSee also:\n* {{msg-mw|tags-edit-nooldid-title}}",
        "tags-edit-none-selected": "Error message for the edit tag form.",
-       "tags-edit-chosen-placeholder": "Placeholder text on the jQuery Chosen input box where users can select zero or more tags.",
-       "tags-edit-chosen-no-results": "Message displayed by the jQuery Chosen input box when the user enters a string which doesn't match a known tag.\n\nDue to technical limitations, the user's input is not passed as a parameter to this message. The string the user entered is wrapped in quotation marks (\") and appended to the end of this string.",
        "comparepages": "The title of [[Special:ComparePages]]",
        "comparepages-summary": "{{doc-specialpagesummary|comparepages}}",
        "compare-page1": "Label for the field of the 1st page in the comparison for [[Special:ComparePages]]\n{{Identical|Page}}",
index 7892473..67f3f43 100644 (file)
@@ -31,7 +31,7 @@
        "tog-shownumberswatching": "Aratâ numiru a utilizatorloru cari ti avinu",
        "tog-oldsig": "Cundiľeauâ di tora:",
        "tog-fancysig": "Saidiseaști cundiľeaua ca wikitext (fârâ unâ ligâturâ automatâ)",
-       "tog-uselivepreview": "Ufiliseaști previzualizarea tu chiro realu (experimentalu)",
+       "tog-uselivepreview": "Ufiliseaști previzualizarea live (experimentalu)",
        "tog-forceeditsummary": "Dzâ-ńi anda mi agârșescu s-fânirusescu alâxirili",
        "tog-watchlisthideown": "Ascundi alâxirili a meali la lista a ńia di avinari",
        "tog-watchlisthidebots": "Ascundi alâxirili a boțloru la lista a ńia di avinari",
        "hidden-category-category": "Categorii ascumsi",
        "category-subcat-count": "{{PLURAL:$2|Categoria conține mași subcategoria aestâ.|Categoria conțini {{PLURAL:$1|aestâ subcategorie|aesti $1 subcategorii}}, di-tu $2.}}",
        "category-subcat-count-limited": "Această categorie conțini {{PLURAL:$1|subcategorie|$1 subcategorii}}.",
+       "category-article-count": "{{PLURAL:$2|Categoria ari mași aestâ frândzâ.|{{PLURAL:$1|Frândza di dupâ|Alanti $1 frândzâ}} suntu tu aestâ categorie, di-tu unu totalu di $2.}}",
        "listingcontinuesabbrev": "cont.",
        "index-category": "Frândzâ indexati",
        "noindex-category": "Frândzâ neindexati",
        "disclaimers": "Termeni",
        "disclaimerpage": "Project:Termeni",
        "edithelp": "Agiutoru trâ alâxiri",
+       "helppage-top-gethelp": "Agiutoru",
        "mainpage": "Prota frândzâ",
        "mainpage-description": "Prota frândzâ",
        "policy-url": "Project:Politicâ",
        "pt-login-button": "Leagâ-ti",
        "pt-createaccount": "Fă contu (isape)",
        "pt-userlogout": "Dizleagâ-ti",
+       "changeemail-none": "(țiva)",
+       "changeemail-submit": "Alâxire email",
        "bold_sample": "Scriari groasâ (bold)",
        "bold_tip": "Scriari groasâ (bold)",
        "italic_sample": "Scriari aplicatâ (italic)",
        "showpreview": "Previzualizare",
        "showdiff": "Aratâ alâxirile",
        "loginreqlink": "leagâ-ti",
+       "newarticle": "(Nou)",
        "lineno": "Linia $1:",
        "editundo": "turnari",
        "searchresults": "Rezultatili câftăriľei",
index ca59d02..35342e3 100644 (file)
        "pager-older-n": "{{PLURAL:$1|аҕа 1|аҕа $1}}",
        "suppress": "Кистээһин",
        "querypage-disabled": "Бу анал сирэй тиһилик үлэтин түргэтэтээри араарыллыбыт.",
+       "apihelp": "API-га көмө",
+       "apihelp-no-such-module": "\"$1\" муодул көстүбэтэ.",
        "booksources": "Кинигэлэр источниктара",
        "booksources-search-legend": "Кинигэ туһунан көрдөө",
        "booksources-search": "Бул",
        "import": "Сирэйдэри импортааһын",
        "importinterwiki": "Биики ыккардынааҕы импорт",
        "import-interwiki-text": "Биикини уонна импортанар сирэй аатын киллэр.\nУларытыылар күннэрэ-ыйдара уонна аапптардар ааттара оннуларынан хаалыахтара.\nБиики ыккардынааҕы импорт дьайыылара [[Special:Log/import|аналлаах сурунаалга]] суруллаллар.",
+       "import-interwiki-sourcewiki": "Ийэ биики:",
+       "import-interwiki-sourcepage": "Бастааҥы сирэй:",
        "import-interwiki-history": "Сирэй туох баар историятын көһөрөргө",
        "import-interwiki-templates": "Бары халыыптары киллэр",
        "import-interwiki-submit": "Импортаа",
        "importcantopen": "Импортанар билэ кыайан арыллыбат",
        "importbadinterwiki": "Интервики ыйынньык сыыһа",
        "importsuccess": "Импортааһын түмүктэннэ!",
-       "importnosources": "Ð\91иики Ñ\8bккÑ\80адÑ\8bнааÒ\95Ñ\8b Ð¸Ð¼Ð¿Ð¾Ñ\80Ñ\82анаÑ\80 Ð±Ð¸Ð»Ñ\8d Ñ\82алÑ\8bллÑ\8bбаÑ\82аÑ\85, Ñ\83лаÑ\80Ñ\8bÑ\82Ñ\8bÑ\8b Ð¸Ñ\81Ñ\82оÑ\80иÑ\8fÑ\82Ñ\8bн ÐºÓ©Ò»Ó©Ñ\80Ò¯Ò¯ Ð°Ñ\80аÑ\85Ñ\81а Ñ\81Ñ\8bлдÑ\8cаÑ\80.",
+       "importnosources": "ЫлÑ\8bллаÑ\80 Ð±Ð¸Ð¸ÐºÐ¸ Ñ\8bйÑ\8bллÑ\8bбаÑ\82аÑ\85 Ð¾Ð½Ð¾Ð½ Ñ\83лаÑ\80Ñ\8bÑ\82Ñ\8bÑ\8b Ñ\81Ñ\83Ñ\80Ñ\83наалÑ\8bн ÐºÓ©Ò»Ó©Ñ\80Ò¯Ò¯ Ð°Ñ\80аÑ\85Ñ\81Ñ\8bбÑ\8bÑ\82.",
        "importnofile": "Импортанар билэ сатаан киллэриллибэтэ.",
        "importuploaderrorsize": "Файл ыйааhына наhаа улахан буолан хачайдааhын тохтотулунна.",
        "importuploaderrorpartial": "Хачайдааhын тохтотулунна. Файл сорҕото эрэ сурулунна.",
        "import-upload": "XML-дааннайдары киллэр",
        "import-token-mismatch": "Арахсан хаалбыт. Өссө киирэн көр.",
        "import-invalid-interwiki": "Бу биикиттэн импорт оҥорор сатаммат(а).",
-       "import-error-edit": "«$1» сирэй киллэриллибэтэ, тоҕо диэтэххэ кинини көннөрөрүҥ көҥүллэммэт эбит.",
+       "import-error-edit": "«$1» сирэй көһөрүллүбэтэ, тоҕо диэтэххэ кинини уларытарыҥ көҥүллэммэт эбит.",
        "import-error-create": "«$1» сирэй киллэриллибэтэ, тоҕо диэтэххэ кинини айарыҥ сатаммат эбит.",
        "import-error-interwiki": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ бу аат тас сигэлэргэ (интервикаҕа) аналлаах эбит.",
-       "import-error-special": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ кини баар аат далыгар саҥа сирэйдэри оҥорор көҥүллэммэт эбит.",
-       "import-error-invalid": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ маннык аат туттуллара бобуллубут.",
+       "import-error-special": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ кини угуллубут аатын далыгар саҥа сирэйдэри оҥорор көҥүллэммэт эбит.",
+       "import-error-invalid": "\"$1\" сирэй импортаммата, тоҕо диэтэххэ маннык аат туттуллара бу биикигэ бобуулаах.",
        "import-error-unserialize": "«$1» сирэй $2 барыла структуураланар (десериализация) кыаҕа суох. Барылга иһинээҕитин модела маннык: $3, маннык серияланар: $4.",
        "import-options-wrong": "Алҕастаах {{PLURAL:$2|опция|опциялар}}: <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "Тирэх сирэй ыйыллыбыт аата алҕастаах.",
index 156cc5a..e3f0ca1 100644 (file)
        "createacct-realname": "真实姓名(可选)",
        "createaccountreason": "原因:",
        "createacct-reason": "原因",
-       "createacct-reason-ph": "为什么要创建另一个账户",
+       "createacct-reason-ph": "为什么要创建另一个账户",
        "createacct-captcha": "安全检查",
        "createacct-imgcaptcha-ph": "请输入上图中的文字",
-       "createacct-submit": "创建的账户",
+       "createacct-submit": "创建的账户",
        "createacct-another-submit": "创建另一个账户",
        "createacct-benefit-heading": "{{SITENAME}}是由同你一样的人们构筑的。",
        "createacct-benefit-body1": "{{PLURAL:$1|编辑}}",
index 027e55e..924f6c2 100644 (file)
@@ -144,4 +144,6 @@ $specialPageAliases = array(
        'Watchlist'                 => array( 'پدگیر_ئی_لیست' ),
        'Whatlinkshere'             => array( 'لینک_په_ای_دیما' ),
        'Withoutinterwiki'          => array( 'بئ_شه_مانیجین_ویکی_ئا' ),
-);
\ No newline at end of file
+);
+
+$linkTrail = "/^([اآأبپتثجچحخدڈذرڑزژسشصضطظعغفقکگلمنوۆؤھهئیێ‌]+)(.*)$/sDu";
\ No newline at end of file
index a2c1873..23b702c 100644 (file)
@@ -405,6 +405,7 @@ $specialPageAliases = array(
        'DeletedContributions'      => array( 'DeletedContributions' ),
        'Diff'                      => array( 'Diff' ),
        'DoubleRedirects'           => array( 'DoubleRedirects' ),
+       'EditTags'                  => array( 'EditTags' ),
        'EditWatchlist'             => array( 'EditWatchlist' ),
        'Emailuser'                 => array( 'EmailUser', 'Email' ),
        'ExpandTemplates'           => array( 'ExpandTemplates' ),
diff --git a/tests/phpunit/includes/api/ApiContinuationManagerTest.php b/tests/phpunit/includes/api/ApiContinuationManagerTest.php
new file mode 100644 (file)
index 0000000..ea08c02
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * @covers ApiContinuationManager
+ * @group API
+ */
+class ApiContinuationManagerTest extends MediaWikiTestCase {
+
+       private static function getManager( $continue, $allModules, $generatedModules ) {
+               $context = new DerivativeContext( RequestContext::getMain() );
+               $context->setRequest( new FauxRequest( array( 'continue' => $continue ) ) );
+               $main = new ApiMain( $context );
+               return new ApiContinuationManager( $main, $allModules, $generatedModules );
+       }
+
+       public function testContinuation() {
+               $allModules = array(
+                       new MockApiQueryBase( 'mock1' ),
+                       new MockApiQueryBase( 'mock2' ),
+                       new MockApiQueryBase( 'mocklist' ),
+               );
+               $generator = new MockApiQueryBase( 'generator' );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( 'ApiMain', $manager->getSource() );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 );
+               $this->assertSame( array( array(
+                       'mlcontinue' => 2,
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2',
+               ), false ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+                       'generator' => array( 'gcontinue' => 3 ),
+               ), $manager->getRawContinuation() );
+
+               $result = new ApiResult( 0 );
+               $manager->setContinuationIntoResult( $result );
+               $this->assertSame( array(
+                       'mlcontinue' => 2,
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $manager->addGeneratorContinueParam( $generator, 'gcontinue', array( 3, 4 ) );
+               $this->assertSame( array( array(
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2|mocklist',
+               ), false ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+                       'generator' => array( 'gcontinue' => '3|4' ),
+               ), $manager->getRawContinuation() );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 );
+               $this->assertSame( array( array(
+                       'mlcontinue' => 2,
+                       'gcontinue' => 3,
+                       'continue' => 'gcontinue||',
+               ), true ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+                       'generator' => array( 'gcontinue' => 3 ),
+               ), $manager->getRawContinuation() );
+
+               $result = new ApiResult( 0 );
+               $manager->setContinuationIntoResult( $result );
+               $this->assertSame( array(
+                       'mlcontinue' => 2,
+                       'gcontinue' => 3,
+                       'continue' => 'gcontinue||',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( '', $result->getResultData( 'batchcomplete' ) );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addGeneratorContinueParam( $generator, 'gcontinue', 3 );
+               $this->assertSame( array( array(
+                       'gcontinue' => 3,
+                       'continue' => 'gcontinue||mocklist',
+               ), true ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'generator' => array( 'gcontinue' => 3 ),
+               ), $manager->getRawContinuation() );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $this->assertSame( array( array(
+                       'mlcontinue' => 2,
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2',
+               ), false ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+               ), $manager->getRawContinuation() );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $this->assertSame( array( array(
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2|mocklist',
+               ), false ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+               ), $manager->getRawContinuation() );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $manager->addContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $this->assertSame( array( array(
+                       'mlcontinue' => 2,
+                       'continue' => '-||mock1|mock2',
+               ), true ), $manager->getContinuation() );
+               $this->assertSame( array(
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+               ), $manager->getRawContinuation() );
+
+               $manager = self::getManager( '', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame( $allModules, $manager->getRunModules() );
+               $this->assertSame( array( array(), true ), $manager->getContinuation() );
+               $this->assertSame( array(), $manager->getRawContinuation() );
+
+               $manager = self::getManager( '||mock2', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( false, $manager->isGeneratorDone() );
+               $this->assertSame(
+                       array_values( array_diff_key( $allModules, array( 1 => 1 ) ) ),
+                       $manager->getRunModules()
+               );
+
+               $manager = self::getManager( '-||', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( true, $manager->isGeneratorDone() );
+               $this->assertSame(
+                       array_values( array_diff_key( $allModules, array( 0 => 0, 1 => 1 ) ) ),
+                       $manager->getRunModules()
+               );
+
+               try {
+                       self::getManager( 'foo', $allModules, array( 'mock1', 'mock2' ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UsageException $ex ) {
+                       $this->assertSame(
+                               'Invalid continue param. You should pass the original value returned by the previous query',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $manager = self::getManager( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) );
+               try {
+                       $manager->addContinueParam( $allModules[1], 'm2continue', 1 );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'Module \'mock2\' was not supposed to have been executed, but it was executed anyway',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               try {
+                       $manager->addContinueParam( $allModules[2], 'mlcontinue', 1 );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+       }
+
+}
diff --git a/tests/phpunit/includes/api/ApiErrorFormatterTest.php b/tests/phpunit/includes/api/ApiErrorFormatterTest.php
new file mode 100644 (file)
index 0000000..344af62
--- /dev/null
@@ -0,0 +1,343 @@
+<?php
+
+/**
+ * @group API
+ */
+class ApiErrorFormatterTest extends MediaWikiTestCase {
+
+       /**
+        * @covers ApiErrorFormatter
+        * @dataProvider provideErrorFormatter
+        */
+       public function testErrorFormatter( $format, $lang, $useDB,
+               $expect1, $expect2, $expect3
+       ) {
+               $result = new ApiResult( 8388608 );
+               $formatter = new ApiErrorFormatter( $result, Language::factory( $lang ), $format, $useDB );
+
+               $formatter->addWarning( 'string', 'mainpage' );
+               $formatter->addError( 'err', 'mainpage' );
+               $this->assertSame( $expect1, $result->getResultData(), 'Simple test' );
+
+               $result->reset();
+               $formatter->addWarning( 'foo', 'mainpage' );
+               $formatter->addWarning( 'foo', 'mainpage' );
+               $formatter->addWarning( 'foo', array( 'parentheses', 'foobar' ) );
+               $msg1 = wfMessage( 'mainpage' );
+               $formatter->addWarning( 'message', $msg1 );
+               $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', array( 'overriddenData' => true ) );
+               $formatter->addWarning( 'messageWithData', $msg2 );
+               $formatter->addError( 'errWithData', $msg2 );
+               $this->assertSame( $expect2, $result->getResultData(), 'Complex test' );
+
+               $result->reset();
+               $status = Status::newGood();
+               $status->warning( 'mainpage' );
+               $status->warning( 'parentheses', 'foobar' );
+               $status->warning( $msg1 );
+               $status->warning( $msg2 );
+               $status->error( 'mainpage' );
+               $status->error( 'parentheses', 'foobar' );
+               $formatter->addMessagesFromStatus( 'status', $status );
+               $this->assertSame( $expect3, $result->getResultData(), 'Status test' );
+
+               $this->assertSame(
+                       $expect3['errors']['status'],
+                       $formatter->arrayFromStatus( $status, 'error' ),
+                       'arrayFromStatus test for error'
+               );
+               $this->assertSame(
+                       $expect3['warnings']['status'],
+                       $formatter->arrayFromStatus( $status, 'warning' ),
+                       'arrayFromStatus test for warning'
+               );
+       }
+
+       public static function provideErrorFormatter() {
+               $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain();
+               $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain();
+               $mainpageText = wfMessage( 'mainpage' )->inLanguage( 'de' )->text();
+               $parensText = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'de' )->text();
+               $C = ApiResult::META_CONTENT;
+               $I = ApiResult::META_INDEXED_TAG_NAME;
+
+               return array(
+                       array( 'wikitext', 'de', true,
+                               array(
+                                       'errors' => array(
+                                               'err' => array(
+                                                       array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'string' => array(
+                                                       array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'errors' => array(
+                                               'errWithData' => array(
+                                                       array( 'code' => 'overriddenCode', 'text' => $mainpageText,
+                                                               'overriddenData' => true, $C => 'text' ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'messageWithData' => array(
+                                                       array( 'code' => 'overriddenCode', 'text' => $mainpageText,
+                                                               'overriddenData' => true, $C => 'text' ),
+                                                       $I => 'warning',
+                                               ),
+                                               'message' => array(
+                                                       array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ),
+                                                       $I => 'warning',
+                                               ),
+                                               'foo' => array(
+                                                       array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ),
+                                                       array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'errors' => array(
+                                               'status' => array(
+                                                       array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ),
+                                                       array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'status' => array(
+                                                       array( 'code' => 'mainpage', 'text' => $mainpageText, $C => 'text' ),
+                                                       array( 'code' => 'parentheses', 'text' => $parensText, $C => 'text' ),
+                                                       array( 'code' => 'overriddenCode', 'text' => $mainpageText,
+                                                               'overriddenData' => true, $C => 'text' ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                       ),
+                       array( 'raw', 'fr', true,
+                               array(
+                                       'errors' => array(
+                                               'err' => array(
+                                                       array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'string' => array(
+                                                       array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'errors' => array(
+                                               'errWithData' => array(
+                                                       array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ),
+                                                               'overriddenData' => true ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'messageWithData' => array(
+                                                       array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ),
+                                                               'overriddenData' => true ),
+                                                       $I => 'warning',
+                                               ),
+                                               'message' => array(
+                                                       array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                                                       $I => 'warning',
+                                               ),
+                                               'foo' => array(
+                                                       array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                                                       array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'errors' => array(
+                                               'status' => array(
+                                                       array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                                                       array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'status' => array(
+                                                       array( 'code' => 'mainpage', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                                                       array( 'code' => 'parentheses', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ),
+                                                       array( 'code' => 'overriddenCode', 'message' => 'mainpage', 'params' => array( $I => 'param' ),
+                                                               'overriddenData' => true ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                       ),
+                       array( 'none', 'fr', true,
+                               array(
+                                       'errors' => array(
+                                               'err' => array(
+                                                       array( 'code' => 'mainpage' ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'string' => array(
+                                                       array( 'code' => 'mainpage' ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'errors' => array(
+                                               'errWithData' => array(
+                                                       array( 'code' => 'overriddenCode', 'overriddenData' => true ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'messageWithData' => array(
+                                                       array( 'code' => 'overriddenCode', 'overriddenData' => true ),
+                                                       $I => 'warning',
+                                               ),
+                                               'message' => array(
+                                                       array( 'code' => 'mainpage' ),
+                                                       $I => 'warning',
+                                               ),
+                                               'foo' => array(
+                                                       array( 'code' => 'mainpage' ),
+                                                       array( 'code' => 'parentheses' ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'errors' => array(
+                                               'status' => array(
+                                                       array( 'code' => 'mainpage' ),
+                                                       array( 'code' => 'parentheses' ),
+                                                       $I => 'error',
+                                               ),
+                                       ),
+                                       'warnings' => array(
+                                               'status' => array(
+                                                       array( 'code' => 'mainpage' ),
+                                                       array( 'code' => 'parentheses' ),
+                                                       array( 'code' => 'overriddenCode', 'overriddenData' => true ),
+                                                       $I => 'warning',
+                                               ),
+                                       ),
+                               ),
+                       ),
+               );
+       }
+
+       /**
+        * @covers ApiErrorFormatter_BackCompat
+        */
+       public function testErrorFormatterBC() {
+               $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain();
+               $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain();
+
+               $result = new ApiResult( 8388608 );
+               $formatter = new ApiErrorFormatter_BackCompat( $result );
+
+               $formatter->addWarning( 'string', 'mainpage' );
+               $formatter->addError( 'err', 'mainpage' );
+               $this->assertSame( array(
+                       'error' => array(
+                               'code' => 'mainpage',
+                               'info' => $mainpagePlain,
+                       ),
+                       'warnings' => array(
+                               'string' => array(
+                                       'warnings' => $mainpagePlain,
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ),
+                       ),
+               ), $result->getResultData(), 'Simple test' );
+
+               $result->reset();
+               $formatter->addWarning( 'foo', 'mainpage' );
+               $formatter->addWarning( 'foo', 'mainpage' );
+               $formatter->addWarning( 'foo', array( 'parentheses', 'foobar' ) );
+               $msg1 = wfMessage( 'mainpage' );
+               $formatter->addWarning( 'message', $msg1 );
+               $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', array( 'overriddenData' => true ) );
+               $formatter->addWarning( 'messageWithData', $msg2 );
+               $formatter->addError( 'errWithData', $msg2 );
+               $this->assertSame( array(
+                       'error' => array(
+                               'code' => 'overriddenCode',
+                               'info' => $mainpagePlain,
+                               'overriddenData' => true,
+                       ),
+                       'warnings' => array(
+                               'messageWithData' => array(
+                                       'warnings' => $mainpagePlain,
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ),
+                               'message' => array(
+                                       'warnings' => $mainpagePlain,
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ),
+                               'foo' => array(
+                                       'warnings' => "$mainpagePlain\n$parensPlain",
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ),
+                       ),
+               ), $result->getResultData(), 'Complex test' );
+
+               $result->reset();
+               $status = Status::newGood();
+               $status->warning( 'mainpage' );
+               $status->warning( 'parentheses', 'foobar' );
+               $status->warning( $msg1 );
+               $status->warning( $msg2 );
+               $status->error( 'mainpage' );
+               $status->error( 'parentheses', 'foobar' );
+               $formatter->addMessagesFromStatus( 'status', $status );
+               $this->assertSame( array(
+                       'error' => array(
+                               'code' => 'parentheses',
+                               'info' => $parensPlain,
+                       ),
+                       'warnings' => array(
+                               'status' => array(
+                                       'warnings' => "$mainpagePlain\n$parensPlain",
+                                       ApiResult::META_CONTENT => 'warnings',
+                               ),
+                       ),
+               ), $result->getResultData(), 'Status test' );
+
+               $I = ApiResult::META_INDEXED_TAG_NAME;
+               $this->assertSame(
+                       array(
+                               array( 'type' => 'error', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                               array( 'type' => 'error', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ),
+                               $I => 'error',
+                       ),
+                       $formatter->arrayFromStatus( $status, 'error' ),
+                       'arrayFromStatus test for error'
+               );
+               $this->assertSame(
+                       array(
+                               array( 'type' => 'warning', 'message' => 'mainpage', 'params' => array( $I => 'param' ) ),
+                               array( 'type' => 'warning', 'message' => 'parentheses', 'params' => array( 'foobar', $I => 'param' ) ),
+                               array( 'message' => 'mainpage', 'params' => array( $I => 'param' ), 'type' => 'warning' ),
+                               array( 'message' => 'mainpage', 'params' => array( $I => 'param' ), 'type' => 'warning' ),
+                               $I => 'warning',
+                       ),
+                       $formatter->arrayFromStatus( $status, 'warning' ),
+                       'arrayFromStatus test for warning'
+               );
+       }
+
+}
index 7a03f7d..e8ef180 100644 (file)
@@ -16,7 +16,7 @@ class ApiMainTest extends ApiTestCase {
                        new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) )
                );
                $api->execute();
-               $data = $api->getResultData();
+               $data = $api->getResult()->getResultData();
                $this->assertInternalType( 'array', $data );
                $this->assertArrayHasKey( 'query', $data );
        }
diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php
new file mode 100644 (file)
index 0000000..6c3ce60
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @group API
+ */
+class ApiMessageTest extends MediaWikiTestCase {
+
+       private function compareMessages( $msg, $msg2 ) {
+               $this->assertSame( $msg->getKey(), $msg2->getKey(), 'getKey' );
+               $this->assertSame( $msg->getKeysToTry(), $msg2->getKeysToTry(), 'getKeysToTry' );
+               $this->assertSame( $msg->getParams(), $msg2->getParams(), 'getParams' );
+               $this->assertSame( $msg->getFormat(), $msg2->getFormat(), 'getFormat' );
+               $this->assertSame( $msg->getLanguage(), $msg2->getLanguage(), 'getLanguage' );
+
+               $msg = TestingAccessWrapper::newFromObject( $msg );
+               $msg2 = TestingAccessWrapper::newFromObject( $msg2 );
+               foreach ( array( 'interface', 'useDatabase', 'title' ) as $key ) {
+                       $this->assertSame( $msg->$key, $msg2->$key, $key );
+               }
+       }
+
+       /**
+        * @covers ApiMessage
+        */
+       public function testApiMessage() {
+               $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) );
+               $msg->inLanguage( 'de' )->title( Title::newMainPage() );
+               $msg2 = new ApiMessage( $msg, 'code', array( 'data' ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
+               $msg = new Message( array( 'foo', 'bar' ), array( 'baz' ) );
+               $msg2 = new ApiMessage( array( array( 'foo', 'bar' ), 'baz' ), 'code', array( 'data' ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
+               $msg = new Message( 'foo' );
+               $msg2 = new ApiMessage( 'foo' );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'foo', $msg2->getApiCode() );
+               $this->assertEquals( array(), $msg2->getApiData() );
+
+               $msg2->setApiCode( 'code', array( 'data' ) );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+               $msg2->setApiCode( null );
+               $this->assertEquals( 'foo', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+               $msg2->setApiData( array( 'data2' ) );
+               $this->assertEquals( array( 'data2' ), $msg2->getApiData() );
+       }
+
+       /**
+        * @covers ApiRawMessage
+        */
+       public function testApiRawMessage() {
+               $msg = new RawMessage( 'foo', array( 'baz' ) );
+               $msg->inLanguage( 'de' )->title( Title::newMainPage() );
+               $msg2 = new ApiRawMessage( $msg, 'code', array( 'data' ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
+               $msg = new RawMessage( 'foo', array( 'baz' ) );
+               $msg2 = new ApiRawMessage( array( 'foo', 'baz' ), 'code', array( 'data' ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
+               $msg = new RawMessage( 'foo' );
+               $msg2 = new ApiRawMessage( 'foo', 'code', array( 'data' ) );
+               $this->compareMessages( $msg, $msg2 );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+
+               $msg2->setApiCode( 'code', array( 'data' ) );
+               $this->assertEquals( 'code', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+               $msg2->setApiCode( null );
+               $this->assertEquals( 'foo', $msg2->getApiCode() );
+               $this->assertEquals( array( 'data' ), $msg2->getApiData() );
+               $msg2->setApiData( array( 'data2' ) );
+               $this->assertEquals( array( 'data2' ), $msg2->getApiData() );
+       }
+
+       /**
+        * @covers ApiMessage::create
+        */
+       public function testApiMessageCreate() {
+               $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( new Message( 'mainpage' ) ) );
+               $this->assertInstanceOf( 'ApiRawMessage', ApiMessage::create( new RawMessage( 'mainpage' ) ) );
+               $this->assertInstanceOf( 'ApiMessage', ApiMessage::create( 'mainpage' ) );
+
+               $msg = new ApiMessage( 'mainpage' );
+               $this->assertSame( $msg, ApiMessage::create( $msg ) );
+
+               $msg = new ApiRawMessage( 'mainpage' );
+               $this->assertSame( $msg, ApiMessage::create( $msg ) );
+       }
+
+}
index bd34018..51154ae 100644 (file)
@@ -138,7 +138,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) );
                $this->mTested->execute();
 
-               return $this->mTested->getResult()->getData();
+               return $this->mTested->getResult()->getResultData( null, array( 'Strip' => 'all' ) );
        }
 
        /**
@@ -396,7 +396,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'options' => 'success',
                        'warnings' => array(
                                'options' => array(
-                                       '*' => "Validation error for 'special': cannot be set by this module"
+                                       'warnings' => "Validation error for 'special': cannot be set by this module"
                                )
                        )
                ), $response );
@@ -419,7 +419,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'options' => 'success',
                        'warnings' => array(
                                'options' => array(
-                                       '*' => "Validation error for 'unknownOption': not a valid preference"
+                                       'warnings' => "Validation error for 'unknownOption': not a valid preference"
                                )
                        )
                ), $response );
diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php
new file mode 100644 (file)
index 0000000..7e43a24
--- /dev/null
@@ -0,0 +1,1446 @@
+<?php
+
+/**
+ * @covers ApiResult
+ * @group API
+ */
+class ApiResultTest extends MediaWikiTestCase {
+
+       /**
+        * @covers ApiResult
+        */
+       public function testStaticDataMethods() {
+               $arr = array();
+
+               ApiResult::setValue( $arr, 'setValue', '1' );
+
+               ApiResult::setValue( $arr, null, 'unnamed 1' );
+               ApiResult::setValue( $arr, null, 'unnamed 2' );
+
+               ApiResult::setValue( $arr, 'deleteValue', '2' );
+               ApiResult::unsetValue( $arr, 'deleteValue' );
+
+               ApiResult::setContentValue( $arr, 'setContentValue', '3' );
+
+               $this->assertSame( array(
+                       'setValue' => '1',
+                       'unnamed 1',
+                       'unnamed 2',
+                       ApiResult::META_CONTENT => 'setContentValue',
+                       'setContentValue' => '3',
+               ), $arr );
+
+               try {
+                       ApiResult::setValue( $arr, 'setValue', '99' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( RuntimeException $ex ) {
+                       $this->assertSame(
+                               'Attempting to add element setValue=99, existing value is 1',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               try {
+                       ApiResult::setContentValue( $arr, 'setContentValue2', '99' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( RuntimeException $ex ) {
+                       $this->assertSame(
+                               'Attempting to set content element as setContentValue2 when setContentValue ' .
+                                       'is already set as the content element',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               ApiResult::setValue( $arr, 'setValue', '99', ApiResult::OVERRIDE );
+               $this->assertSame( '99', $arr['setValue'] );
+
+               ApiResult::setContentValue( $arr, 'setContentValue2', '99', ApiResult::OVERRIDE );
+               $this->assertSame( 'setContentValue2', $arr[ApiResult::META_CONTENT] );
+
+               $arr = array( 'foo' => 1, 'bar' => 1 );
+               ApiResult::setValue( $arr, 'top', '2', ApiResult::ADD_ON_TOP );
+               ApiResult::setValue( $arr, null, '2', ApiResult::ADD_ON_TOP );
+               ApiResult::setValue( $arr, 'bottom', '2' );
+               ApiResult::setValue( $arr, 'foo', '2', ApiResult::OVERRIDE );
+               ApiResult::setValue( $arr, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
+               $this->assertSame( array( 0, 'top', 'foo', 'bar', 'bottom' ), array_keys( $arr ) );
+
+               $arr = array();
+               ApiResult::setValue( $arr, 'sub', array( 'foo' => 1 ) );
+               ApiResult::setValue( $arr, 'sub', array( 'bar' => 1 ) );
+               $this->assertSame( array( 'sub' => array( 'foo' => 1, 'bar' => 1 ) ), $arr );
+
+               try {
+                       ApiResult::setValue( $arr, 'sub', array( 'foo' => 2, 'baz' => 2 ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( RuntimeException $ex ) {
+                       $this->assertSame(
+                               'Conflicting keys (foo) when attempting to merge element sub',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $arr = array();
+               $title = Title::newFromText( "MediaWiki:Foobar" );
+               $obj = new stdClass;
+               $obj->foo = 1;
+               $obj->bar = 2;
+               ApiResult::setValue( $arr, 'title', $title );
+               ApiResult::setValue( $arr, 'obj', $obj );
+               $this->assertSame( array(
+                       'title' => (string)$title,
+                       'obj' => array( 'foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc' ),
+               ), $arr );
+
+               $fh = tmpfile();
+               try {
+                       ApiResult::setValue( $arr, 'file', $fh );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add resource(stream) to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               try {
+                       $obj->file = $fh;
+                       ApiResult::setValue( $arr, 'sub', $obj );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add resource(stream) to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               fclose( $fh );
+
+               try {
+                       ApiResult::setValue( $arr, 'inf', INF );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add non-finite floats to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               try {
+                       ApiResult::setValue( $arr, 'nan', NAN );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add non-finite floats to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $arr = array();
+               $result2 = new ApiResult( 8388608 );
+               $result2->addValue( null, 'foo', 'bar' );
+               ApiResult::setValue( $arr, 'baz', $result2 );
+               $this->assertSame( array( 'baz' => array( 'foo' => 'bar' ) ), $arr );
+
+               $arr = array();
+               ApiResult::setValue( $arr, 'foo', "foo\x80bar" );
+               ApiResult::setValue( $arr, 'bar', "a\xcc\x81" );
+               ApiResult::setValue( $arr, 'baz', 74 );
+               $this->assertSame( array(
+                       'foo' => "foo\xef\xbf\xbdbar",
+                       'bar' => "\xc3\xa1",
+                       'baz' => 74,
+               ), $arr );
+       }
+
+       /**
+        * @covers ApiResult
+        */
+       public function testInstanceDataMethods() {
+               $result = new ApiResult( 8388608 );
+
+               $result->addValue( null, 'setValue', '1' );
+
+               $result->addValue( null, null, 'unnamed 1' );
+               $result->addValue( null, null, 'unnamed 2' );
+
+               $result->addValue( null, 'deleteValue', '2' );
+               $result->removeValue( null, 'deleteValue' );
+
+               $result->addValue( array( 'a', 'b' ), 'deleteValue', '3' );
+               $result->removeValue( array( 'a', 'b', 'deleteValue' ), null, '3' );
+
+               $result->addContentValue( null, 'setContentValue', '3' );
+
+               $this->assertSame( array(
+                       'setValue' => '1',
+                       'unnamed 1',
+                       'unnamed 2',
+                       'a' => array( 'b' => array() ),
+                       'setContentValue' => '3',
+                       ApiResult::META_CONTENT => 'setContentValue',
+               ), $result->getResultData() );
+               $this->assertSame( 20, $result->getSize() );
+
+               try {
+                       $result->addValue( null, 'setValue', '99' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( RuntimeException $ex ) {
+                       $this->assertSame(
+                               'Attempting to add element setValue=99, existing value is 1',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               try {
+                       $result->addContentValue( null, 'setContentValue2', '99' );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( RuntimeException $ex ) {
+                       $this->assertSame(
+                               'Attempting to set content element as setContentValue2 when setContentValue ' .
+                                       'is already set as the content element',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $result->addValue( null, 'setValue', '99', ApiResult::OVERRIDE );
+               $this->assertSame( '99', $result->getResultData( array( 'setValue' ) ) );
+
+               $result->addContentValue( null, 'setContentValue2', '99', ApiResult::OVERRIDE );
+               $this->assertSame( 'setContentValue2',
+                       $result->getResultData( array( ApiResult::META_CONTENT ) ) );
+
+               $result->reset();
+               $this->assertSame( array(), $result->getResultData() );
+               $this->assertSame( 0, $result->getSize() );
+
+               $result->addValue( null, 'foo', 1 );
+               $result->addValue( null, 'bar', 1 );
+               $result->addValue( null, 'top', '2', ApiResult::ADD_ON_TOP );
+               $result->addValue( null, null, '2', ApiResult::ADD_ON_TOP );
+               $result->addValue( null, 'bottom', '2' );
+               $result->addValue( null, 'foo', '2', ApiResult::OVERRIDE );
+               $result->addValue( null, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
+               $this->assertSame( array( 0, 'top', 'foo', 'bar', 'bottom' ),
+                       array_keys( $result->getResultData() ) );
+
+               $result->reset();
+               $result->addValue( null, 'foo', array( 'bar' => 1 ) );
+               $result->addValue( array( 'foo', 'top' ), 'x', 2, ApiResult::ADD_ON_TOP );
+               $result->addValue( array( 'foo', 'bottom' ), 'x', 2 );
+               $this->assertSame( array( 'top', 'bar', 'bottom' ),
+                       array_keys( $result->getResultData( array( 'foo' ) ) ) );
+
+               $result->reset();
+               $result->addValue( null, 'sub', array( 'foo' => 1 ) );
+               $result->addValue( null, 'sub', array( 'bar' => 1 ) );
+               $this->assertSame( array( 'sub' => array( 'foo' => 1, 'bar' => 1 ) ),
+                       $result->getResultData() );
+
+               try {
+                       $result->addValue( null, 'sub', array( 'foo' => 2, 'baz' => 2 ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( RuntimeException $ex ) {
+                       $this->assertSame(
+                               'Conflicting keys (foo) when attempting to merge element sub',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $result->reset();
+               $title = Title::newFromText( "MediaWiki:Foobar" );
+               $obj = new stdClass;
+               $obj->foo = 1;
+               $obj->bar = 2;
+               $result->addValue( null, 'title', $title );
+               $result->addValue( null, 'obj', $obj );
+               $this->assertSame( array(
+                       'title' => (string)$title,
+                       'obj' => array( 'foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc' ),
+               ), $result->getResultData() );
+
+               $fh = tmpfile();
+               try {
+                       $result->addValue( null, 'file', $fh );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add resource(stream) to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               try {
+                       $obj->file = $fh;
+                       $result->addValue( null, 'sub', $obj );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add resource(stream) to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               fclose( $fh );
+
+               try {
+                       $result->addValue( null, 'inf', INF );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add non-finite floats to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               try {
+                       $result->addValue( null, 'nan', NAN );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Cannot add non-finite floats to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $result->reset();
+               $result->addParsedLimit( 'foo', 12 );
+               $this->assertSame( array( 'limits' => array( 'foo' => 12 ) ), $result->getResultData() );
+               $result->addParsedLimit( 'foo', 13 );
+               $this->assertSame( array( 'limits' => array( 'foo' => 13 ) ), $result->getResultData() );
+               $this->assertSame( null, $result->getResultData( array( 'foo', 'bar', 'baz' ) ) );
+               $this->assertSame( 13, $result->getResultData( array( 'limits', 'foo' ) ) );
+               try {
+                       $result->getResultData( array( 'limits', 'foo', 'bar' ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               'Path limits.foo is not an array',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $result = new ApiResult( 10 );
+               $formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'none', false );
+               $result->setErrorFormatter( $formatter );
+               $this->assertFalse( $result->addValue( null, 'foo', '12345678901' ) );
+               $this->assertTrue( $result->addValue( null, 'foo', '12345678901', ApiResult::NO_SIZE_CHECK ) );
+               $this->assertSame( 0, $result->getSize() );
+               $result->reset();
+               $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) );
+               $this->assertFalse( $result->addValue( null, 'foo', '1' ) );
+               $result->removeValue( null, 'foo' );
+               $this->assertTrue( $result->addValue( null, 'foo', '1' ) );
+
+               $result = new ApiResult( 8388608 );
+               $result2 = new ApiResult( 8388608 );
+               $result2->addValue( null, 'foo', 'bar' );
+               $result->addValue( null, 'baz', $result2 );
+               $this->assertSame( array( 'baz' => array( 'foo' => 'bar' ) ), $result->getResultData() );
+
+               $result = new ApiResult( 8388608 );
+               $result->addValue( null, 'foo', "foo\x80bar" );
+               $result->addValue( null, 'bar', "a\xcc\x81" );
+               $result->addValue( null, 'baz', 74 );
+               $this->assertSame( array(
+                       'foo' => "foo\xef\xbf\xbdbar",
+                       'bar' => "\xc3\xa1",
+                       'baz' => 74,
+               ), $result->getResultData() );
+       }
+
+       /**
+        * @covers ApiResult
+        */
+       public function testMetadata() {
+               $arr = array( 'foo' => array( 'bar' => array() ) );
+               $result = new ApiResult( 8388608 );
+               $result->addValue( null, 'foo', array( 'bar' => array() ) );
+
+               $expect = array(
+                       'foo' => array(
+                               'bar' => array(
+                                       ApiResult::META_INDEXED_TAG_NAME => 'ritn',
+                                       ApiResult::META_TYPE => 'default',
+                               ),
+                               ApiResult::META_INDEXED_TAG_NAME => 'ritn',
+                               ApiResult::META_TYPE => 'default',
+                       ),
+                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar' ),
+                       ApiResult::META_TYPE => 'array',
+               );
+
+               ApiResult::setSubelementsList( $arr, 'foo' );
+               ApiResult::setSubelementsList( $arr, array( 'bar', 'baz' ) );
+               ApiResult::unsetSubelementsList( $arr, 'baz' );
+               ApiResult::setIndexedTagNameRecursive( $arr, 'ritn' );
+               ApiResult::setIndexedTagName( $arr, 'itn' );
+               ApiResult::setPreserveKeysList( $arr, 'foo' );
+               ApiResult::setPreserveKeysList( $arr, array( 'bar', 'baz' ) );
+               ApiResult::unsetPreserveKeysList( $arr, 'baz' );
+               ApiResult::setArrayTypeRecursive( $arr, 'default' );
+               ApiResult::setArrayType( $arr, 'array' );
+               $this->assertSame( $expect, $arr );
+
+               $result->addSubelementsList( null, 'foo' );
+               $result->addSubelementsList( null, array( 'bar', 'baz' ) );
+               $result->removeSubelementsList( null, 'baz' );
+               $result->addIndexedTagNameRecursive( null, 'ritn' );
+               $result->addIndexedTagName( null, 'itn' );
+               $result->addPreserveKeysList( null, 'foo' );
+               $result->addPreserveKeysList( null, array( 'bar', 'baz' ) );
+               $result->removePreserveKeysList( null, 'baz' );
+               $result->addArrayTypeRecursive( null, 'default' );
+               $result->addArrayType( null, 'array' );
+               $this->assertSame( $expect, $result->getResultData() );
+
+               $arr = array( 'foo' => array( 'bar' => array() ) );
+               $expect = array(
+                       'foo' => array(
+                               'bar' => array(
+                                       ApiResult::META_TYPE => 'kvp',
+                                       ApiResult::META_KVP_KEY_NAME => 'key',
+                               ),
+                               ApiResult::META_TYPE => 'kvp',
+                               ApiResult::META_KVP_KEY_NAME => 'key',
+                       ),
+                       ApiResult::META_TYPE => 'BCkvp',
+                       ApiResult::META_KVP_KEY_NAME => 'bc',
+               );
+               ApiResult::setArrayTypeRecursive( $arr, 'kvp', 'key' );
+               ApiResult::setArrayType( $arr, 'BCkvp', 'bc' );
+               $this->assertSame( $expect, $arr );
+       }
+
+       /**
+        * @covers ApiResult
+        */
+       public function testUtilityFunctions() {
+               $arr = array(
+                       'foo' => array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'bar2' => (object)array( '_dummy' => 'foobaz' ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       'foo2' => (object)array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'bar2' => (object)array( '_dummy' => 'foobaz' ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ),
+                       ApiResult::META_TYPE => 'array',
+                       '_dummy' => 'foobaz',
+                       '_dummy2' => 'foobaz!',
+               );
+               $this->assertEquals( array(
+                       'foo' => array(
+                               'bar' => array(),
+                               'bar2' => (object)array(),
+                               'x' => 'ok',
+                       ),
+                       'foo2' => (object)array(
+                               'bar' => array(),
+                               'bar2' => (object)array(),
+                               'x' => 'ok',
+                       ),
+                       '_dummy2' => 'foobaz!',
+               ), ApiResult::stripMetadata( $arr ), 'ApiResult::stripMetadata' );
+
+               $metadata = array();
+               $data = ApiResult::stripMetadataNonRecursive( $arr, $metadata );
+               $this->assertEquals( array(
+                       'foo' => array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'bar2' => (object)array( '_dummy' => 'foobaz' ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       'foo2' => (object)array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'bar2' => (object)array( '_dummy' => 'foobaz' ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       '_dummy2' => 'foobaz!',
+               ), $data, 'ApiResult::stripMetadataNonRecursive ($data)' );
+               $this->assertEquals( array(
+                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ),
+                       ApiResult::META_TYPE => 'array',
+                       '_dummy' => 'foobaz',
+               ), $metadata, 'ApiResult::stripMetadataNonRecursive ($metadata)' );
+
+               $metadata = null;
+               $data = ApiResult::stripMetadataNonRecursive( (object)$arr, $metadata );
+               $this->assertEquals( (object)array(
+                       'foo' => array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'bar2' => (object)array( '_dummy' => 'foobaz' ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       'foo2' => (object)array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'bar2' => (object)array( '_dummy' => 'foobaz' ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       '_dummy2' => 'foobaz!',
+               ), $data, 'ApiResult::stripMetadataNonRecursive on object ($data)' );
+               $this->assertEquals( array(
+                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ),
+                       ApiResult::META_TYPE => 'array',
+                       '_dummy' => 'foobaz',
+               ), $metadata, 'ApiResult::stripMetadataNonRecursive on object ($metadata)' );
+       }
+
+       /**
+        * @covers ApiResult
+        * @dataProvider provideTransformations
+        * @param string $label
+        * @param array $input
+        * @param array $transforms
+        * @param array|Exception $expect
+        */
+       public function testTransformations( $label, $input, $transforms, $expect ) {
+               $result = new ApiResult( false );
+               $result->addValue( null, 'test', $input );
+
+               if ( $expect instanceof Exception ) {
+                       try {
+                               $output = $result->getResultData( 'test', $transforms );
+                               $this->fail( 'Expected exception not thrown', $label );
+                       } catch ( Exception $ex ) {
+                               $this->assertEquals( $ex, $expect, $label );
+                       }
+               } else {
+                       $output = $result->getResultData( 'test', $transforms );
+                       $this->assertEquals( $expect, $output, $label );
+               }
+       }
+
+       public function provideTransformations() {
+               $kvp = function ( $keyKey, $key, $valKey, $value ) {
+                       return array(
+                               $keyKey => $key,
+                               $valKey => $value,
+                               ApiResult::META_PRESERVE_KEYS => array( $keyKey ),
+                               ApiResult::META_CONTENT => $valKey,
+                               ApiResult::META_TYPE => 'assoc',
+                       );
+               };
+               $typeArr = array(
+                       'defaultArray' => array( 2 => 'a', 0 => 'b', 1 => 'c' ),
+                       'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c' ),
+                       'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c' ),
+                       'array' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'array' ),
+                       'BCarray' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'BCarray' ),
+                       'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'BCassoc' ),
+                       'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                       'kvp' => array( 'x' => 'a', 'y' => 'b', 'z' => array( 'c' ), ApiResult::META_TYPE => 'kvp' ),
+                       'BCkvp' => array( 'x' => 'a', 'y' => 'b',
+                               ApiResult::META_TYPE => 'BCkvp',
+                               ApiResult::META_KVP_KEY_NAME => 'key',
+                       ),
+                       'emptyDefault' => array( '_dummy' => 1 ),
+                       'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ),
+                       '_dummy' => 1,
+                       ApiResult::META_PRESERVE_KEYS => array( '_dummy' ),
+               );
+               $stripArr = array(
+                       'foo' => array(
+                               'bar' => array( '_dummy' => 'foobaz' ),
+                               'baz' => array(
+                                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ),
+                                       ApiResult::META_TYPE => 'array',
+                               ),
+                               'x' => 'ok',
+                               '_dummy' => 'foobaz',
+                       ),
+                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ),
+                       ApiResult::META_TYPE => 'array',
+                       '_dummy' => 'foobaz',
+                       '_dummy2' => 'foobaz!',
+               );
+
+               return array(
+                       array(
+                               'BC: META_BC_BOOLS',
+                               array(
+                                       'BCtrue' => true,
+                                       'BCfalse' => false,
+                                       'true' => true,
+                                       'false' => false,
+                                       ApiResult::META_BC_BOOLS => array( 0, 'true', 'false' ),
+                               ),
+                               array( 'BC' => array() ),
+                               array(
+                                       'BCtrue' => '',
+                                       'true' => true,
+                                       'false' => false,
+                                       ApiResult::META_BC_BOOLS => array( 0, 'true', 'false' ),
+                               )
+                       ),
+                       array(
+                               'BC: META_BC_SUBELEMENTS',
+                               array(
+                                       'bc' => 'foo',
+                                       'nobc' => 'bar',
+                                       ApiResult::META_BC_SUBELEMENTS => array( 'bc' ),
+                               ),
+                               array( 'BC' => array() ),
+                               array(
+                                       'bc' => array(
+                                               '*' => 'foo',
+                                               ApiResult::META_CONTENT => '*',
+                                               ApiResult::META_TYPE => 'assoc',
+                                       ),
+                                       'nobc' => 'bar',
+                                       ApiResult::META_BC_SUBELEMENTS => array( 'bc' ),
+                               ),
+                       ),
+                       array(
+                               'BC: META_CONTENT',
+                               array(
+                                       'content' => '!!!',
+                                       ApiResult::META_CONTENT => 'content',
+                               ),
+                               array( 'BC' => array() ),
+                               array(
+                                       '*' => '!!!',
+                                       ApiResult::META_CONTENT => '*',
+                               ),
+                       ),
+                       array(
+                               'BC: BCkvp type',
+                               array(
+                                       'foo' => 'foo value',
+                                       'bar' => 'bar value',
+                                       '_baz' => 'baz value',
+                                       ApiResult::META_TYPE => 'BCkvp',
+                                       ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ApiResult::META_PRESERVE_KEYS => array( '_baz' ),
+                               ),
+                               array( 'BC' => array() ),
+                               array(
+                                       $kvp( 'key', 'foo', '*', 'foo value' ),
+                                       $kvp( 'key', 'bar', '*', 'bar value' ),
+                                       $kvp( 'key', '_baz', '*', 'baz value' ),
+                                       ApiResult::META_TYPE => 'array',
+                                       ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ApiResult::META_PRESERVE_KEYS => array( '_baz' ),
+                               ),
+                       ),
+                       array(
+                               'BC: BCarray type',
+                               array(
+                                       ApiResult::META_TYPE => 'BCarray',
+                               ),
+                               array( 'BC' => array() ),
+                               array(
+                                       ApiResult::META_TYPE => 'default',
+                               ),
+                       ),
+                       array(
+                               'BC: BCassoc type',
+                               array(
+                                       ApiResult::META_TYPE => 'BCassoc',
+                               ),
+                               array( 'BC' => array() ),
+                               array(
+                                       ApiResult::META_TYPE => 'default',
+                               ),
+                       ),
+                       array(
+                               'BC: BCkvp exception',
+                               array(
+                                       ApiResult::META_TYPE => 'BCkvp',
+                               ),
+                               array( 'BC' => array() ),
+                               new UnexpectedValueException(
+                                       'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item'
+                               ),
+                       ),
+                       array(
+                               'BC: nobool, no*, nosub',
+                               array(
+                                       'true' => true,
+                                       'false' => false,
+                                       'content' => 'content',
+                                       ApiResult::META_CONTENT => 'content',
+                                       'bc' => 'foo',
+                                       ApiResult::META_BC_SUBELEMENTS => array( 'bc' ),
+                                       'BCarray' => array( ApiResult::META_TYPE => 'BCarray' ),
+                                       'BCassoc' => array( ApiResult::META_TYPE => 'BCassoc' ),
+                                       'BCkvp' => array(
+                                               'foo' => 'foo value',
+                                               'bar' => 'bar value',
+                                               '_baz' => 'baz value',
+                                               ApiResult::META_TYPE => 'BCkvp',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                               ApiResult::META_PRESERVE_KEYS => array( '_baz' ),
+                                       ),
+                               ),
+                               array( 'BC' => array( 'nobool', 'no*', 'nosub' ) ),
+                               array(
+                                       'true' => true,
+                                       'false' => false,
+                                       'content' => 'content',
+                                       'bc' => 'foo',
+                                       'BCarray' => array( ApiResult::META_TYPE => 'default' ),
+                                       'BCassoc' => array( ApiResult::META_TYPE => 'default' ),
+                                       'BCkvp' => array(
+                                               $kvp( 'key', 'foo', '*', 'foo value' ),
+                                               $kvp( 'key', 'bar', '*', 'bar value' ),
+                                               $kvp( 'key', '_baz', '*', 'baz value' ),
+                                               ApiResult::META_TYPE => 'array',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                               ApiResult::META_PRESERVE_KEYS => array( '_baz' ),
+                                       ),
+                                       ApiResult::META_CONTENT => 'content',
+                                       ApiResult::META_BC_SUBELEMENTS => array( 'bc' ),
+                               ),
+                       ),
+
+                       array(
+                               'Types: Normal transform',
+                               $typeArr,
+                               array( 'Types' => array() ),
+                               array(
+                                       'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ),
+                                       'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'kvp' => array( 'x' => 'a', 'y' => 'b',
+                                               'z' => array( 'c', ApiResult::META_TYPE => 'array' ),
+                                               ApiResult::META_TYPE => 'assoc'
+                                       ),
+                                       'BCkvp' => array( 'x' => 'a', 'y' => 'b',
+                                               ApiResult::META_TYPE => 'assoc',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ),
+                                       'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ),
+                                       'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ),
+                                       '_dummy' => 1,
+                                       ApiResult::META_PRESERVE_KEYS => array( '_dummy' ),
+                                       ApiResult::META_TYPE => 'assoc',
+                               ),
+                       ),
+                       array(
+                               'Types: AssocAsObject',
+                               $typeArr,
+                               array( 'Types' => array( 'AssocAsObject' => true ) ),
+                               (object)array(
+                                       'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ),
+                                       'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'kvp' => (object)array( 'x' => 'a', 'y' => 'b',
+                                               'z' => array( 'c', ApiResult::META_TYPE => 'array' ),
+                                               ApiResult::META_TYPE => 'assoc'
+                                       ),
+                                       'BCkvp' => (object)array( 'x' => 'a', 'y' => 'b',
+                                               ApiResult::META_TYPE => 'assoc',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ),
+                                       'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ),
+                                       'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ),
+                                       '_dummy' => 1,
+                                       ApiResult::META_PRESERVE_KEYS => array( '_dummy' ),
+                                       ApiResult::META_TYPE => 'assoc',
+                               ),
+                       ),
+                       array(
+                               'Types: ArmorKVP',
+                               $typeArr,
+                               array( 'Types' => array( 'ArmorKVP' => 'name' ) ),
+                               array(
+                                       'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ),
+                                       'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'kvp' => array(
+                                               $kvp( 'name', 'x', 'value', 'a' ),
+                                               $kvp( 'name', 'y', 'value', 'b' ),
+                                               $kvp( 'name', 'z', 'value', array( 'c', ApiResult::META_TYPE => 'array' ) ),
+                                               ApiResult::META_TYPE => 'array'
+                                       ),
+                                       'BCkvp' => array(
+                                               $kvp( 'key', 'x', 'value', 'a' ),
+                                               $kvp( 'key', 'y', 'value', 'b' ),
+                                               ApiResult::META_TYPE => 'array',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ),
+                                       'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ),
+                                       'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ),
+                                       '_dummy' => 1,
+                                       ApiResult::META_PRESERVE_KEYS => array( '_dummy' ),
+                                       ApiResult::META_TYPE => 'assoc',
+                               ),
+                       ),
+                       array(
+                               'Types: ArmorKVP + BC',
+                               $typeArr,
+                               array( 'BC' => array(), 'Types' => array( 'ArmorKVP' => 'name' ) ),
+                               array(
+                                       'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ),
+                                       'defaultAssoc' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'defaultAssoc2' => array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCarray' => array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'BCassoc' => array( 'a', 'b', 'c', ApiResult::META_TYPE => 'array' ),
+                                       'assoc' => array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'kvp' => array(
+                                               $kvp( 'name', 'x', '*', 'a' ),
+                                               $kvp( 'name', 'y', '*', 'b' ),
+                                               $kvp( 'name', 'z', '*', array( 'c', ApiResult::META_TYPE => 'array' ) ),
+                                               ApiResult::META_TYPE => 'array'
+                                       ),
+                                       'BCkvp' => array(
+                                               $kvp( 'key', 'x', '*', 'a' ),
+                                               $kvp( 'key', 'y', '*', 'b' ),
+                                               ApiResult::META_TYPE => 'array',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ),
+                                       'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ),
+                                       'emptyAssoc' => array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ),
+                                       '_dummy' => 1,
+                                       ApiResult::META_PRESERVE_KEYS => array( '_dummy' ),
+                                       ApiResult::META_TYPE => 'assoc',
+                               ),
+                       ),
+                       array(
+                               'Types: ArmorKVP + AssocAsObject',
+                               $typeArr,
+                               array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ) ),
+                               (object)array(
+                                       'defaultArray' => array( 'b', 'c', 'a', ApiResult::META_TYPE => 'array' ),
+                                       'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'array' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCarray' => array( 'a', 'c', 'b', ApiResult::META_TYPE => 'array' ),
+                                       'BCassoc' => (object)array( 'a', 'b', 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c', ApiResult::META_TYPE => 'assoc' ),
+                                       'kvp' => array(
+                                               (object)$kvp( 'name', 'x', 'value', 'a' ),
+                                               (object)$kvp( 'name', 'y', 'value', 'b' ),
+                                               (object)$kvp( 'name', 'z', 'value', array( 'c', ApiResult::META_TYPE => 'array' ) ),
+                                               ApiResult::META_TYPE => 'array'
+                                       ),
+                                       'BCkvp' => array(
+                                               (object)$kvp( 'key', 'x', 'value', 'a' ),
+                                               (object)$kvp( 'key', 'y', 'value', 'b' ),
+                                               ApiResult::META_TYPE => 'array',
+                                               ApiResult::META_KVP_KEY_NAME => 'key',
+                                       ),
+                                       'emptyDefault' => array( '_dummy' => 1, ApiResult::META_TYPE => 'array' ),
+                                       'emptyAssoc' => (object)array( '_dummy' => 1, ApiResult::META_TYPE => 'assoc' ),
+                                       '_dummy' => 1,
+                                       ApiResult::META_PRESERVE_KEYS => array( '_dummy' ),
+                                       ApiResult::META_TYPE => 'assoc',
+                               ),
+                       ),
+                       array(
+                               'Types: BCkvp exception',
+                               array(
+                                       ApiResult::META_TYPE => 'BCkvp',
+                               ),
+                               array( 'Types' => array() ),
+                               new UnexpectedValueException(
+                                       'Type "BCkvp" used without setting ApiResult::META_KVP_KEY_NAME metadata item'
+                               ),
+                       ),
+
+                       array(
+                               'Strip: With ArmorKVP + AssocAsObject transforms',
+                               $typeArr,
+                               array( 'Types' => array( 'ArmorKVP' => 'name', 'AssocAsObject' => true ), 'Strip' => 'all' ),
+                               (object)array(
+                                       'defaultArray' => array( 'b', 'c', 'a' ),
+                                       'defaultAssoc' => (object)array( 'x' => 'a', 1 => 'b', 0 => 'c' ),
+                                       'defaultAssoc2' => (object)array( 2 => 'a', 3 => 'b', 0 => 'c' ),
+                                       'array' => array( 'a', 'c', 'b' ),
+                                       'BCarray' => array( 'a', 'c', 'b' ),
+                                       'BCassoc' => (object)array( 'a', 'b', 'c' ),
+                                       'assoc' => (object)array( 2 => 'a', 0 => 'b', 1 => 'c' ),
+                                       'kvp' => array(
+                                               (object)array( 'name' => 'x', 'value' => 'a' ),
+                                               (object)array( 'name' => 'y', 'value' => 'b' ),
+                                               (object)array( 'name' => 'z', 'value' => array( 'c' ) ),
+                                       ),
+                                       'BCkvp' => array(
+                                               (object)array( 'key' => 'x', 'value' => 'a' ),
+                                               (object)array( 'key' => 'y', 'value' => 'b' ),
+                                       ),
+                                       'emptyDefault' => array(),
+                                       'emptyAssoc' => (object)array(),
+                                       '_dummy' => 1,
+                               ),
+                       ),
+
+                       array(
+                               'Strip: all',
+                               $stripArr,
+                               array( 'Strip' => 'all' ),
+                               array(
+                                       'foo' => array(
+                                               'bar' => array(),
+                                               'baz' => array(),
+                                               'x' => 'ok',
+                                       ),
+                                       '_dummy2' => 'foobaz!',
+                               ),
+                       ),
+                       array(
+                               'Strip: base',
+                               $stripArr,
+                               array( 'Strip' => 'base' ),
+                               array(
+                                       'foo' => array(
+                                               'bar' => array( '_dummy' => 'foobaz' ),
+                                               'baz' => array(
+                                                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                                                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                                                       ApiResult::META_PRESERVE_KEYS => array( 'foo', 'bar', '_dummy2', 0 ),
+                                                       ApiResult::META_TYPE => 'array',
+                                               ),
+                                               'x' => 'ok',
+                                               '_dummy' => 'foobaz',
+                                       ),
+                                       '_dummy2' => 'foobaz!',
+                               ),
+                       ),
+                       array(
+                               'Strip: bc',
+                               $stripArr,
+                               array( 'Strip' => 'bc' ),
+                               array(
+                                       'foo' => array(
+                                               'bar' => array(),
+                                               'baz' => array(
+                                                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                                                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                                               ),
+                                               'x' => 'ok',
+                                       ),
+                                       '_dummy2' => 'foobaz!',
+                                       ApiResult::META_SUBELEMENTS => array( 'foo', 'bar' ),
+                                       ApiResult::META_INDEXED_TAG_NAME => 'itn',
+                               ),
+                       ),
+
+                       array(
+                               'Custom transform',
+                               array(
+                                       'foo' => '?',
+                                       'bar' => '?',
+                                       '_dummy' => '?',
+                                       '_dummy2' => '?',
+                                       '_dummy3' => '?',
+                                       ApiResult::META_CONTENT => 'foo',
+                                       ApiResult::META_PRESERVE_KEYS => array( '_dummy2', '_dummy3' ),
+                               ),
+                               array(
+                                       'Custom' => array( $this, 'customTransform' ),
+                                       'BC' => array(),
+                                       'Types' => array(),
+                                       'Strip' => 'all'
+                               ),
+                               array(
+                                       '*' => 'FOO',
+                                       'bar' => 'BAR',
+                                       'baz' => array( 'a', 'b' ),
+                                       '_dummy2' => '_DUMMY2',
+                                       '_dummy3' => '_DUMMY3',
+                                       ApiResult::META_CONTENT => 'bar',
+                               ),
+                       ),
+               );
+
+       }
+
+       /**
+        * Custom transformer for testTransformations
+        * @param array &$data
+        * @param array &$metadata
+        */
+       public function customTransform( &$data, &$metadata ) {
+               // Prevent recursion
+               if ( isset( $metadata['_added'] ) ) {
+                       $metadata[ApiResult::META_TYPE] = 'array';
+                       return;
+               }
+
+               foreach ( $data as $k => $v ) {
+                       $data[$k] = strtoupper( $k );
+               }
+               $data['baz'] = array( '_added' => 1, 'z' => 'b', 'y' => 'a' );
+               $metadata[ApiResult::META_PRESERVE_KEYS][0] = '_dummy';
+               $data[ApiResult::META_CONTENT] = 'bar';
+       }
+
+       /**
+        * @covers ApiResult
+        */
+       public function testDeprecatedFunctions() {
+               // Ignore ApiResult deprecation warnings during this test
+               set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
+                       if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) {
+                               return true;
+                       }
+                       return false;
+               } );
+               $reset = new ScopedCallback( 'restore_error_handler' );
+
+               $context = new DerivativeContext( RequestContext::getMain() );
+               $context->setConfig( new HashConfig( array(
+                       'APIModules' => array(),
+                       'APIFormatModules' => array(),
+                       'APIMaxResultSize' => 42,
+               ) ) );
+               $main = new ApiMain( $context );
+               $result = TestingAccessWrapper::newFromObject( new ApiResult( $main ) );
+               $this->assertSame( 42, $result->maxSize );
+               $this->assertSame( $main->getErrorFormatter(), $result->errorFormatter );
+               $this->assertSame( $main, $result->mainForContinuation );
+
+               $result = new ApiResult( 8388608 );
+
+               $result->addContentValue( null, 'test', 'content' );
+               $result->addContentValue( array( 'foo', 'bar' ), 'test', 'content' );
+               $result->addIndexedTagName( null, 'itn' );
+               $result->addSubelementsList( null, array( 'sub' ) );
+               $this->assertSame( array(
+                       'foo' => array(
+                               'bar' => array(
+                                       '*' => 'content',
+                               ),
+                       ),
+                       '*' => 'content',
+               ), $result->getData() );
+               $result->setRawMode();
+               $this->assertSame( array(
+                       'foo' => array(
+                               'bar' => array(
+                                       '*' => 'content',
+                               ),
+                       ),
+                       '*' => 'content',
+                       '_element' => 'itn',
+                       '_subelements' => array( 'sub' ),
+               ), $result->getData() );
+
+               $arr = array();
+               ApiResult::setContent( $arr, 'value' );
+               ApiResult::setContent( $arr, 'value2', 'foobar' );
+               $this->assertSame( array(
+                       ApiResult::META_CONTENT => 'content',
+                       'content' => 'value',
+                       'foobar' => array(
+                               ApiResult::META_CONTENT => 'content',
+                               'content' => 'value2',
+                       ),
+               ), $arr );
+
+               $result = new ApiResult( 3 );
+               $formatter = new ApiErrorFormatter_BackCompat( $result );
+               $result->setErrorFormatter( $formatter );
+               $result->disableSizeCheck();
+               $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) );
+               $result->enableSizeCheck();
+               $this->assertSame( 0, $result->getSize() );
+               $this->assertFalse( $result->addValue( null, 'foo', '1234567890' ) );
+
+               $arr = array( 'foo' => array( 'bar' => 1 ) );
+               $result->setIndexedTagName_recursive( $arr, 'itn' );
+               $this->assertSame( array(
+                       'foo' => array(
+                               'bar' => 1,
+                               ApiResult::META_INDEXED_TAG_NAME => 'itn'
+                       ),
+               ), $arr );
+
+               $status = Status::newGood();
+               $status->fatal( 'parentheses', '1' );
+               $status->fatal( 'parentheses', '2' );
+               $status->warning( 'parentheses', '3' );
+               $status->warning( 'parentheses', '4' );
+               $this->assertSame( array(
+                       array(
+                               'type' => 'error',
+                               'message' => 'parentheses',
+                               'params' => array(
+                                       0 => '1',
+                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
+                               ),
+                       ),
+                       array(
+                               'type' => 'error',
+                               'message' => 'parentheses',
+                               'params' => array(
+                                       0 => '2',
+                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
+                               ),
+                       ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'error',
+               ), $result->convertStatusToArray( $status, 'error' ) );
+               $this->assertSame( array(
+                       array(
+                               'type' => 'warning',
+                               'message' => 'parentheses',
+                               'params' => array(
+                                       0 => '3',
+                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
+                               ),
+                       ),
+                       array(
+                               'type' => 'warning',
+                               'message' => 'parentheses',
+                               'params' => array(
+                                       0 => '4',
+                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
+                               ),
+                       ),
+                       ApiResult::META_INDEXED_TAG_NAME => 'warning',
+               ), $result->convertStatusToArray( $status, 'warning' ) );
+       }
+
+       /**
+        * @covers ApiResult
+        */
+       public function testDeprecatedContinuation() {
+               // Ignore ApiResult deprecation warnings during this test
+               set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
+                       if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) {
+                               return true;
+                       }
+                       return false;
+               } );
+
+               $reset = new ScopedCallback( 'restore_error_handler' );
+               $allModules = array(
+                       new MockApiQueryBase( 'mock1' ),
+                       new MockApiQueryBase( 'mock2' ),
+                       new MockApiQueryBase( 'mocklist' ),
+               );
+               $generator = new MockApiQueryBase( 'generator' );
+
+               $main = new ApiMain( RequestContext::getMain() );
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'mlcontinue' => 2,
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+                       'generator' => array( 'gcontinue' => 3 ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $result->setGeneratorContinueParam( $generator, 'gcontinue', array( 3, 4 ) );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2|mocklist',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+                       'generator' => array( 'gcontinue' => '3|4' ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'mlcontinue' => 2,
+                       'gcontinue' => 3,
+                       'continue' => 'gcontinue||',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( '', $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+                       'generator' => array( 'gcontinue' => 3 ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'gcontinue' => 3,
+                       'continue' => 'gcontinue||mocklist',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( '', $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'generator' => array( 'gcontinue' => 3 ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'mlcontinue' => 2,
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setContinueParam( $allModules[0], 'm1continue', array( 1, 2 ) );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'm1continue' => '1|2',
+                       'continue' => '||mock2|mocklist',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'mock1' => array( 'm1continue' => '1|2' ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( array(
+                       'mlcontinue' => 2,
+                       'continue' => '-||mock1|mock2',
+               ), $result->getResultData( 'continue' ) );
+               $this->assertSame( '', $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( array(
+                       'mocklist' => array( 'mlcontinue' => 2 ),
+               ), $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( null, $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame( array( false, $allModules ), $ret );
+               $result->endContinuation( 'raw' );
+               $result->endContinuation( 'standard' );
+               $this->assertSame( null, $result->getResultData( 'continue' ) );
+               $this->assertSame( '', $result->getResultData( 'batchcomplete' ) );
+               $this->assertSame( null, $result->getResultData( 'query-continue' ) );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( '||mock2', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame(
+                       array( false, array_values( array_diff_key( $allModules, array( 1 => 1 ) ) ) ),
+                       $ret
+               );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $ret = $result->beginContinuation( '-||', $allModules, array( 'mock1', 'mock2' ) );
+               $this->assertSame(
+                       array( true, array_values( array_diff_key( $allModules, array( 0 => 0, 1 => 1 ) ) ) ),
+                       $ret
+               );
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               try {
+                       $result->beginContinuation( 'foo', $allModules, array( 'mock1', 'mock2' ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UsageException $ex ) {
+                       $this->assertSame(
+                               'Invalid continue param. You should pass the original value returned by the previous query',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               $main->setContinuationManager( null );
+
+               $result = new ApiResult( 8388608 );
+               $result->setMainForContinuation( $main );
+               $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ), array( 'mock1', 'mock2' ) );
+               try {
+                       $result->setContinueParam( $allModules[1], 'm2continue', 1 );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'Module \'mock2\' was not supposed to have been executed, but it was executed anyway',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               try {
+                       $result->setContinueParam( $allModules[2], 'mlcontinue', 1 );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'Module \'mocklist\' called ApiContinuationManager::addContinueParam but was not passed to ApiContinuationManager::__construct',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+               $main->setContinuationManager( null );
+
+       }
+
+       public function testObjectSerialization() {
+               $arr = array();
+               ApiResult::setValue( $arr, 'foo', (object)array( 'a' => 1, 'b' => 2 ) );
+               $this->assertSame( array(
+                       'a' => 1,
+                       'b' => 2,
+                       ApiResult::META_TYPE => 'assoc',
+               ), $arr['foo'] );
+
+               $arr = array();
+               ApiResult::setValue( $arr, 'foo', new ApiResultTestStringifiableObject() );
+               $this->assertSame( 'Ok', $arr['foo'] );
+
+               $arr = array();
+               ApiResult::setValue( $arr, 'foo', new ApiResultTestSerializableObject( 'Ok' ) );
+               $this->assertSame( 'Ok', $arr['foo'] );
+
+               try {
+                       $arr = array();
+                       ApiResult::setValue( $arr, 'foo',  new ApiResultTestSerializableObject(
+                               new ApiResultTestStringifiableObject()
+                       ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'ApiResultTestSerializableObject::serializeForApiResult() returned an object of class ApiResultTestStringifiableObject',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               try {
+                       $arr = array();
+                       ApiResult::setValue( $arr, 'foo',  new ApiResultTestSerializableObject( NAN ) );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( UnexpectedValueException $ex ) {
+                       $this->assertSame(
+                               'ApiResultTestSerializableObject::serializeForApiResult() returned an invalid value: Cannot add non-finite floats to ApiResult',
+                               $ex->getMessage(),
+                               'Expected exception'
+                       );
+               }
+
+               $arr = array();
+               ApiResult::setValue( $arr, 'foo',  new ApiResultTestSerializableObject(
+                       array(
+                               'one' => new ApiResultTestStringifiableObject( '1' ),
+                               'two' => new ApiResultTestSerializableObject( 2 ),
+                       )
+               ) );
+               $this->assertSame( array(
+                       'one' => '1',
+                       'two' => 2,
+               ), $arr['foo'] );
+       }
+
+}
+
+class ApiResultTestStringifiableObject {
+       private $ret;
+
+       public function __construct( $ret = 'Ok' ) {
+               $this->ret = $ret;
+       }
+
+       public function __toString() {
+               return $this->ret;
+       }
+}
+
+class ApiResultTestSerializableObject {
+       private $ret;
+
+       public function __construct( $ret ) {
+               $this->ret = $ret;
+       }
+
+       public function __toString() {
+               return "Fail";
+       }
+
+       public function serializeForApiResult() {
+               return $this->ret;
+       }
+}
index 8c27b10..da62bb0 100644 (file)
@@ -116,7 +116,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 
                // construct result
                $results = array(
-                       $module->getResultData(),
+                       $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ),
                        $context->getRequest(),
                        $context->getRequest()->getSessionArray()
                );
index d94aa2c..516da0c 100644 (file)
@@ -4,9 +4,6 @@ class MockApi extends ApiBase {
        public function execute() {
        }
 
-       public function getVersion() {
-       }
-
        public function __construct() {
        }
 
index 4bede51..f5b50e5 100644 (file)
@@ -1,11 +1,15 @@
 <?php
 class MockApiQueryBase extends ApiQueryBase {
+       private $name;
+
        public function execute() {
        }
 
-       public function getVersion() {
+       public function __construct( $name = 'mock' ) {
+               $this->name = $name;
        }
 
-       public function __construct() {
+       public function getModuleName() {
+               return $this->name;
        }
 }
index 1e4ea53..3fcfc73 100644 (file)
@@ -16,8 +16,12 @@ class ApiFormatDbgTest extends ApiFormatTestBase {
                return array(
                        // Basic types
                        array( array( null ), "array ({$warning}\n  0 => NULL,\n)" ),
-                       array( array( true ), "array ({$warning}\n  0 => true,\n)" ),
-                       array( array( false ), "array ({$warning}\n  0 => false,\n)" ),
+                       array( array( true ), "array ({$warning}\n  0 => '',\n)" ),
+                       array( array( false ), "array ({$warning}\n)" ),
+                       array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "array ({$warning}\n  0 => true,\n)" ),
+                       array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "array ({$warning}\n  0 => false,\n)" ),
                        array( array( 42 ), "array ({$warning}\n  0 => 42,\n)" ),
                        array( array( 42.5 ), "array ({$warning}\n  0 => 42.5,\n)" ),
                        array( array( 1e42 ), "array ({$warning}\n  0 => 1.0E+42,\n)" ),
@@ -29,9 +33,22 @@ class ApiFormatDbgTest extends ApiFormatTestBase {
                        array( array( array( 1 ) ), "array ({$warning}\n  0 => \n  array (\n    0 => 1,\n  ),\n)" ),
                        array( array( array( 'x' => 1 ) ), "array ({$warning}\n  0 => \n  array (\n    'x' => 1,\n  ),\n)" ),
                        array( array( array( 2 => 1 ) ), "array ({$warning}\n  0 => \n  array (\n    2 => 1,\n  ),\n)" ),
+                       array( array( (object)array() ), "array ({$warning}\n  0 => \n  array (\n  ),\n)" ),
+                       array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array ({$warning}\n  0 => \n  array (\n    0 => 1,\n  ),\n)" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array ({$warning}\n  0 => \n  array (\n    0 => 1,\n  ),\n)" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array ({$warning}\n  0 => \n  array (\n    'x' => 1,\n  ),\n)" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                               "array ({$warning}\n  0 => \n  array (\n    0 => \n    array (\n      'key' => 'x',\n      '*' => 1,\n    ),\n  ),\n)" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array ({$warning}\n  0 => \n  array (\n    'x' => 1,\n  ),\n)" ),
+                       array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array ({$warning}\n  0 => \n  array (\n    0 => 'a',\n    1 => 'b',\n  ),\n)" ),
 
                        // Content
-                       array( array( '*' => 'foo' ), "array ({$warning}\n  '*' => 'foo',\n)" ),
+                       array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               "array ({$warning}\n  '*' => 'foo',\n)" ),
+
+                       // BC Subelements
+                       array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                               "array ({$warning}\n  'foo' => \n  array (\n    '*' => 'foo',\n  ),\n)" ),
                );
        }
 
index 2800d2d..c0f67f8 100644 (file)
@@ -24,8 +24,12 @@ class ApiFormatDumpTest extends ApiFormatTestBase {
                return array(
                        // Basic types
                        array( array( null ), "array(2) {{$warning}\n  [0]=>\n  NULL\n}\n" ),
-                       array( array( true ), "array(2) {{$warning}\n  [0]=>\n  bool(true)\n}\n" ),
-                       array( array( false ), "array(2) {{$warning}\n  [0]=>\n  bool(false)\n}\n" ),
+                       array( array( true ), "array(2) {{$warning}\n  [0]=>\n  string(0) \"\"\n}\n" ),
+                       array( array( false ), "array(1) {{$warning}\n}\n" ),
+                       array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "array(2) {{$warning}\n  [0]=>\n  bool(true)\n}\n" ),
+                       array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "array(2) {{$warning}\n  [0]=>\n  bool(false)\n}\n" ),
                        array( array( 42 ), "array(2) {{$warning}\n  [0]=>\n  int(42)\n}\n" ),
                        array( array( 42.5 ), "array(2) {{$warning}\n  [0]=>\n  float(42.5)\n}\n" ),
                        array( array( 1e42 ), "array(2) {{$warning}\n  [0]=>\n  float(1.0E+42)\n}\n" ),
@@ -37,9 +41,22 @@ class ApiFormatDumpTest extends ApiFormatTestBase {
                        array( array( array( 1 ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [0]=>\n    int(1)\n  }\n}\n" ),
                        array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [\"x\"]=>\n    int(1)\n  }\n}\n" ),
                        array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [2]=>\n    int(1)\n  }\n}\n" ),
+                       array( array( (object)array() ), "array(2) {{$warning}\n  [0]=>\n  array(0) {\n  }\n}\n" ),
+                       array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [0]=>\n    int(1)\n  }\n}\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [0]=>\n    int(1)\n  }\n}\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [\"x\"]=>\n    int(1)\n  }\n}\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                               "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [0]=>\n    array(2) {\n      [\"key\"]=>\n      string(1) \"x\"\n      [\"*\"]=>\n      int(1)\n    }\n  }\n}\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "array(2) {{$warning}\n  [0]=>\n  array(1) {\n    [\"x\"]=>\n    int(1)\n  }\n}\n" ),
+                       array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "array(2) {{$warning}\n  [0]=>\n  array(2) {\n    [0]=>\n    string(1) \"a\"\n    [1]=>\n    string(1) \"b\"\n  }\n}\n" ),
 
                        // Content
-                       array( array( '*' => 'foo' ), "array(2) {{$warning}\n  [\"*\"]=>\n  string(3) \"foo\"\n}\n" ),
+                       array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               "array(2) {{$warning}\n  [\"*\"]=>\n  string(3) \"foo\"\n}\n" ),
+
+                       // BC Subelements
+                       array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                               "array(2) {{$warning}\n  [\"foo\"]=>\n  array(1) {\n    [\"*\"]=>\n    string(3) \"foo\"\n  }\n}\n" ),
                );
        }
 
index bdf3f13..3dfcaf0 100644 (file)
@@ -8,34 +8,102 @@ class ApiFormatJsonTest extends ApiFormatTestBase {
 
        protected $printerName = 'json';
 
+       private static function addFormatVersion( $format, $arr ) {
+               foreach ( $arr as &$p ) {
+                       if ( !isset( $p[2] ) ) {
+                               $p[2] = array( 'formatversion' => $format );
+                       } else {
+                               $p[2]['formatversion'] = $format;
+                       }
+               }
+               return $arr;
+       }
+
        public static function provideGeneralEncoding() {
-               return array(
-                       // Basic types
-                       array( array( null ), '[null]' ),
-                       array( array( true ), '[true]' ),
-                       array( array( false ), '[false]' ),
-                       array( array( 42 ), '[42]' ),
-                       array( array( 42.5 ), '[42.5]' ),
-                       array( array( 1e42 ), '[1.0e+42]' ),
-                       array( array( 'foo' ), '["foo"]' ),
-                       array( array( 'fóo' ), '["f\u00f3o"]' ),
-                       array( array( 'fóo' ), '["fóo"]', array( 'utf8' => 1 ) ),
-
-                       // Arrays and objects
-                       array( array( array() ), '[[]]' ),
-                       array( array( array( 1 ) ), '[[1]]' ),
-                       array( array( array( 'x' => 1 ) ), '[{"x":1}]' ),
-                       array( array( array( 2 => 1 ) ), '[{"2":1}]' ),
-                       array( array( (object)array() ), '[{}]' ),
-
-                       // Content
-                       array( array( '*' => 'foo' ), '{"*":"foo"}' ),
-
-                       // Callbacks
-                       array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ),
-
-                       // Cross-domain mangling
-                       array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ),
+               return array_merge(
+                       self::addFormatVersion( 1, array(
+                               // Basic types
+                               array( array( null ), '[null]' ),
+                               array( array( true ), '[""]' ),
+                               array( array( false ), '[]' ),
+                               array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), '[true]' ),
+                               array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), '[false]' ),
+                               array( array( 42 ), '[42]' ),
+                               array( array( 42.5 ), '[42.5]' ),
+                               array( array( 1e42 ), '[1.0e+42]' ),
+                               array( array( 'foo' ), '["foo"]' ),
+                               array( array( 'fóo' ), '["f\u00f3o"]' ),
+                               array( array( 'fóo' ), '["fóo"]', array( 'utf8' => 1 ) ),
+
+                               // Arrays and objects
+                               array( array( array() ), '[[]]' ),
+                               array( array( array( 1 ) ), '[[1]]' ),
+                               array( array( array( 'x' => 1 ) ), '[{"x":1}]' ),
+                               array( array( array( 2 => 1 ) ), '[{"2":1}]' ),
+                               array( array( (object)array() ), '[{}]' ),
+                               array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '[{"0":1}]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '[[1]]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '[{"x":1}]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                                       '[[{"key":"x","*":1}]]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '[{"x":1}]' ),
+                               array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '[["a","b"]]' ),
+
+                               // Content
+                               array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                                       '{"*":"foo"}' ),
+
+                               // BC Subelements
+                               array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                                       '{"foo":{"*":"foo"}}' ),
+
+                               // Callbacks
+                               array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ),
+
+                               // Cross-domain mangling
+                               array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ),
+                       ) ),
+                       self::addFormatVersion( 2, array(
+                               // Basic types
+                               array( array( null ), '[null]' ),
+                               array( array( true ), '[true]' ),
+                               array( array( false ), '[false]' ),
+                               array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ), '[true]' ),
+                               array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ), '[false]' ),
+                               array( array( 42 ), '[42]' ),
+                               array( array( 42.5 ), '[42.5]' ),
+                               array( array( 1e42 ), '[1.0e+42]' ),
+                               array( array( 'foo' ), '["foo"]' ),
+                               array( array( 'fóo' ), '["fóo"]' ),
+                               array( array( 'fóo' ), '["f\u00f3o"]', array( 'ascii' => 1 ) ),
+
+                               // Arrays and objects
+                               array( array( array() ), '[[]]' ),
+                               array( array( array( 'x' => 1 ) ), '[{"x":1}]' ),
+                               array( array( array( 2 => 1 ) ), '[{"2":1}]' ),
+                               array( array( (object)array() ), '[{}]' ),
+                               array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '[{"0":1}]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '[[1]]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '[{"x":1}]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                                       '[{"x":1}]' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '[[1]]' ),
+                               array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '[{"0":"a","1":"b"}]' ),
+
+                               // Content
+                               array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                                       '{"content":"foo"}' ),
+
+                               // BC Subelements
+                               array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                                       '{"foo":"foo"}' ),
+
+                               // Callbacks
+                               array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ),
+
+                               // Cross-domain mangling
+                               array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ),
+                       ) )
                );
        }
 
index 1487ad0..8f81a41 100644 (file)
@@ -25,9 +25,19 @@ class ApiFormatNoneTest extends ApiFormatTestBase {
                        array( array( array( 1 ) ), '' ),
                        array( array( array( 'x' => 1 ) ), '' ),
                        array( array( array( 2 => 1 ) ), '' ),
+                       array( array( (object)array() ), '' ),
+                       array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), '' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ), '' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '' ),
+                       array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '' ),
 
                        // Content
                        array( array( '*' => 'foo' ), '' ),
+
+                       // BC Subelements
+                       array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ), '' ),
                );
        }
 
index 469346c..0cb44e9 100644 (file)
@@ -8,26 +8,93 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
 
        protected $printerName = 'php';
 
+       private static function addFormatVersion( $format, $arr ) {
+               foreach ( $arr as &$p ) {
+                       if ( !isset( $p[2] ) ) {
+                               $p[2] = array( 'formatversion' => $format );
+                       } else {
+                               $p[2]['formatversion'] = $format;
+                       }
+               }
+               return $arr;
+       }
+
        public static function provideGeneralEncoding() {
-               return array(
-                       // Basic types
-                       array( array( null ), 'a:1:{i:0;N;}' ),
-                       array( array( true ), 'a:1:{i:0;b:1;}' ),
-                       array( array( false ), 'a:1:{i:0;b:0;}' ),
-                       array( array( 42 ), 'a:1:{i:0;i:42;}' ),
-                       array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ),
-                       array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ),
-                       array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ),
-                       array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ),
+               return array_merge(
+                       self::addFormatVersion( 1, array(
+                               // Basic types
+                               array( array( null ), 'a:1:{i:0;N;}' ),
+                               array( array( true ), 'a:1:{i:0;s:0:"";}' ),
+                               array( array( false ), 'a:0:{}' ),
+                               array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                                       'a:1:{i:0;b:1;}' ),
+                               array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                                       'a:1:{i:0;b:0;}' ),
+                               array( array( 42 ), 'a:1:{i:0;i:42;}' ),
+                               array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ),
+                               array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ),
+                               array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ),
+                               array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ),
+
+                               // Arrays and objects
+                               array( array( array() ), 'a:1:{i:0;a:0:{}}' ),
+                               array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+                               array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ),
+                               array( array( (object)array() ), 'a:1:{i:0;a:0:{}}' ),
+                               array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                                       'a:1:{i:0;a:1:{i:0;a:2:{s:3:"key";s:1:"x";s:1:"*";i:1;}}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+                               array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), 'a:1:{i:0;a:2:{i:0;s:1:"a";i:1;s:1:"b";}}' ),
+
+                               // Content
+                               array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                                       'a:1:{s:1:"*";s:3:"foo";}' ),
+
+                               // BC Subelements
+                               array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                                       'a:1:{s:3:"foo";a:1:{s:1:"*";s:3:"foo";}}' ),
+                       ) ),
+                       self::addFormatVersion( 2, array(
+                               // Basic types
+                               array( array( null ), 'a:1:{i:0;N;}' ),
+                               array( array( true ), 'a:1:{i:0;b:1;}' ),
+                               array( array( false ), 'a:1:{i:0;b:0;}' ),
+                               array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                                       'a:1:{i:0;b:1;}' ),
+                               array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                                       'a:1:{i:0;b:0;}' ),
+                               array( array( 42 ), 'a:1:{i:0;i:42;}' ),
+                               array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ),
+                               array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ),
+                               array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ),
+                               array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ),
+
+                               // Arrays and objects
+                               array( array( array() ), 'a:1:{i:0;a:0:{}}' ),
+                               array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+                               array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ),
+                               array( array( (object)array() ), 'a:1:{i:0;a:0:{}}' ),
+                               array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                                       'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+                               array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+                               array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), 'a:1:{i:0;a:2:{i:0;s:1:"a";i:1;s:1:"b";}}' ),
 
-                       // Arrays and objects
-                       array( array( array() ), 'a:1:{i:0;a:0:{}}' ),
-                       array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
-                       array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
-                       array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ),
+                               // Content
+                               array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                                       'a:1:{s:7:"content";s:3:"foo";}' ),
 
-                       // Content
-                       array( array( '*' => 'foo' ), 'a:1:{s:1:"*";s:3:"foo";}' ),
+                               // BC Subelements
+                               array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                                       'a:1:{s:3:"foo";s:3:"foo";}' ),
+                       ) )
                );
        }
 
index 06e9204..b0a2a96 100644 (file)
@@ -16,8 +16,12 @@ class ApiFormatTxtTest extends ApiFormatTestBase {
                return array(
                        // Basic types
                        array( array( null ), "Array\n({$warning}\n    [0] => \n)\n" ),
-                       array( array( true ), "Array\n({$warning}\n    [0] => 1\n)\n" ),
-                       array( array( false ), "Array\n({$warning}\n    [0] => \n)\n" ),
+                       array( array( true ), "Array\n({$warning}\n    [0] => \n)\n" ),
+                       array( array( false ), "Array\n({$warning}\n)\n" ),
+                       array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "Array\n({$warning}\n    [0] => 1\n)\n" ),
+                       array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "Array\n({$warning}\n    [0] => \n)\n" ),
                        array( array( 42 ), "Array\n({$warning}\n    [0] => 42\n)\n" ),
                        array( array( 42.5 ), "Array\n({$warning}\n    [0] => 42.5\n)\n" ),
                        array( array( 1e42 ), "Array\n({$warning}\n    [0] => 1.0E+42\n)\n" ),
@@ -29,9 +33,22 @@ class ApiFormatTxtTest extends ApiFormatTestBase {
                        array( array( array( 1 ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [0] => 1\n        )\n\n)\n" ),
                        array( array( array( 'x' => 1 ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [x] => 1\n        )\n\n)\n" ),
                        array( array( array( 2 => 1 ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [2] => 1\n        )\n\n)\n" ),
+                       array( array( (object)array() ), "Array\n({$warning}\n    [0] => Array\n        (\n        )\n\n)\n" ),
+                       array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [0] => 1\n        )\n\n)\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [0] => 1\n        )\n\n)\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [x] => 1\n        )\n\n)\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                               "Array\n({$warning}\n    [0] => Array\n        (\n            [0] => Array\n                (\n                    [key] => x\n                    [*] => 1\n                )\n\n        )\n\n)\n" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [x] => 1\n        )\n\n)\n" ),
+                       array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "Array\n({$warning}\n    [0] => Array\n        (\n            [0] => a\n            [1] => b\n        )\n\n)\n" ),
 
                        // Content
-                       array( array( '*' => 'foo' ), "Array\n({$warning}\n    [*] => foo\n)\n" ),
+                       array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               "Array\n({$warning}\n    [*] => foo\n)\n" ),
+
+                       // BC Subelements
+                       array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                               "Array\n({$warning}\n    [foo] => Array\n        (\n            [*] => foo\n        )\n\n)\n" ),
                );
        }
 
index 81676e0..0711130 100644 (file)
@@ -24,8 +24,12 @@ class ApiFormatWddxTest extends ApiFormatTestBase {
                return array(
                        // Basic types
                        array( array( null ), "{$p}<var name='0'><null/></var>{$s}" ),
-                       array( array( true ), "{$p}<var name='0'><boolean value='true'/></var>{$s}" ),
-                       array( array( false ), "{$p}<var name='0'><boolean value='false'/></var>{$s}" ),
+                       array( array( true ), "{$p}<var name='0'><string></string></var>{$s}" ),
+                       array( array( false ), "{$p}{$s}" ),
+                       array( array( true, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "{$p}<var name='0'><boolean value='true'/></var>{$s}" ),
+                       array( array( false, ApiResult::META_BC_BOOLS => array( 0 ) ),
+                               "{$p}<var name='0'><boolean value='false'/></var>{$s}" ),
                        array( array( 42 ), "{$p}<var name='0'><number>42</number></var>{$s}" ),
                        array( array( 42.5 ), "{$p}<var name='0'><number>42.5</number></var>{$s}" ),
                        array( array( 1e42 ), "{$p}<var name='0'><number>1.0E+42</number></var>{$s}" ),
@@ -37,9 +41,22 @@ class ApiFormatWddxTest extends ApiFormatTestBase {
                        array( array( array( 1 ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ),
                        array( array( array( 'x' => 1 ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ),
                        array( array( array( 2 => 1 ) ), "{$p}<var name='0'><struct><var name='2'><number>1</number></var></struct></var>{$s}" ),
+                       array( array( (object)array() ), "{$p}<var name='0'><struct></struct></var>{$s}" ),
+                       array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), "{$p}<var name='0'><struct><var name='0'><number>1</number></var></struct></var>{$s}" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                               "{$p}<var name='0'><array length='1'><struct><var name='key'><string>x</string></var><var name='*'><number>1</number></var></struct></array></var>{$s}" ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ),
+                       array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), "{$p}<var name='0'><array length='2'><string>a</string><string>b</string></array></var>{$s}" ),
 
                        // Content
-                       array( array( '*' => 'foo' ), "{$p}<var name='*'><string>foo</string></var>{$s}" ),
+                       array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               "{$p}<var name='*'><string>foo</string></var>{$s}" ),
+
+                       // BC Subelements
+                       array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                               "{$p}<var name='foo'><struct><var name='*'><string>foo</string></var></struct></var>{$s}" ),
                );
        }
 
index afb47e7..0c31b95 100644 (file)
@@ -9,8 +9,8 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
 
        protected $printerName = 'xml';
 
-       protected function setUp() {
-               parent::setUp();
+       public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
                $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest.xsl' ) );
                $page->doEditContent( new WikitextContent(
                        '<?xml version="1.0"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />'
@@ -22,29 +22,79 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
        }
 
        public static function provideGeneralEncoding() {
-               $tests = array(
+               return array(
                        // Basic types
-                       array( array( null ), '<?xml version="1.0"?><api><x /></api>' ),
-                       array( array( true, 'a' => true ), '<?xml version="1.0"?><api a=""><x>1</x></api>' ),
-                       array( array( false, 'a' => false ), '<?xml version="1.0"?><api><x></x></api>' ),
-                       array( array( 42, 'a' => 42 ), '<?xml version="1.0"?><api a="42"><x>42</x></api>' ),
-                       array( array( 42.5, 'a' => 42.5 ), '<?xml version="1.0"?><api a="42.5"><x>42.5</x></api>' ),
-                       array( array( 1e42, 'a' => 1e42 ), '<?xml version="1.0"?><api a="1.0E+42"><x>1.0E+42</x></api>' ),
-                       array( array( 'foo', 'a' => 'foo' ), '<?xml version="1.0"?><api a="foo"><x>foo</x></api>' ),
-                       array( array( 'fóo', 'a' => 'fóo' ), '<?xml version="1.0"?><api a="fóo"><x>fóo</x></api>' ),
+                       array( array( null, 'a' => null ), '<?xml version="1.0"?><api><_v _idx="0" /></api>' ),
+                       array( array( true, 'a' => true ), '<?xml version="1.0"?><api a=""><_v _idx="0">true</_v></api>' ),
+                       array( array( false, 'a' => false ), '<?xml version="1.0"?><api><_v _idx="0">false</_v></api>' ),
+                       array( array( true, 'a' => true, ApiResult::META_BC_BOOLS => array( 0, 'a' ) ),
+                               '<?xml version="1.0"?><api a=""><_v _idx="0">1</_v></api>' ),
+                       array( array( false, 'a' => false, ApiResult::META_BC_BOOLS => array( 0, 'a' ) ),
+                               '<?xml version="1.0"?><api><_v _idx="0"></_v></api>' ),
+                       array( array( 42, 'a' => 42 ), '<?xml version="1.0"?><api a="42"><_v _idx="0">42</_v></api>' ),
+                       array( array( 42.5, 'a' => 42.5 ), '<?xml version="1.0"?><api a="42.5"><_v _idx="0">42.5</_v></api>' ),
+                       array( array( 1e42, 'a' => 1e42 ), '<?xml version="1.0"?><api a="1.0E+42"><_v _idx="0">1.0E+42</_v></api>' ),
+                       array( array( 'foo', 'a' => 'foo' ), '<?xml version="1.0"?><api a="foo"><_v _idx="0">foo</_v></api>' ),
+                       array( array( 'fóo', 'a' => 'fóo' ), '<?xml version="1.0"?><api a="fóo"><_v _idx="0">fóo</_v></api>' ),
 
                        // Arrays and objects
-                       array( array( array() ), '<?xml version="1.0"?><api><x /></api>' ),
-                       array( array( array( 'x' => 1 ) ), '<?xml version="1.0"?><api><x x="1" /></api>' ),
-                       array( array( array( 2 => 1, '_element' => 'x' ) ), '<?xml version="1.0"?><api><x><x>1</x></x></api>' ),
+                       array( array( array() ), '<?xml version="1.0"?><api><_v /></api>' ),
+                       array( array( array( 'x' => 1 ) ), '<?xml version="1.0"?><api><_v x="1" /></api>' ),
+                       array( array( array( 2 => 1 ) ), '<?xml version="1.0"?><api><_v><_v _idx="2">1</_v></_v></api>' ),
+                       array( array( (object)array() ), '<?xml version="1.0"?><api><_v /></api>' ),
+                       array( array( array( 1, ApiResult::META_TYPE => 'assoc' ) ), '<?xml version="1.0"?><api><_v><_v _idx="0">1</_v></_v></api>' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'array' ) ), '<?xml version="1.0"?><api><_v><_v>1</_v></_v></api>' ),
+                       array( array( array( 'x' => 1, 'y' => array( 'z' => 1 ), ApiResult::META_TYPE => 'kvp' ) ),
+                               '<?xml version="1.0"?><api><_v><_v _name="x" xml:space="preserve">1</_v><_v _name="y"><z xml:space="preserve">1</z></_v></_v></api>' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'kvp', ApiResult::META_INDEXED_TAG_NAME => 'i', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                               '<?xml version="1.0"?><api><_v><i key="x" xml:space="preserve">1</i></_v></api>' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCkvp', ApiResult::META_KVP_KEY_NAME => 'key' ) ),
+                               '<?xml version="1.0"?><api><_v><_v key="x" xml:space="preserve">1</_v></_v></api>' ),
+                       array( array( array( 'x' => 1, ApiResult::META_TYPE => 'BCarray' ) ), '<?xml version="1.0"?><api><_v x="1" /></api>' ),
+                       array( array( array( 'a', 'b', ApiResult::META_TYPE => 'BCassoc' ) ), '<?xml version="1.0"?><api><_v><_v _idx="0">a</_v><_v _idx="1">b</_v></_v></api>' ),
 
                        // Content
-                       array( array( '*' => 'foo' ), '<?xml version="1.0"?><api xml:space="preserve">foo</api>' ),
+                       array( array( 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               '<?xml version="1.0"?><api xml:space="preserve">foo</api>' ),
+
+                       // Specified element name
+                       array( array( 'foo', 'bar', ApiResult::META_INDEXED_TAG_NAME => 'itn' ),
+                               '<?xml version="1.0"?><api><itn>foo</itn><itn>bar</itn></api>' ),
 
                        // Subelements
                        array( array( 'a' => 1, 's' => 1, '_subelements' => array( 's' ) ),
                                '<?xml version="1.0"?><api a="1"><s xml:space="preserve">1</s></api>' ),
 
+                       // Content and subelement
+                       array( array( 'a' => 1, 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               '<?xml version="1.0"?><api a="1" xml:space="preserve">foo</api>' ),
+                       array( array( 's' => array(), 'content' => 'foo', ApiResult::META_CONTENT => 'content' ),
+                               '<?xml version="1.0"?><api><s /><content xml:space="preserve">foo</content></api>' ),
+                       array(
+                               array(
+                                       's' => 1,
+                                       'content' => 'foo',
+                                       ApiResult::META_CONTENT => 'content',
+                                       ApiResult::META_SUBELEMENTS => array( 's' )
+                               ),
+                               '<?xml version="1.0"?><api><s xml:space="preserve">1</s><content xml:space="preserve">foo</content></api>'
+                       ),
+
+                       // BC Subelements
+                       array( array( 'foo' => 'foo', ApiResult::META_BC_SUBELEMENTS => array( 'foo' ) ),
+                               '<?xml version="1.0"?><api><foo xml:space="preserve">foo</foo></api>' ),
+
+                       // Name mangling
+                       array( array( 'foo.bar' => 1 ), '<?xml version="1.0"?><api foo.bar="1" />' ),
+                       array( array( '' => 1 ), '<?xml version="1.0"?><api _="1" />' ),
+                       array( array( 'foo bar' => 1 ), '<?xml version="1.0"?><api _foo.20.bar="1" />' ),
+                       array( array( 'foo:bar' => 1 ), '<?xml version="1.0"?><api _foo.3A.bar="1" />' ),
+                       array( array( 'foo%.bar' => 1 ), '<?xml version="1.0"?><api _foo.25..2E.bar="1" />' ),
+                       array( array( '4foo' => 1, 'foo4' => 1 ), '<?xml version="1.0"?><api _4foo="1" foo4="1" />' ),
+                       array( array( "foo\xe3\x80\x80bar" => 1 ), '<?xml version="1.0"?><api _foo.3000.bar="1" />' ),
+                       array( array( 'foo:bar' => 1, ApiResult::META_PRESERVE_KEYS => array( 'foo:bar' ) ),
+                               '<?xml version="1.0"?><api foo:bar="1" />' ),
+
                        // includenamespace param
                        array( array( 'x' => 'foo' ), '<?xml version="1.0"?><api x="foo" xmlns="http://www.mediawiki.org/xml/api/" />',
                                array( 'includexmlnamespace' => 1 ) ),
@@ -62,40 +112,6 @@ class ApiFormatXmlTest extends ApiFormatTestBase {
                                        '" type="text/xsl" ?><api />',
                                array( 'xslt' => 'MediaWiki:ApiFormatXmlTest.xsl' ) ),
                );
-
-               // Add in the needed "_element" for all indexed arrays
-               $ret = array();
-               foreach ( $tests as $v ) {
-                       $v[0] += array( '_element' => 'x' );
-                       $ret[] = $v;
-               }
-               return $ret;
-       }
-
-       /**
-        * @dataProvider provideXmlFail
-        */
-       public function testXmlFail( array $data, $expect, array $params = array() ) {
-               try {
-                       echo $this->encodeData( $params, $data ) . "\n";
-                       $this->fail( "Expected exception not thrown" );
-               } catch ( MWException $ex ) {
-                       $this->assertSame( $expect, $ex->getMessage(), 'Expected exception' );
-               }
-       }
-
-       public static function provideXmlFail() {
-               return array(
-                       // Array without _element
-                       array( array( 1 ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName().' ),
-                       // Content and subelement
-                       array( array( 1, 's' => array(), '*' => 2, '_element' => 'x' ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
-                       array( array( 1, 's' => 1, '*' => 2, '_element' => 'x', '_subelements' => array( 's' ) ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
-                       // These should fail but don't because of a long-standing bug (see T57371#639713)
-                       //array( array( 1, '*' => 2, '_element' => 'x' ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
-                       //array( array( 's' => array(), '*' => 2 ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
-                       //array( array( 's' => 1, '*' => 2, '_subelements' => array( 's' ) ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
-               );
        }
 
 }
index 06951b7..1abb47e 100644 (file)
@@ -96,12 +96,11 @@ class MWDebugTest extends MediaWikiTestCase {
                $apiMain = new ApiMain( $context );
 
                $result = new ApiResult( $apiMain );
-               $result->setRawMode( true );
 
                MWDebug::appendDebugInfoToApiResult( $context, $result );
 
                $this->assertInstanceOf( 'ApiResult', $result );
-               $data = $result->getData();
+               $data = $result->getResultData();
 
                $expectedKeys = array( 'mwVersion', 'phpEngine', 'phpVersion', 'gitRevision', 'gitBranch',
                        'gitViewUrl', 'time', 'log', 'debugLog', 'queries', 'request', 'memory',
index 758cfe1..cc121ba 100644 (file)
@@ -109,9 +109,6 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
 
 class ResourceLoaderImageTestable extends ResourceLoaderImage {
        // Make some protected methods public
-       public function getPath( ResourceLoaderContext $context ) {
-               return parent::getPath( $context );
-       }
        public function massageSvgPathdata( $svg ) {
                return parent::massageSvgPathdata( $svg );
        }
index ec56b63..b749662 100644 (file)
@@ -35,7 +35,10 @@ class UploadFromUrlTest extends ApiTestCase {
 
                wfSetupSession( $sessionId );
 
-               return array( $module->getResultData(), $req );
+               return array(
+                       $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ),
+                       $req
+               );
        }
 
        /**
index a5ef762..7fc9997 100644 (file)
@@ -434,7 +434,10 @@ class TextPassDumperDatabaseTest extends DumpTestCase {
         * PHP extensions, we go for gzip instead, which triggers the same relevant code
         * paths while still being testable on more systems.
         *
+        * Broken per T70653.
+        *
         * @group large
+        * @group Broken
         */
        function testCheckpointGzip() {
                $this->checkHasGzip();
index 03aaf4a..3c44b55 100644 (file)
         * </code>
         */
        QUnit.newMwEnvironment = ( function () {
-               var warn, log, liveConfig, liveMessages;
+               var warn, log, liveConfig, liveMessages,
+                       ajaxRequests = [];
 
                liveConfig = mw.config.values;
                liveMessages = mw.messages.values;
                        return $.extend( /*deep=*/true, {}, liveMessages, custom );
                }
 
+               /**
+                * @param {jQuery.Event} event
+                * @param {jqXHR} jqXHR
+                * @param {Object} ajaxOptions
+                */
+               function trackAjax( event, jqXHR, ajaxOptions ) {
+                       ajaxRequests.push( { xhr: jqXHR, options: ajaxOptions } );
+               }
+
                log = QUnit.urlParams.mwlogenv ? mw.log : function () {};
 
                return function ( localEnv ) {
                                        this.suppressWarnings = suppressWarnings;
                                        this.restoreWarnings = restoreWarnings;
 
+                                       // Start tracking ajax requests
+                                       $( document ).on( 'ajaxSend', trackAjax );
+
                                        localEnv.setup.call( this );
                                },
 
                                teardown: function () {
-                                       var timers;
+                                       var timers, active;
                                        log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module
                                                + ': ' + QUnit.config.current.testName + '"' );
 
                                        localEnv.teardown.call( this );
 
+                                       // Stop tracking ajax requests
+                                       $( document ).off( 'ajaxSend', trackAjax );
+
                                        // Farewell, mock environment!
                                        mw.config.values = liveConfig;
                                        mw.messages.values = liveMessages;
                                        // still suppressed by the end of the test.
                                        restoreWarnings();
 
-                                       // Check for incomplete animations/requests/etc and throw
-                                       // error if there are any.
+                                       // Tests should use fake timers or wait for animations to complete
+                                       // Check for incomplete animations/requests/etc and throw if there are any.
                                        if ( $.timers && $.timers.length !== 0 ) {
                                                timers = $.timers.length;
-                                               // Tests shoulld use fake timers or wait for animations to complete
                                                $.each( $.timers, function ( i, timer ) {
                                                        var node = timer.elem;
                                                        mw.log.warn( 'Unfinished animation #' + i + ' in ' + timer.queue + ' queue on ' +
 
                                                throw new Error( 'Unfinished animations: ' + timers );
                                        }
+
+                                       // Test should use fake XHR, wait for requests, or call abort()
                                        if ( $.active !== undefined && $.active !== 0 ) {
-                                               // Test may need to use fake XHR, wait for requests or
-                                               // call abort().
-                                               throw new Error( 'Unfinished AJAX requests: ' + $.active );
+                                               active = $.grep( ajaxRequests, function ( ajax ) {
+                                                       return ajax.xhr.state() === 'pending';
+                                               } );
+                                               if ( active.length !== $.active ) {
+                                                       mw.log.warn( 'Pending requests does not match jQuery.active count' );
+                                               }
+                                               // Force requests to stop to give the next test a clean start
+                                               $.each( active, function ( i, ajax ) {
+                                                       mw.log.warn( 'Unfinished AJAX request #' + i, ajax.options );
+                                                       ajax.xhr.abort();
+                                               } );
+                                               ajaxRequests = [];
+
+                                               throw new Error( 'Unfinished AJAX requests: ' + active.length );
                                        }
                                }
                        };
index 7e23e2f..5dcd941 100644 (file)
        /**
         * @param {Function[]} tasks List of functions that perform tasks
         *  that may be asynchronous. Invoke the callback parameter when done.
-        * @param {Function} done When all tasks are done.
+        * @param {Function} complete Called when all tasks are done, or when the sequence is aborted.
         * @return
         */
-       function process( tasks, done ) {
-               function run() {
+       function process( tasks, complete ) {
+               function abort() {
+                       tasks.splice( 0, tasks.length );
+                       next();
+               }
+               function next() {
+                       if ( !tasks ) {
+                               // This happens if after the process is completed, one of our callbacks is
+                               // invoked. This can happen if a test timed out but the process was still
+                               // running. In that case, ignore it. Don't invoke complete() a second time.
+                               return;
+                       }
                        var task = tasks.shift();
                        if ( task ) {
-                               task( run );
+                               task( next, abort );
                        } else {
-                               done();
+                               // Remove tasks list to indicate the process is final.
+                               tasks = null;
+                               complete();
                        }
                }
-               run();
+               next();
        }
 
        QUnit.test( 'Replace', 16, function ( assert ) {
        QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
                mw.messages.set( mw.libs.phpParserData.messages );
                var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) {
-                       return function ( next ) {
+                       return function ( next, abort ) {
                                getMwLanguage( test.lang )
-                                       .done( function ( langClass ) {
+                                       .then( function ( langClass ) {
                                                mw.config.set( 'wgUserLanguage', test.lang );
                                                var parser = new mw.jqueryMsg.parser( { language: langClass } );
                                                assert.equal(
                                                        test.result,
                                                        test.name
                                                );
-                                       } )
-                                       .fail( function () {
+                                       }, function () {
                                                assert.ok( false, 'Language "' + test.lang + '" failed to load.' );
                                        } )
-                                       .always( next );
+                                       .then( next, abort );
                        };
                } );
 
                mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' );
                mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' );
                var queue = $.map( formatnumTests, function ( test ) {
-                       return function ( next ) {
+                       return function ( next, abort ) {
                                getMwLanguage( test.lang )
-                                       .done( function ( langClass ) {
+                                       .then( function ( langClass ) {
                                                mw.config.set( 'wgUserLanguage', test.lang );
                                                var parser = new mw.jqueryMsg.parser( { language: langClass } );
                                                assert.equal(
                                                        test.result,
                                                        test.description
                                                );
-                                       } )
-                                       .fail( function () {
+                                       }, function () {
                                                assert.ok( false, 'Language "' + test.lang + '" failed to load' );
                                        } )
-                                       .always( next );
+                                       .then( next, abort );
                        };
                } );
                QUnit.stop();