MediaWiki  REL1_22
ApiBase.php
Go to the documentation of this file.
00001 <?php
00042 abstract class ApiBase extends ContextSource {
00043 
00044     // These constants allow modules to specify exactly how to treat incoming parameters.
00045 
00046     const PARAM_DFLT = 0; // Default value of the parameter
00047     const PARAM_ISMULTI = 1; // Boolean, do we accept more than one item for this parameter (e.g.: titles)?
00048     const PARAM_TYPE = 2; // Can be either a string type (e.g.: 'integer') or an array of allowed values
00049     const PARAM_MAX = 3; // Max value allowed for a parameter. Only applies if TYPE='integer'
00050     const PARAM_MAX2 = 4; // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer'
00051     const PARAM_MIN = 5; // Lowest value allowed for a parameter. Only applies if TYPE='integer'
00052     const PARAM_ALLOW_DUPLICATES = 6; // Boolean, do we allow the same value to be set more than once when ISMULTI=true
00053     const PARAM_DEPRECATED = 7; // Boolean, is the parameter deprecated (will show a warning)
00055     const PARAM_REQUIRED = 8; // Boolean, is the parameter required?
00057     const PARAM_RANGE_ENFORCE = 9; // Boolean, if MIN/MAX are set, enforce (die) these? Only applies if TYPE='integer' Use with extreme caution
00058 
00059     const PROP_ROOT = 'ROOT'; // Name of property group that is on the root element of the result, i.e. not part of a list
00060     const PROP_LIST = 'LIST'; // Boolean, is the result multiple items? Defaults to true for query modules, to false for other modules
00061     const PROP_TYPE = 0; // Type of the property, uses same format as PARAM_TYPE
00062     const PROP_NULLABLE = 1; // Boolean, can the property be not included in the result? Defaults to false
00063 
00064     const LIMIT_BIG1 = 500; // Fast query, std user limit
00065     const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
00066     const LIMIT_SML1 = 50; // Slow query, std user limit
00067     const LIMIT_SML2 = 500; // Slow query, bot/sysop limit
00068 
00074     const GET_VALUES_FOR_HELP = 1;
00075 
00076     private $mMainModule, $mModuleName, $mModulePrefix;
00077     private $mSlaveDB = null;
00078     private $mParamCache = array();
00079 
00086     public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
00087         $this->mMainModule = $mainModule;
00088         $this->mModuleName = $moduleName;
00089         $this->mModulePrefix = $modulePrefix;
00090 
00091         if ( !$this->isMain() ) {
00092             $this->setContext( $mainModule->getContext() );
00093         }
00094     }
00095 
00096     /*****************************************************************************
00097      * ABSTRACT METHODS                                                          *
00098      *****************************************************************************/
00099 
00116     abstract public function execute();
00117 
00125     public function getVersion() {
00126         wfDeprecated( __METHOD__, '1.21' );
00127         return '';
00128     }
00129 
00134     public function getModuleName() {
00135         return $this->mModuleName;
00136     }
00137 
00143     public function getModuleManager() {
00144         return null;
00145     }
00146 
00151     public function getModulePrefix() {
00152         return $this->mModulePrefix;
00153     }
00154 
00162     public function getModuleProfileName( $db = false ) {
00163         if ( $db ) {
00164             return 'API:' . $this->mModuleName . '-DB';
00165         } else {
00166             return 'API:' . $this->mModuleName;
00167         }
00168     }
00169 
00174     public function getMain() {
00175         return $this->mMainModule;
00176     }
00177 
00183     public function isMain() {
00184         return $this === $this->mMainModule;
00185     }
00186 
00191     public function getResult() {
00192         // Main module has getResult() method overridden
00193         // Safety - avoid infinite loop:
00194         if ( $this->isMain() ) {
00195             ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
00196         }
00197         return $this->getMain()->getResult();
00198     }
00199 
00204     public function getResultData() {
00205         return $this->getResult()->getData();
00206     }
00207 
00217     public function createContext() {
00218         wfDeprecated( __METHOD__, '1.19' );
00219         return new DerivativeContext( $this->getContext() );
00220     }
00221 
00229     public function setWarning( $warning ) {
00230         $result = $this->getResult();
00231         $data = $result->getData();
00232         $moduleName = $this->getModuleName();
00233         if ( isset( $data['warnings'][$moduleName] ) ) {
00234             // Don't add duplicate warnings
00235             $oldWarning = $data['warnings'][$moduleName]['*'];
00236             $warnPos = strpos( $oldWarning, $warning );
00237             // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
00238             if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
00239                 // Check if $warning is followed by "\n" or the end of the $oldWarning
00240                 $warnPos += strlen( $warning );
00241                 if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
00242                     return;
00243                 }
00244             }
00245             // If there is a warning already, append it to the existing one
00246             $warning = "$oldWarning\n$warning";
00247         }
00248         $msg = array();
00249         ApiResult::setContent( $msg, $warning );
00250         $result->disableSizeCheck();
00251         $result->addValue( 'warnings', $moduleName,
00252             $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
00253         $result->enableSizeCheck();
00254     }
00255 
00262     public function getCustomPrinter() {
00263         return null;
00264     }
00265 
00270     public function makeHelpMsg() {
00271         static $lnPrfx = "\n  ";
00272 
00273         $msg = $this->getFinalDescription();
00274 
00275         if ( $msg !== false ) {
00276 
00277             if ( !is_array( $msg ) ) {
00278                 $msg = array(
00279                     $msg
00280                 );
00281             }
00282             $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
00283 
00284             $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
00285 
00286             if ( $this->isReadMode() ) {
00287                 $msg .= "\nThis module requires read rights";
00288             }
00289             if ( $this->isWriteMode() ) {
00290                 $msg .= "\nThis module requires write rights";
00291             }
00292             if ( $this->mustBePosted() ) {
00293                 $msg .= "\nThis module only accepts POST requests";
00294             }
00295             if ( $this->isReadMode() || $this->isWriteMode() ||
00296                     $this->mustBePosted() ) {
00297                 $msg .= "\n";
00298             }
00299 
00300             // Parameters
00301             $paramsMsg = $this->makeHelpMsgParameters();
00302             if ( $paramsMsg !== false ) {
00303                 $msg .= "Parameters:\n$paramsMsg";
00304             }
00305 
00306             $examples = $this->getExamples();
00307             if ( $examples ) {
00308                 if ( !is_array( $examples ) ) {
00309                     $examples = array(
00310                         $examples
00311                     );
00312                 }
00313                 $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
00314                 foreach ( $examples as $k => $v ) {
00315                     if ( is_numeric( $k ) ) {
00316                         $msg .= "  $v\n";
00317                     } else {
00318                         if ( is_array( $v ) ) {
00319                             $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
00320                         } else {
00321                             $msgExample = "  $v";
00322                         }
00323                         $msgExample .= ":";
00324                         $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
00325                     }
00326                 }
00327             }
00328         }
00329 
00330         return $msg;
00331     }
00332 
00337     private function indentExampleText( $item ) {
00338         return "  " . $item;
00339     }
00340 
00347     protected function makeHelpArrayToString( $prefix, $title, $input ) {
00348         if ( $input === false ) {
00349             return '';
00350         }
00351         if ( !is_array( $input ) ) {
00352             $input = array( $input );
00353         }
00354 
00355         if ( count( $input ) > 0 ) {
00356             if ( $title ) {
00357                 $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
00358             } else {
00359                 $msg = '  ';
00360             }
00361             $msg .= implode( $prefix, $input ) . "\n";
00362             return $msg;
00363         }
00364         return '';
00365     }
00366 
00372     public function makeHelpMsgParameters() {
00373         $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
00374         if ( $params ) {
00375 
00376             $paramsDescription = $this->getFinalParamDescription();
00377             $msg = '';
00378             $paramPrefix = "\n" . str_repeat( ' ', 24 );
00379             $descWordwrap = "\n" . str_repeat( ' ', 28 );
00380             foreach ( $params as $paramName => $paramSettings ) {
00381                 $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
00382                 if ( is_array( $desc ) ) {
00383                     $desc = implode( $paramPrefix, $desc );
00384                 }
00385 
00386                 //handle shorthand
00387                 if ( !is_array( $paramSettings ) ) {
00388                     $paramSettings = array(
00389                         self::PARAM_DFLT => $paramSettings,
00390                     );
00391                 }
00392 
00393                 //handle missing type
00394                 if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
00395                     $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] ) ? $paramSettings[ApiBase::PARAM_DFLT] : null;
00396                     if ( is_bool( $dflt ) ) {
00397                         $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
00398                     } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
00399                         $paramSettings[ApiBase::PARAM_TYPE] = 'string';
00400                     } elseif ( is_int( $dflt ) ) {
00401                         $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
00402                     }
00403                 }
00404 
00405                 if ( isset( $paramSettings[self::PARAM_DEPRECATED] ) && $paramSettings[self::PARAM_DEPRECATED] ) {
00406                     $desc = "DEPRECATED! $desc";
00407                 }
00408 
00409                 if ( isset( $paramSettings[self::PARAM_REQUIRED] ) && $paramSettings[self::PARAM_REQUIRED] ) {
00410                     $desc .= $paramPrefix . "This parameter is required";
00411                 }
00412 
00413                 $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
00414                 if ( isset( $type ) ) {
00415                     $hintPipeSeparated = true;
00416                     $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
00417                     if ( $multi ) {
00418                         $prompt = 'Values (separate with \'|\'): ';
00419                     } else {
00420                         $prompt = 'One value: ';
00421                     }
00422 
00423                     if ( is_array( $type ) ) {
00424                         $choices = array();
00425                         $nothingPrompt = '';
00426                         foreach ( $type as $t ) {
00427                             if ( $t === '' ) {
00428                                 $nothingPrompt = 'Can be empty, or ';
00429                             } else {
00430                                 $choices[] = $t;
00431                             }
00432                         }
00433                         $desc .= $paramPrefix . $nothingPrompt . $prompt;
00434                         $choicesstring = implode( ', ', $choices );
00435                         $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
00436                         $hintPipeSeparated = false;
00437                     } else {
00438                         switch ( $type ) {
00439                             case 'namespace':
00440                                 // Special handling because namespaces are type-limited, yet they are not given
00441                                 $desc .= $paramPrefix . $prompt;
00442                                 $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
00443                                     100, $descWordwrap );
00444                                 $hintPipeSeparated = false;
00445                                 break;
00446                             case 'limit':
00447                                 $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
00448                                 if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
00449                                     $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
00450                                 }
00451                                 $desc .= ' allowed';
00452                                 break;
00453                             case 'integer':
00454                                 $s = $multi ? 's' : '';
00455                                 $hasMin = isset( $paramSettings[self::PARAM_MIN] );
00456                                 $hasMax = isset( $paramSettings[self::PARAM_MAX] );
00457                                 if ( $hasMin || $hasMax ) {
00458                                     if ( !$hasMax ) {
00459                                         $intRangeStr = "The value$s must be no less than {$paramSettings[self::PARAM_MIN]}";
00460                                     } elseif ( !$hasMin ) {
00461                                         $intRangeStr = "The value$s must be no more than {$paramSettings[self::PARAM_MAX]}";
00462                                     } else {
00463                                         $intRangeStr = "The value$s must be between {$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
00464                                     }
00465 
00466                                     $desc .= $paramPrefix . $intRangeStr;
00467                                 }
00468                                 break;
00469                             case 'upload':
00470                                 $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
00471                                 break;
00472                         }
00473                     }
00474 
00475                     if ( $multi ) {
00476                         if ( $hintPipeSeparated ) {
00477                             $desc .= $paramPrefix . "Separate values with '|'";
00478                         }
00479 
00480                         $isArray = is_array( $type );
00481                         if ( !$isArray
00482                                 || $isArray && count( $type ) > self::LIMIT_SML1 ) {
00483                             $desc .= $paramPrefix . "Maximum number of values " .
00484                                     self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
00485                         }
00486                     }
00487                 }
00488 
00489                 $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
00490                 if ( !is_null( $default ) && $default !== false ) {
00491                     $desc .= $paramPrefix . "Default: $default";
00492                 }
00493 
00494                 $msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
00495             }
00496             return $msg;
00497 
00498         } else {
00499             return false;
00500         }
00501     }
00502 
00507     protected function getDescription() {
00508         return false;
00509     }
00510 
00515     protected function getExamples() {
00516         return false;
00517     }
00518 
00531     protected function getAllowedParams( /* $flags = 0 */ ) {
00532         // int $flags is not declared because it causes "Strict standards"
00533         // warning. Most derived classes do not implement it.
00534         return false;
00535     }
00536 
00543     protected function getParamDescription() {
00544         return false;
00545     }
00546 
00555     public function getFinalParams( $flags = 0 ) {
00556         $params = $this->getAllowedParams( $flags );
00557         wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
00558         return $params;
00559     }
00560 
00567     public function getFinalParamDescription() {
00568         $desc = $this->getParamDescription();
00569         wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
00570         return $desc;
00571     }
00572 
00589     protected function getResultProperties() {
00590         return false;
00591     }
00592 
00599     public function getFinalResultProperties() {
00600         $properties = $this->getResultProperties();
00601         wfRunHooks( 'APIGetResultProperties', array( $this, &$properties ) );
00602         return $properties;
00603     }
00604 
00609     protected static function addTokenProperties( &$props, $tokenFunctions ) {
00610         foreach ( array_keys( $tokenFunctions ) as $token ) {
00611             $props[''][$token . 'token'] = array(
00612                 ApiBase::PROP_TYPE => 'string',
00613                 ApiBase::PROP_NULLABLE => true
00614             );
00615         }
00616     }
00617 
00624     public function getFinalDescription() {
00625         $desc = $this->getDescription();
00626         wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
00627         return $desc;
00628     }
00629 
00636     public function encodeParamName( $paramName ) {
00637         return $this->mModulePrefix . $paramName;
00638     }
00639 
00649     public function extractRequestParams( $parseLimit = true ) {
00650         // Cache parameters, for performance and to avoid bug 24564.
00651         if ( !isset( $this->mParamCache[$parseLimit] ) ) {
00652             $params = $this->getFinalParams();
00653             $results = array();
00654 
00655             if ( $params ) { // getFinalParams() can return false
00656                 foreach ( $params as $paramName => $paramSettings ) {
00657                     $results[$paramName] = $this->getParameterFromSettings(
00658                         $paramName, $paramSettings, $parseLimit );
00659                 }
00660             }
00661             $this->mParamCache[$parseLimit] = $results;
00662         }
00663         return $this->mParamCache[$parseLimit];
00664     }
00665 
00672     protected function getParameter( $paramName, $parseLimit = true ) {
00673         $params = $this->getFinalParams();
00674         $paramSettings = $params[$paramName];
00675         return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
00676     }
00677 
00682     public function requireOnlyOneParameter( $params ) {
00683         $required = func_get_args();
00684         array_shift( $required );
00685         $p = $this->getModulePrefix();
00686 
00687         $intersection = array_intersect( array_keys( array_filter( $params,
00688             array( $this, "parameterNotEmpty" ) ) ), $required );
00689 
00690         if ( count( $intersection ) > 1 ) {
00691             $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' );
00692         } elseif ( count( $intersection ) == 0 ) {
00693             $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', 'missingparam' );
00694         }
00695     }
00696 
00703     public function getRequireOnlyOneParameterErrorMessages( $params ) {
00704         $p = $this->getModulePrefix();
00705         $params = implode( ", {$p}", $params );
00706 
00707         return array(
00708             array( 'code' => "{$p}missingparam", 'info' => "One of the parameters {$p}{$params} is required" ),
00709             array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
00710         );
00711     }
00712 
00718     public function requireMaxOneParameter( $params ) {
00719         $required = func_get_args();
00720         array_shift( $required );
00721         $p = $this->getModulePrefix();
00722 
00723         $intersection = array_intersect( array_keys( array_filter( $params,
00724             array( $this, "parameterNotEmpty" ) ) ), $required );
00725 
00726         if ( count( $intersection ) > 1 ) {
00727             $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' );
00728         }
00729     }
00730 
00737     public function getRequireMaxOneParameterErrorMessages( $params ) {
00738         $p = $this->getModulePrefix();
00739         $params = implode( ", {$p}", $params );
00740 
00741         return array(
00742             array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
00743         );
00744     }
00745 
00754     public function getTitleOrPageId( $params, $load = false ) {
00755         $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
00756 
00757         $pageObj = null;
00758         if ( isset( $params['title'] ) ) {
00759             $titleObj = Title::newFromText( $params['title'] );
00760             if ( !$titleObj || $titleObj->isExternal() ) {
00761                 $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
00762             }
00763             if ( !$titleObj->canExist() ) {
00764                 $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
00765             }
00766             $pageObj = WikiPage::factory( $titleObj );
00767             if ( $load !== false ) {
00768                 $pageObj->loadPageData( $load );
00769             }
00770         } elseif ( isset( $params['pageid'] ) ) {
00771             if ( $load === false ) {
00772                 $load = 'fromdb';
00773             }
00774             $pageObj = WikiPage::newFromID( $params['pageid'], $load );
00775             if ( !$pageObj ) {
00776                 $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
00777             }
00778         }
00779 
00780         return $pageObj;
00781     }
00782 
00786     public function getTitleOrPageIdErrorMessage() {
00787         return array_merge(
00788             $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
00789             array(
00790                 array( 'invalidtitle', 'title' ),
00791                 array( 'nosuchpageid', 'pageid' ),
00792             )
00793         );
00794     }
00795 
00802     private function parameterNotEmpty( $x ) {
00803         return !is_null( $x ) && $x !== false;
00804     }
00805 
00811     public static function getValidNamespaces() {
00812         wfDeprecated( __METHOD__, '1.17' );
00813         return MWNamespace::getValidNamespaces();
00814     }
00815 
00824     protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
00825 
00826         $userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS );
00827 
00828         switch ( $watchlist ) {
00829             case 'watch':
00830                 return true;
00831 
00832             case 'unwatch':
00833                 return false;
00834 
00835             case 'preferences':
00836                 # If the user is already watching, don't bother checking
00837                 if ( $userWatching ) {
00838                     return true;
00839                 }
00840                 # If no user option was passed, use watchdefault or watchcreations
00841                 if ( is_null( $userOption ) ) {
00842                     $userOption = $titleObj->exists()
00843                             ? 'watchdefault' : 'watchcreations';
00844                 }
00845                 # Watch the article based on the user preference
00846                 return $this->getUser()->getBoolOption( $userOption );
00847 
00848             case 'nochange':
00849                 return $userWatching;
00850 
00851             default:
00852                 return $userWatching;
00853         }
00854     }
00855 
00862     protected function setWatch( $watch, $titleObj, $userOption = null ) {
00863         $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
00864         if ( $value === null ) {
00865             return;
00866         }
00867 
00868         WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
00869     }
00870 
00880     protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
00881         // Some classes may decide to change parameter names
00882         $encParamName = $this->encodeParamName( $paramName );
00883 
00884         if ( !is_array( $paramSettings ) ) {
00885             $default = $paramSettings;
00886             $multi = false;
00887             $type = gettype( $paramSettings );
00888             $dupes = false;
00889             $deprecated = false;
00890             $required = false;
00891         } else {
00892             $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
00893             $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
00894             $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
00895             $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] ) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false;
00896             $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? $paramSettings[self::PARAM_DEPRECATED] : false;
00897             $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ? $paramSettings[self::PARAM_REQUIRED] : false;
00898 
00899             // When type is not given, and no choices, the type is the same as $default
00900             if ( !isset( $type ) ) {
00901                 if ( isset( $default ) ) {
00902                     $type = gettype( $default );
00903                 } else {
00904                     $type = 'NULL'; // allow everything
00905                 }
00906             }
00907         }
00908 
00909         if ( $type == 'boolean' ) {
00910             if ( isset( $default ) && $default !== false ) {
00911                 // Having a default value of anything other than 'false' is not allowed
00912                 ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'. Boolean parameters must default to false." );
00913             }
00914 
00915             $value = $this->getMain()->getCheck( $encParamName );
00916         } elseif ( $type == 'upload' ) {
00917             if ( isset( $default ) ) {
00918                 // Having a default value is not allowed
00919                 ApiBase::dieDebug( __METHOD__, "File upload param $encParamName's default is set to '$default'. File upload parameters may not have a default." );
00920             }
00921             if ( $multi ) {
00922                 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00923             }
00924             $value = $this->getMain()->getUpload( $encParamName );
00925             if ( !$value->exists() ) {
00926                 // This will get the value without trying to normalize it
00927                 // (because trying to normalize a large binary file
00928                 // accidentally uploaded as a field fails spectacularly)
00929                 $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
00930                 if ( $value !== null ) {
00931                     $this->dieUsage(
00932                         "File upload param $encParamName is not a file upload; " .
00933                         "be sure to use multipart/form-data for your POST and include " .
00934                         "a filename in the Content-Disposition header.",
00935                         "badupload_{$encParamName}"
00936                     );
00937                 }
00938             }
00939         } else {
00940             $value = $this->getMain()->getVal( $encParamName, $default );
00941 
00942             if ( isset( $value ) && $type == 'namespace' ) {
00943                 $type = MWNamespace::getValidNamespaces();
00944             }
00945         }
00946 
00947         if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
00948             $value = $this->parseMultiValue( $encParamName, $value, $multi, is_array( $type ) ? $type : null );
00949         }
00950 
00951         // More validation only when choices were not given
00952         // choices were validated in parseMultiValue()
00953         if ( isset( $value ) ) {
00954             if ( !is_array( $type ) ) {
00955                 switch ( $type ) {
00956                     case 'NULL': // nothing to do
00957                         break;
00958                     case 'string':
00959                         if ( $required && $value === '' ) {
00960                             $this->dieUsageMsg( array( 'missingparam', $paramName ) );
00961                         }
00962                         break;
00963                     case 'integer': // Force everything using intval() and optionally validate limits
00964                         $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
00965                         $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
00966                         $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
00967                                 ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
00968 
00969                         if ( is_array( $value ) ) {
00970                             $value = array_map( 'intval', $value );
00971                             if ( !is_null( $min ) || !is_null( $max ) ) {
00972                                 foreach ( $value as &$v ) {
00973                                     $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
00974                                 }
00975                             }
00976                         } else {
00977                             $value = intval( $value );
00978                             if ( !is_null( $min ) || !is_null( $max ) ) {
00979                                 $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
00980                             }
00981                         }
00982                         break;
00983                     case 'limit':
00984                         if ( !$parseLimit ) {
00985                             // Don't do any validation whatsoever
00986                             break;
00987                         }
00988                         if ( !isset( $paramSettings[self::PARAM_MAX] ) || !isset( $paramSettings[self::PARAM_MAX2] ) ) {
00989                             ApiBase::dieDebug( __METHOD__, "MAX1 or MAX2 are not defined for the limit $encParamName" );
00990                         }
00991                         if ( $multi ) {
00992                             ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00993                         }
00994                         $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
00995                         if ( $value == 'max' ) {
00996                             $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX];
00997                             $this->getResult()->setParsedLimit( $this->getModuleName(), $value );
00998                         } else {
00999                             $value = intval( $value );
01000                             $this->validateLimit( $paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2] );
01001                         }
01002                         break;
01003                     case 'boolean':
01004                         if ( $multi ) {
01005                             ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
01006                         }
01007                         break;
01008                     case 'timestamp':
01009                         if ( is_array( $value ) ) {
01010                             foreach ( $value as $key => $val ) {
01011                                 $value[$key] = $this->validateTimestamp( $val, $encParamName );
01012                             }
01013                         } else {
01014                             $value = $this->validateTimestamp( $value, $encParamName );
01015                         }
01016                         break;
01017                     case 'user':
01018                         if ( is_array( $value ) ) {
01019                             foreach ( $value as $key => $val ) {
01020                                 $value[$key] = $this->validateUser( $val, $encParamName );
01021                             }
01022                         } else {
01023                             $value = $this->validateUser( $value, $encParamName );
01024                         }
01025                         break;
01026                     case 'upload': // nothing to do
01027                         break;
01028                     default:
01029                         ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
01030                 }
01031             }
01032 
01033             // Throw out duplicates if requested
01034             if ( !$dupes && is_array( $value ) ) {
01035                 $value = array_unique( $value );
01036             }
01037 
01038             // Set a warning if a deprecated parameter has been passed
01039             if ( $deprecated && $value !== false ) {
01040                 $this->setWarning( "The $encParamName parameter has been deprecated." );
01041             }
01042         } elseif ( $required ) {
01043             $this->dieUsageMsg( array( 'missingparam', $paramName ) );
01044         }
01045 
01046         return $value;
01047     }
01048 
01062     protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
01063         if ( trim( $value ) === '' && $allowMultiple ) {
01064             return array();
01065         }
01066 
01067         // This is a bit awkward, but we want to avoid calling canApiHighLimits() because it unstubs $wgUser
01068         $valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
01069         $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() ?
01070                 self::LIMIT_SML2 : self::LIMIT_SML1;
01071 
01072         if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
01073             $this->setWarning( "Too many values supplied for parameter '$valueName': the limit is $sizeLimit" );
01074         }
01075 
01076         if ( !$allowMultiple && count( $valuesList ) != 1 ) {
01077             // Bug 33482 - Allow entries with | in them for non-multiple values
01078             if ( in_array( $value, $allowedValues, true ) ) {
01079                 return $value;
01080             }
01081 
01082             $possibleValues = is_array( $allowedValues ) ? "of '" . implode( "', '", $allowedValues ) . "'" : '';
01083             $this->dieUsage( "Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName" );
01084         }
01085 
01086         if ( is_array( $allowedValues ) ) {
01087             // Check for unknown values
01088             $unknown = array_diff( $valuesList, $allowedValues );
01089             if ( count( $unknown ) ) {
01090                 if ( $allowMultiple ) {
01091                     $s = count( $unknown ) > 1 ? 's' : '';
01092                     $vals = implode( ", ", $unknown );
01093                     $this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
01094                 } else {
01095                     $this->dieUsage( "Unrecognized value for parameter '$valueName': {$valuesList[0]}", "unknown_$valueName" );
01096                 }
01097             }
01098             // Now throw them out
01099             $valuesList = array_intersect( $valuesList, $allowedValues );
01100         }
01101 
01102         return $allowMultiple ? $valuesList : $valuesList[0];
01103     }
01104 
01115     function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
01116         if ( !is_null( $min ) && $value < $min ) {
01117 
01118             $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
01119             $this->warnOrDie( $msg, $enforceLimits );
01120             $value = $min;
01121         }
01122 
01123         // Minimum is always validated, whereas maximum is checked only if not running in internal call mode
01124         if ( $this->getMain()->isInternalMode() ) {
01125             return;
01126         }
01127 
01128         // Optimization: do not check user's bot status unless really needed -- skips db query
01129         // assumes $botMax >= $max
01130         if ( !is_null( $max ) && $value > $max ) {
01131             if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
01132                 if ( $value > $botMax ) {
01133                     $msg = $this->encodeParamName( $paramName ) . " may not be over $botMax (set to $value) for bots or sysops";
01134                     $this->warnOrDie( $msg, $enforceLimits );
01135                     $value = $botMax;
01136                 }
01137             } else {
01138                 $msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
01139                 $this->warnOrDie( $msg, $enforceLimits );
01140                 $value = $max;
01141             }
01142         }
01143     }
01144 
01151     function validateTimestamp( $value, $encParamName ) {
01152         $unixTimestamp = wfTimestamp( TS_UNIX, $value );
01153         if ( $unixTimestamp === false ) {
01154             $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
01155         }
01156         return wfTimestamp( TS_MW, $unixTimestamp );
01157     }
01158 
01165     private function validateUser( $value, $encParamName ) {
01166         $title = Title::makeTitleSafe( NS_USER, $value );
01167         if ( $title === null ) {
01168             $this->dieUsage( "Invalid value '$value' for user parameter $encParamName", "baduser_{$encParamName}" );
01169         }
01170         return $title->getText();
01171     }
01172 
01179     private function warnOrDie( $msg, $enforceLimits = false ) {
01180         if ( $enforceLimits ) {
01181             $this->dieUsage( $msg, 'integeroutofrange' );
01182         } else {
01183             $this->setWarning( $msg );
01184         }
01185     }
01186 
01193     public static function truncateArray( &$arr, $limit ) {
01194         $modified = false;
01195         while ( count( $arr ) > $limit ) {
01196             array_pop( $arr );
01197             $modified = true;
01198         }
01199         return $modified;
01200     }
01201 
01214     public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
01215         Profiler::instance()->close();
01216         throw new UsageException( $description, $this->encodeParamName( $errorCode ), $httpRespCode, $extradata );
01217     }
01218 
01226     public function dieStatus( $status ) {
01227         if ( $status->isGood() ) {
01228             throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
01229         }
01230 
01231         $errors = $status->getErrorsArray();
01232         if ( !$errors ) {
01233             // No errors? Assume the warnings should be treated as errors
01234             $errors = $status->getWarningsArray();
01235         }
01236         if ( !$errors ) {
01237             // Still no errors? Punt
01238             $errors = array( array( 'unknownerror-nocode' ) );
01239         }
01240 
01241         // Cannot use dieUsageMsg() because extensions might return custom
01242         // error messages.
01243         if ( $errors[0] instanceof Message ) {
01244             $msg = $errors[0];
01245             $code = $msg->getKey();
01246         } else {
01247             $code = array_shift( $errors[0] );
01248             $msg = wfMessage( $code, $errors[0] );
01249         }
01250         if ( isset( ApiBase::$messageMap[$code] ) ) {
01251             // Translate message to code, for backwards compatability
01252             $code = ApiBase::$messageMap[$code]['code'];
01253         }
01254         $this->dieUsage( $msg->inLanguage( 'en' )->useDatabase( false )->plain(), $code );
01255     }
01256 
01260     public static $messageMap = array(
01261         // This one MUST be present, or dieUsageMsg() will recurse infinitely
01262         'unknownerror' => array( 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ),
01263         'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
01264 
01265         // Messages from Title::getUserPermissionsErrors()
01266         'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ),
01267         'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ),
01268         'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the \"\$1\" namespace" ),
01269         'customcssprotected' => array( 'code' => 'customcssprotected', 'info' => "You're not allowed to edit custom CSS pages" ),
01270         'customjsprotected' => array( 'code' => 'customjsprotected', 'info' => "You're not allowed to edit custom JavaScript pages" ),
01271         'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ),
01272         'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The \"\$1\" right is required to edit this page" ),
01273         'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ),
01274         'badaccess-group0' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ), // Generic permission denied message
01275         'badaccess-groups' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ),
01276         'titleprotected' => array( 'code' => 'protectedtitle', 'info' => "This title has been protected from creation" ),
01277         'nocreate-loggedin' => array( 'code' => 'cantcreate', 'info' => "You don't have permission to create new pages" ),
01278         'nocreatetext' => array( 'code' => 'cantcreate-anon', 'info' => "Anonymous users can't create new pages" ),
01279         'movenologintext' => array( 'code' => 'cantmove-anon', 'info' => "Anonymous users can't move pages" ),
01280         'movenotallowed' => array( 'code' => 'cantmove', 'info' => "You don't have permission to move pages" ),
01281         'confirmedittext' => array( 'code' => 'confirmemail', 'info' => "You must confirm your email address before you can edit" ),
01282         'blockedtext' => array( 'code' => 'blocked', 'info' => "You have been blocked from editing" ),
01283         'autoblockedtext' => array( 'code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user" ),
01284 
01285         // Miscellaneous interface messages
01286         'actionthrottledtext' => array( 'code' => 'ratelimited', 'info' => "You've exceeded your rate limit. Please wait some time and try again" ),
01287         'alreadyrolled' => array( 'code' => 'alreadyrolled', 'info' => "The page you tried to rollback was already rolled back" ),
01288         'cantrollback' => array( 'code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author" ),
01289         'readonlytext' => array( 'code' => 'readonly', 'info' => "The wiki is currently in read-only mode" ),
01290         'sessionfailure' => array( 'code' => 'badtoken', 'info' => "Invalid token" ),
01291         'cannotdelete' => array( 'code' => 'cantdelete', 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else" ),
01292         'notanarticle' => array( 'code' => 'missingtitle', 'info' => "The page you requested doesn't exist" ),
01293         'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself" ),
01294         'immobile_namespace' => array( 'code' => 'immobilenamespace', 'info' => "You tried to move pages from or to a namespace that is protected from moving" ),
01295         'articleexists' => array( 'code' => 'articleexists', 'info' => "The destination article already exists and is not a redirect to the source article" ),
01296         'protectedpage' => array( 'code' => 'protectedpage', 'info' => "You don't have permission to perform this move" ),
01297         'hookaborted' => array( 'code' => 'hookaborted', 'info' => "The modification you tried to make was aborted by an extension hook" ),
01298         'cantmove-titleprotected' => array( 'code' => 'protectedtitle', 'info' => "The destination article has been protected from creation" ),
01299         'imagenocrossnamespace' => array( 'code' => 'nonfilenamespace', 'info' => "Can't move a file to a non-file namespace" ),
01300         'imagetypemismatch' => array( 'code' => 'filetypemismatch', 'info' => "The new file extension doesn't match its type" ),
01301         // 'badarticleerror' => shouldn't happen
01302         // 'badtitletext' => shouldn't happen
01303         'ip_range_invalid' => array( 'code' => 'invalidrange', 'info' => "Invalid IP range" ),
01304         'range_block_disabled' => array( 'code' => 'rangedisabled', 'info' => "Blocking IP ranges has been disabled" ),
01305         'nosuchusershort' => array( 'code' => 'nosuchuser', 'info' => "The user you specified doesn't exist" ),
01306         'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
01307         'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
01308         'ipb_already_blocked' => array( 'code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked" ),
01309         'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole." ),
01310         'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
01311         'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email" ),
01312         'ipbblocked' => array( 'code' => 'ipbblocked', 'info' => 'You cannot block or unblock users while you are yourself blocked' ),
01313         'ipbnounblockself' => array( 'code' => 'ipbnounblockself', 'info' => 'You are not allowed to unblock yourself' ),
01314         'usermaildisabled' => array( 'code' => 'usermaildisabled', 'info' => "User email has been disabled" ),
01315         'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending email" ),
01316         'notarget' => array( 'code' => 'notarget', 'info' => "You have not specified a valid target for this action" ),
01317         'noemail' => array( 'code' => 'noemail', 'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users" ),
01318         'rcpatroldisabled' => array( 'code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki" ),
01319         'markedaspatrollederror-noautopatrol' => array( 'code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes" ),
01320         'delete-toobig' => array( 'code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions" ),
01321         'movenotallowedfile' => array( 'code' => 'cantmovefile', 'info' => "You don't have permission to move files" ),
01322         'userrights-no-interwiki' => array( 'code' => 'nointerwikiuserrights', 'info' => "You don't have permission to change user rights on other wikis" ),
01323         'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database \"\$1\" does not exist or is not local" ),
01324         'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01325         'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01326         'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
01327         'import-rootpage-invalid' => array( 'code' => 'import-rootpage-invalid', 'info' => 'Root page is an invalid title' ),
01328         'import-rootpage-nosubpage' => array( 'code' => 'import-rootpage-nosubpage', 'info' => 'Namespace "$1" of the root page does not allow subpages' ),
01329 
01330         // API-specific messages
01331         'readrequired' => array( 'code' => 'readapidenied', 'info' => "You need read permission to use this module" ),
01332         'writedisabled' => array( 'code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file" ),
01333         'writerequired' => array( 'code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API" ),
01334         'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
01335         'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ),
01336         'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
01337         'nosuchrevid' => array( 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ),
01338         'nosuchuser' => array( 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ),
01339         'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01340         'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ),
01341         'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ),
01342         'create-titleexists' => array( 'code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'" ),
01343         'missingtitle-createonly' => array( 'code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'" ),
01344         'cantblock' => array( 'code' => 'cantblock', 'info' => "You don't have permission to block users" ),
01345         'canthide' => array( 'code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log" ),
01346         'cantblock-email' => array( 'code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending email through the wiki" ),
01347         'unblock-notarget' => array( 'code' => 'notarget', 'info' => "Either the id or the user parameter must be set" ),
01348         'unblock-idanduser' => array( 'code' => 'idanduser', 'info' => "The id and user parameters can't be used together" ),
01349         'cantunblock' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to unblock users" ),
01350         'cannotundelete' => array( 'code' => 'cantundelete', 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already" ),
01351         'permdenied-undelete' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions" ),
01352         'createonly-exists' => array( 'code' => 'articleexists', 'info' => "The article you tried to create has been created already" ),
01353         'nocreate-missing' => array( 'code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist" ),
01354         'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid \"\$1\"" ),
01355         'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type \"\$1\"" ),
01356         'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level \"\$1\"" ),
01357         'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ),
01358         'cantimport' => array( 'code' => 'cantimport', 'info' => "You don't have permission to import pages" ),
01359         'cantimport-upload' => array( 'code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages" ),
01360         'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ),
01361         'importuploaderrorsize' => array( 'code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size' ),
01362         'importuploaderrorpartial' => array( 'code' => 'partialupload', 'info' => 'The file was only partially uploaded' ),
01363         'importuploaderrortemp' => array( 'code' => 'notempdir', 'info' => 'The temporary upload directory is missing' ),
01364         'importcantopen' => array( 'code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file" ),
01365         'import-noarticle' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
01366         'importbadinterwiki' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
01367         'import-unknownerror' => array( 'code' => 'import-unknownerror', 'info' => "Unknown error on import: \"\$1\"" ),
01368         'cantoverwrite-sharedfile' => array( 'code' => 'cantoverwrite-sharedfile', 'info' => 'The target file exists on a shared repository and you do not have permission to override it' ),
01369         'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ),
01370         'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ),
01371         'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ),
01372         'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ),
01373         'invalidoldimage' => array( 'code' => 'invalidoldimage', 'info' => 'The oldimage parameter has invalid format' ),
01374         'nodeleteablefile' => array( 'code' => 'nodeleteablefile', 'info' => 'No such old version of the file' ),
01375         'fileexists-forbidden' => array( 'code' => 'fileexists-forbidden', 'info' => 'A file with name "$1" already exists, and cannot be overwritten.' ),
01376         'fileexists-shared-forbidden' => array( 'code' => 'fileexists-shared-forbidden', 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.' ),
01377         'filerevert-badversion' => array( 'code' => 'filerevert-badversion', 'info' => 'There is no previous local version of this file with the provided timestamp.' ),
01378 
01379         // ApiEditPage messages
01380         'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
01381         'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ),
01382         'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" ),
01383         'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ),
01384         'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
01385         'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),
01386         'wasdeleted' => array( 'code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp" ),
01387         'blankpage' => array( 'code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed" ),
01388         'editconflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
01389         'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
01390         'missingtext' => array( 'code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set" ),
01391         'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ),
01392         'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of \"\$2\"" ),
01393         'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ),
01394 
01395         // Messages from WikiPage::doEit()
01396         'edit-hook-aborted' => array( 'code' => 'edit-hook-aborted', 'info' => "Your edit was aborted by an ArticleSave hook" ),
01397         'edit-gone-missing' => array( 'code' => 'edit-gone-missing', 'info' => "The page you tried to edit doesn't seem to exist anymore" ),
01398         'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
01399         'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ),
01400 
01401         // uploadMsgs
01402         'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ),
01403         'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
01404         'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
01405         'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ),
01406         'copyuploadbaddomain' => array( 'code' => 'copyuploadbaddomain', 'info' => 'Uploads by URL are not allowed from this domain.' ),
01407         'copyuploadbadurl' => array( 'code' => 'copyuploadbadurl', 'info' => 'Upload not allowed from this URL.' ),
01408 
01409         'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
01410         'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
01411         'illegal-filename' => array( 'code' => 'illegal-filename', 'info' => 'The filename is not allowed' ),
01412         'filetype-missing' => array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
01413 
01414         'mustbeloggedin' => array( 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' )
01415     );
01416 
01420     public function dieReadOnly() {
01421         $parsed = $this->parseMsg( array( 'readonlytext' ) );
01422         $this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
01423             array( 'readonlyreason' => wfReadOnlyReason() ) );
01424     }
01425 
01430     public function dieUsageMsg( $error ) {
01431         # most of the time we send a 1 element, so we might as well send it as
01432         # a string and make this an array here.
01433         if ( is_string( $error ) ) {
01434             $error = array( $error );
01435         }
01436         $parsed = $this->parseMsg( $error );
01437         $this->dieUsage( $parsed['info'], $parsed['code'] );
01438     }
01439 
01446     public function dieUsageMsgOrDebug( $error ) {
01447         global $wgDebugAPI;
01448         if ( $wgDebugAPI !== true ) {
01449             $this->dieUsageMsg( $error );
01450         } else {
01451             if ( is_string( $error ) ) {
01452                 $error = array( $error );
01453             }
01454             $parsed = $this->parseMsg( $error );
01455             $this->setWarning( '$wgDebugAPI: ' . $parsed['code']
01456                 . ' - ' . $parsed['info'] );
01457         }
01458     }
01459 
01465     protected function dieContinueUsageIf( $condition ) {
01466         if ( $condition ) {
01467             $this->dieUsage(
01468                 'Invalid continue param. You should pass the original value returned by the previous query',
01469                 'badcontinue' );
01470         }
01471     }
01472 
01478     public function parseMsg( $error ) {
01479         $error = (array)$error; // It seems strings sometimes make their way in here
01480         $key = array_shift( $error );
01481 
01482         // Check whether the error array was nested
01483         // array( array( <code>, <params> ), array( <another_code>, <params> ) )
01484         if ( is_array( $key ) ) {
01485             $error = $key;
01486             $key = array_shift( $error );
01487         }
01488 
01489         if ( isset( self::$messageMap[$key] ) ) {
01490             return array(
01491                 'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
01492                 'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
01493             );
01494         }
01495 
01496         // If the key isn't present, throw an "unknown error"
01497         return $this->parseMsg( array( 'unknownerror', $key ) );
01498     }
01499 
01505     protected static function dieDebug( $method, $message ) {
01506         throw new MWException( "Internal error in $method: $message" );
01507     }
01508 
01513     public function shouldCheckMaxlag() {
01514         return true;
01515     }
01516 
01521     public function isReadMode() {
01522         return true;
01523     }
01528     public function isWriteMode() {
01529         return false;
01530     }
01531 
01536     public function mustBePosted() {
01537         return false;
01538     }
01539 
01546     public function needsToken() {
01547         return false;
01548     }
01549 
01558     public function getTokenSalt() {
01559         return false;
01560     }
01561 
01568     public function getWatchlistUser( $params ) {
01569         if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
01570             $user = User::newFromName( $params['owner'], false );
01571             if ( !( $user && $user->getId() ) ) {
01572                 $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
01573             }
01574             $token = $user->getOption( 'watchlisttoken' );
01575             if ( $token == '' || $token != $params['token'] ) {
01576                 $this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
01577             }
01578         } else {
01579             if ( !$this->getUser()->isLoggedIn() ) {
01580                 $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
01581             }
01582             if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
01583                 $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
01584             }
01585             $user = $this->getUser();
01586         }
01587         return $user;
01588     }
01589 
01593     public function getHelpUrls() {
01594         return false;
01595     }
01596 
01605     public function getPossibleErrors() {
01606         $ret = array();
01607 
01608         $params = $this->getFinalParams();
01609         if ( $params ) {
01610             foreach ( $params as $paramName => $paramSettings ) {
01611                 if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) && $paramSettings[ApiBase::PARAM_REQUIRED] ) {
01612                     $ret[] = array( 'missingparam', $paramName );
01613                 }
01614             }
01615             if ( array_key_exists( 'continue', $params ) ) {
01616                 $ret[] = array(
01617                     'code' => 'badcontinue',
01618                     'info' => 'Invalid continue param. You should pass the original value returned by the previous query'
01619                 );
01620             }
01621         }
01622 
01623         if ( $this->mustBePosted() ) {
01624             $ret[] = array( 'mustbeposted', $this->getModuleName() );
01625         }
01626 
01627         if ( $this->isReadMode() ) {
01628             $ret[] = array( 'readrequired' );
01629         }
01630 
01631         if ( $this->isWriteMode() ) {
01632             $ret[] = array( 'writerequired' );
01633             $ret[] = array( 'writedisabled' );
01634         }
01635 
01636         if ( $this->needsToken() ) {
01637             if ( !isset( $params['token'][ApiBase::PARAM_REQUIRED] )
01638                 || !$params['token'][ApiBase::PARAM_REQUIRED]
01639             ) {
01640                 // Add token as possible missing parameter, if not already done
01641                 $ret[] = array( 'missingparam', 'token' );
01642             }
01643             $ret[] = array( 'sessionfailure' );
01644         }
01645 
01646         return $ret;
01647     }
01648 
01656     public function getFinalPossibleErrors() {
01657         $possibleErrors = $this->getPossibleErrors();
01658         wfRunHooks( 'APIGetPossibleErrors', array( $this, &$possibleErrors ) );
01659         return $possibleErrors;
01660     }
01661 
01667     public function parseErrors( $errors ) {
01668         $ret = array();
01669 
01670         foreach ( $errors as $row ) {
01671             if ( isset( $row['code'] ) && isset( $row['info'] ) ) {
01672                 $ret[] = $row;
01673             } else {
01674                 $ret[] = $this->parseMsg( $row );
01675             }
01676         }
01677         return $ret;
01678     }
01679 
01683     private $mTimeIn = 0, $mModuleTime = 0;
01684 
01688     public function profileIn() {
01689         if ( $this->mTimeIn !== 0 ) {
01690             ApiBase::dieDebug( __METHOD__, 'called twice without calling profileOut()' );
01691         }
01692         $this->mTimeIn = microtime( true );
01693         wfProfileIn( $this->getModuleProfileName() );
01694     }
01695 
01699     public function profileOut() {
01700         if ( $this->mTimeIn === 0 ) {
01701             ApiBase::dieDebug( __METHOD__, 'called without calling profileIn() first' );
01702         }
01703         if ( $this->mDBTimeIn !== 0 ) {
01704             ApiBase::dieDebug( __METHOD__, 'must be called after database profiling is done with profileDBOut()' );
01705         }
01706 
01707         $this->mModuleTime += microtime( true ) - $this->mTimeIn;
01708         $this->mTimeIn = 0;
01709         wfProfileOut( $this->getModuleProfileName() );
01710     }
01711 
01716     public function safeProfileOut() {
01717         if ( $this->mTimeIn !== 0 ) {
01718             if ( $this->mDBTimeIn !== 0 ) {
01719                 $this->profileDBOut();
01720             }
01721             $this->profileOut();
01722         }
01723     }
01724 
01729     public function getProfileTime() {
01730         if ( $this->mTimeIn !== 0 ) {
01731             ApiBase::dieDebug( __METHOD__, 'called without calling profileOut() first' );
01732         }
01733         return $this->mModuleTime;
01734     }
01735 
01739     private $mDBTimeIn = 0, $mDBTime = 0;
01740 
01744     public function profileDBIn() {
01745         if ( $this->mTimeIn === 0 ) {
01746             ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
01747         }
01748         if ( $this->mDBTimeIn !== 0 ) {
01749             ApiBase::dieDebug( __METHOD__, 'called twice without calling profileDBOut()' );
01750         }
01751         $this->mDBTimeIn = microtime( true );
01752         wfProfileIn( $this->getModuleProfileName( true ) );
01753     }
01754 
01758     public function profileDBOut() {
01759         if ( $this->mTimeIn === 0 ) {
01760             ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
01761         }
01762         if ( $this->mDBTimeIn === 0 ) {
01763             ApiBase::dieDebug( __METHOD__, 'called without calling profileDBIn() first' );
01764         }
01765 
01766         $time = microtime( true ) - $this->mDBTimeIn;
01767         $this->mDBTimeIn = 0;
01768 
01769         $this->mDBTime += $time;
01770         $this->getMain()->mDBTime += $time;
01771         wfProfileOut( $this->getModuleProfileName( true ) );
01772     }
01773 
01778     public function getProfileDBTime() {
01779         if ( $this->mDBTimeIn !== 0 ) {
01780             ApiBase::dieDebug( __METHOD__, 'called without calling profileDBOut() first' );
01781         }
01782         return $this->mDBTime;
01783     }
01784 
01789     protected function getDB() {
01790         if ( !isset( $this->mSlaveDB ) ) {
01791             $this->profileDBIn();
01792             $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
01793             $this->profileDBOut();
01794         }
01795         return $this->mSlaveDB;
01796     }
01797 
01804     public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
01805         print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
01806         var_export( $value );
01807         if ( $backtrace ) {
01808             print "\n" . wfBacktrace();
01809         }
01810         print "\n</pre>\n";
01811     }
01812 }