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.
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
* 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:
'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',
'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',
'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',
'ApiTokens' => __DIR__ . '/includes/api/ApiTokens.php',
'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',
}
/**
- * 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();
}
/**
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 );
+ }
+
/**@}*/
/************************************************************************//**
$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(
* @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 );
}
/**
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();
+ }
+
/**@}*/
}
);
}
- ApiResult::setContent( $vals, $difftext );
+ ApiResult::setContentValue( $vals, 'body', $difftext );
$this->getResult()->addValue( null, $this->getModuleName(), $vals );
}
--- /dev/null
+<?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 );
+ }
+ }
+}
$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 {
$titleObj = $newTitle;
}
- $apiResult->setIndexedTagName( $redirValues, 'r' );
+ ApiResult::setIndexedTagName( $redirValues, 'r' );
$apiResult->addValue( null, 'redirects', $redirValues );
// Since the page changed, update $pageObj
--- /dev/null
+<?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 );
+ }
+ }
+}
} else {
// the old way
$xml_result = array();
- ApiResult::setContent( $xml_result, $xml );
+ ApiResult::setContentValue( $xml_result, 'xml', $xml );
$result->addValue( null, 'parsetree', $xml_result );
}
}
$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();
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;
}
}
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;
}
}
}
}
}
- $result->setSubelements( $retval, array( 'wikitext', 'parsetree' ) );
+ ApiResult::setSubelementsList( $retval, array( 'wikitext', 'parsetree' ) );
$result->addValue( null, $this->getModuleName(), $retval );
}
$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;
} else {
$result = array(
'result' => 'Failure',
- 'errors' => $this->getResult()->convertStatusToArray( $status ),
+ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $status ),
);
}
*/
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
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;
+ }
+
/**@}*/
}
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() {
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 );
// 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 );
}
/**
return;
}
- $data = $this->getResultData();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
$data['_feed']->httpHeaders();
} else {
* $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'];
*/
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() {
return 'application/json';
}
+ /**
+ * @deprecated since 1.25
+ */
public function getNeedsRawData() {
- return $this->mIsRaw;
+ return $this->isRaw;
}
/**
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
}
public function getAllowedParams() {
- return array(
+ if ( $this->isRaw ) {
+ return array();
+ }
+
+ $ret = array(
'callback' => array(
ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-callback',
),
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;
}
}
}
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
$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;
+ }
}
}
public function getMimeType() {
- $data = $this->getResultData();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['error'] ) ) {
return $this->errorFallback->getMimeType();
}
public function initPrinter( $unused = false ) {
- $data = $this->getResultData();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['error'] ) ) {
$this->errorFallback->initPrinter( $unused );
} else {
}
public function closePrinter() {
- $data = $this->getResultData();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['error'] ) ) {
$this->errorFallback->closePrinter();
} else {
}
public function execute() {
- $data = $this->getResultData();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['error'] ) ) {
$this->errorFallback->execute();
return;
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() {
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
$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" );
}
$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
return 'text/xml';
}
+ /**
+ * @deprecated since 1.25
+ */
public function getNeedsRawData() {
return true;
}
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
)
/**
* 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() ) {
'mime' => 'text/html',
'help' => $html,
);
- $result->setSubelements( $data, 'help' );
+ ApiResult::setSubelementsList( $data, 'help' );
$result->addValue( null, $this->getModuleName(), $data );
} else {
$result->reset();
$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();
$r['result'] = 'Success';
} else {
$r['result'] = 'Failure';
- $r['errormessage'] = $this->getResult()->convertStatusToArray( $status );
+ $r['errormessage'] = $this->getErrorFormatter()->arrayFromStatus( $status );
}
} else {
$r['result'] = 'Failure';
$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 );
}
/**
$resultData = $reporter->getData();
$result = $this->getResult();
- $result->setIndexedTagName( $resultData, 'page' );
+ ApiResult::setIndexedTagName( $resultData, 'page' );
$result->addValue( null, $this->getModuleName(), $resultData );
}
*/
private $mPrinter;
- private $mModuleMgr, $mResult;
+ private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager;
private $mAction;
private $mEnableWrite;
private $mInternalMode, $mSquidMaxage, $mModule;
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()
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()
*
// 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' ) ) {
'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
$this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
}
- $this->getResult()->cleanUpUTF8();
$printer = $this->mPrinter;
-
$printer->initPrinter( false );
$printer->execute();
$printer->closePrinter();
'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'] = '';
--- /dev/null
+<?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;
+ }
+}
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' );
}
}
);
$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;
$values[] = $r;
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'r' );
+ ApiResult::setIndexedTagName( $values, 'r' );
}
return $values;
);
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'n' );
+ ApiResult::setIndexedTagName( $values, 'n' );
}
return $values;
);
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'c' );
+ ApiResult::setIndexedTagName( $values, 'c' );
}
return $values;
$values[] = $item;
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'i' );
+ ApiResult::setIndexedTagName( $values, 'i' );
}
return $values;
);
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'rev' );
+ ApiResult::setIndexedTagName( $values, 'rev' );
}
return $values;
*/
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 ) {
$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'] ) {
}
$res[$key][] = $a;
}
- $this->getResult()->setIndexedTagName( $res[$key], 'msg' );
+ ApiResult::setIndexedTagName( $res[$key], 'msg' );
break;
}
}
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();
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();
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] ) ) {
);
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}" )
->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;
}
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
$reqParams = array(
- 'action' => 'query',
'redirects' => '',
);
if ( !is_null( $pageid ) ) {
}
$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 ) ) {
// 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 );
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'] ) ) {
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() );
if ( isset( $prop['headhtml'] ) ) {
$result_array['headhtml'] = array();
- ApiResult::setContent(
+ ApiResult::setContentValue(
$result_array['headhtml'],
+ 'headhtml',
$context->getOutput()->headElement( $context->getSkin() )
);
}
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;
}
}
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'] ) ) {
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'] ) {
$xml = $dom->__toString();
}
$result_array['parsetree'] = array();
- ApiResult::setContent( $result_array['parsetree'], $xml );
+ ApiResult::setContentValue( $result_array['parsetree'], 'parsetree', $xml );
}
$result_mapping = array(
// native language name
$entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
}
- ApiResult::setContent( $entry, $bits[1] );
+ ApiResult::setContentValue( $entry, 'title', $bits[1] );
$result[] = $entry;
}
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] ) {
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'] = '';
}
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
- ApiResult::setContent( $entry, $title->getFullText() );
+ ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
$result[] = $entry;
}
}
foreach ( $headItems as $tag => $content ) {
$entry = array();
$entry['tag'] = $tag;
- ApiResult::setContent( $entry, $content );
+ ApiResult::setContentValue( $entry, 'content', $content );
$result[] = $entry;
}
foreach ( $properties as $name => $value ) {
$entry = array();
$entry['name'] = $name;
- ApiResult::setContent( $entry, $value );
+ ApiResult::setContentValue( $entry, 'value', $value );
$result[] = $entry;
}
foreach ( $css as $file => $link ) {
$entry = array();
$entry['file'] = $file;
- ApiResult::setContent( $entry, $link );
+ ApiResult::setContentValue( $entry, 'link', $link );
$result[] = $entry;
}
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;
}
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 );
}
}
}
}
$res['protections'] = $resultProtections;
$result = $this->getResult();
- $result->setIndexedTagName( $res['protections'], 'protection' );
+ ApiResult::setIndexedTagName( $res['protections'], 'protection' );
$result->addValue( null, $this->getModuleName(), $res );
}
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'];
$result[] = $r;
}
$apiResult = $this->getResult();
- $apiResult->setIndexedTagName( $result, 'page' );
+ ApiResult::setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
$values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
$apiResult->addValue( null, 'redirects', $values );
}
- $apiResult->endContinuation();
+ $this->setContinuationManager( null );
+ $continuationManager->setContinuationIntoResult( $apiResult );
}
/**
$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 ) {
$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(
$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 );
}
*/
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;
}
$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 );
}
}
$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;
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'c' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'c' );
} else {
$resultPageSet->populateFromTitles( $pages );
}
'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 {
$resultPageSet->populateFromRevisionIDs( $generated );
}
} else {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
}
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'img' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'img' );
} else {
$resultPageSet->populateFromTitles( $titles );
}
}
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 {
$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 );
}
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'message' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'message' );
}
public function getCacheMode( $params ) {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'p' );
}
}
// 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;
}
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' );
}
}
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'u' );
}
public function getCacheMode( $params ) {
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 );
$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 ) );
$hasRedirs = true;
}
if ( $hasRedirs ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName(), $idx, 'redirlinks' ),
$this->bl_code );
}
}
}
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ),
$this->bl_code
);
*/
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(),
if ( !$fit ) {
return false;
}
- $result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
+ $result->addIndexedTagName( array( 'query', 'pages', $pageId,
$this->getModuleName() ), $elemname );
return true;
* @param string|array $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
- $this->getResult()->setContinueParam( $this, $paramName, $paramValue );
+ $this->getContinuationManager()->addContinueParam( $this, $paramName, $paramValue );
}
/**
*/
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 );
}
break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'block' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'block' );
}
protected function prepareUsername( $user ) {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ), 'cm' );
}
}
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 );
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 ) );
}
}
}
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();
$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 ) {
break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
}
public function isDeprecated() {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ),
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ),
$this->getModulePrefix() );
}
}
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 );
$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 );
}
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'fa' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'fa' );
}
public function getAllowedParams() {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'iw' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'iw' );
} else {
$resultPageSet->populateFromTitles( $pages );
}
}
}
- ApiResult::setContent( $entry, $row->iwl_title );
+ ApiResult::setContentValue( $entry, 'title', $row->iwl_title );
$fit = $this->addPageSubItem( $row->iwl_from, $entry );
if ( !$fit ) {
$this->setContinueEnumParameter(
$retval[] = $r;
}
}
- $result->setIndexedTagName( $retval, 'metadata' );
+ ApiResult::setIndexedTagName( $retval, 'metadata' );
return $retval;
}
$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] ) ) {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'll' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'll' );
} else {
$resultPageSet->populateFromTitles( $pages );
}
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}" );
break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'item' );
}
/**
$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 );
}
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();
* @param array $serializedResults
*/
protected function setIndexedTagNames( array &$serializedResults ) {
- $this->getResult()->setIndexedTagName( $serializedResults, $this->getRowName() );
+ ApiResult::setIndexedTagName( $serializedResults, $this->getRowName() );
}
/**
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'p' );
}
public function getAllowedParams() {
}
if ( $resultPageSet === null ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
}
}
break;
}
}
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ), $this->getModulePrefix()
);
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ),
$this->getModulePrefix()
);
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName(), 'results' ),
'page'
);
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
}
}
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 );
}
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();
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 );
}
}
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();
}
if ( $text !== false ) {
- ApiResult::setContent( $vals, $text );
+ ApiResult::setContentValue( $vals, 'content', $text );
}
}
}
if ( $engine ) {
$difftext = $engine->getDiffBody();
- ApiResult::setContent( $vals['diff'], $difftext );
+ ApiResult::setContentValue( $vals['diff'], 'body', $difftext );
if ( !$engine->wasCacheHit() ) {
$n++;
}
}
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' );
}
}
if ( $allowException ) {
$data['externalimages'] = (array)$allowFrom;
- $this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' );
+ ApiResult::setIndexedTagName( $data['externalimages'], 'prefix' );
}
if ( !$config->get( 'DisableLangConversion' ) ) {
$fallbacks[] = array( 'code' => $code );
}
$data['fallback'] = $fallbacks;
- $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
+ ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
if ( $wgContLang->hasVariants() ) {
$variants = array();
);
}
$data['variants'] = $variants;
- $this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
+ ApiResult::setIndexedTagName( $data['variants'], 'lang' );
}
if ( $wgContLang->isRTL() ) {
$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] );
}
'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 ) ) {
}
}
- $this->getResult()->setIndexedTagName( $data, 'ns' );
+ ApiResult::setIndexedTagName( $data, 'ns' );
return $this->getResult()->addValue( 'query', $property, $data );
}
$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 );
}
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 );
}
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 );
}
$data[] = $val;
}
- $this->getResult()->setIndexedTagName( $data, 'iw' );
+ ApiResult::setIndexedTagName( $data, 'iw' );
return $this->getResult()->addValue( 'query', $property, $data );
}
}
$result = $this->getResult();
- $result->setIndexedTagName( $data, 'db' );
+ ApiResult::setIndexedTagName( $data, 'db' );
return $this->getResult()->addValue( 'query', $property, $data );
}
$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 );
}
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 );
}
'version' => $info['version'],
);
}
- $this->getResult()->setIndexedTagName( $data, 'library' );
+ ApiResult::setIndexedTagName( $data, 'library' );
return $this->getResult()->addValue( 'query', $property, $data );
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'];
}
}
}
- $this->getResult()->setIndexedTagName( $data, 'ext' );
+ ApiResult::setIndexedTagName( $data, 'ext' );
return $this->getResult()->addValue( 'query', $property, $data );
}
'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 );
}
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 );
}
$displayName = $msg->text();
}
$skin = array( 'code' => $name );
- ApiResult::setContent( $skin, $displayName );
+ ApiResult::setContentValue( $skin, 'name', $displayName );
if ( !isset( $allowed[$name] ) ) {
$skin['unusable'] = '';
}
}
$data[] = $skin;
}
- $this->getResult()->setIndexedTagName( $data, 'skin' );
+ ApiResult::setIndexedTagName( $data, 'skin' );
return $this->getResult()->addValue( 'query', $property, $data );
}
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 );
}
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 );
}
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 );
}
'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 );
}
$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 ) {
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'tag' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'tag' );
}
public function getCacheMode( $params ) {
}
}
- $this->getResult()->setIndexedTagName_internal(
+ $this->getResult()->addIndexedTagName(
array( 'query', $this->getModuleName() ),
'item'
);
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();
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'] ) ) {
$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;
}
}
} 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' );
}
}
}
$done[] = $u;
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'user' );
}
public function getCacheMode( $params ) {
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal(
+ $this->getResult()->addIndexedTagName(
array( 'query', $this->getModuleName() ),
'item'
);
}
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal( $this->getModuleName(), 'wr' );
+ $this->getResult()->addIndexedTagName( $this->getModuleName(), 'wr' );
} else {
$resultPageSet->populateFromTitles( $titles );
}
<?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] );
$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
+ */
// @codingStandardsIgnoreEnd
$data['items'] = array_values( $data['items'] );
- $result->setIndexedTagName( $data['items'], 'i' );
+ ApiResult::setIndexedTagName( $data['items'], 'i' );
$result->addValue( null, $this->getModuleName(), $data );
}
);
$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;
}
$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'] );
}
}
$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 );
}
);
$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 ) {
$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;
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 );
+ }
}
--- /dev/null
+<?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();
+}
$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 ) {
}
}
- $apiResult->setIndexedTagName( $result, 'page' );
+ ApiResult::setIndexedTagName( $result, 'page' );
}
$apiResult->addValue( null, $this->getModuleName(), $result );
- $apiResult->endContinuation();
+ $this->setContinuationManager( null );
+ $continuationManager->setContinuationIntoResult( $apiResult );
}
/**
'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;
if ( $warnings ) {
// Add indices
$result = $this->getResult();
- $result->setIndexedTagName( $warnings, 'warning' );
+ ApiResult::setIndexedTagName( $warnings, 'warning' );
if ( isset( $warnings['duplicate'] ) ) {
$dupes = array();
foreach ( $warnings['duplicate'] as $dupe ) {
$dupes[] = $dupe->getName();
}
- $result->setIndexedTagName( $dupes, 'duplicate' );
+ ApiResult::setIndexedTagName( $dupes, 'duplicate' );
$warnings['duplicate'] = $dupes;
}
);
}
- $this->getResult()->setIndexedTagName( $error, 'error' );
+ ApiResult::setIndexedTagName( $error, 'error' );
$this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
}
$result['result'] = 'Success';
);
$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 );
}
$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.
$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 ) {
$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,
"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.",
"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}}}}",
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 );
}
--- /dev/null
+<?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'
+ );
+ }
+
+ }
+
+}
--- /dev/null
+<?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'
+ );
+ }
+
+}
new FauxRequest( array( 'action' => 'query', 'meta' => 'siteinfo' ) )
);
$api->execute();
- $data = $api->getResultData();
+ $data = $api->getResult()->getResultData();
$this->assertInternalType( 'array', $data );
$this->assertArrayHasKey( 'query', $data );
}
--- /dev/null
+<?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 ) );
+ }
+
+}
$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' ) );
}
/**
'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 );
'options' => 'success',
'warnings' => array(
'options' => array(
- '*' => "Validation error for 'unknownOption': not a valid preference"
+ 'warnings' => "Validation error for 'unknownOption': not a valid preference"
)
)
), $response );
--- /dev/null
+<?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;
+ }
+}
// construct result
$results = array(
- $module->getResultData(),
+ $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ),
$context->getRequest(),
$context->getRequest()->getSessionArray()
);
public function execute() {
}
- public function getVersion() {
- }
-
public function __construct() {
}
<?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;
}
}
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)" ),
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)" ),
);
}
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" ),
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" ),
);
}
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"]' ),
+ ) )
);
}
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' ) ), '' ),
);
}
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";}' ),
+ ) )
);
}
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" ),
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" ),
);
}
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}" ),
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}" ),
);
}
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" />'
}
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 ) ),
'" 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' ),
- );
}
}
$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',
wfSetupSession( $sessionId );
- return array( $module->getResultData(), $req );
+ return array(
+ $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ),
+ $req
+ );
}
/**