MediaWiki  REL1_23
ApiBase.php
Go to the documentation of this file.
00001 <?php
00042 abstract class ApiBase extends ContextSource {
00043     // These constants allow modules to specify exactly how to treat incoming parameters.
00044 
00045     // Default value of the parameter
00046     const PARAM_DFLT = 0;
00047     // Boolean, do we accept more than one item for this parameter (e.g.: titles)?
00048     const PARAM_ISMULTI = 1;
00049     // Can be either a string type (e.g.: 'integer') or an array of allowed values
00050     const PARAM_TYPE = 2;
00051     // Max value allowed for a parameter. Only applies if TYPE='integer'
00052     const PARAM_MAX = 3;
00053     // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer'
00054     const PARAM_MAX2 = 4;
00055     // Lowest value allowed for a parameter. Only applies if TYPE='integer'
00056     const PARAM_MIN = 5;
00057     // Boolean, do we allow the same value to be set more than once when ISMULTI=true
00058     const PARAM_ALLOW_DUPLICATES = 6;
00059     // Boolean, is the parameter deprecated (will show a warning)
00060     const PARAM_DEPRECATED = 7;
00062     const PARAM_REQUIRED = 8; // Boolean, is the parameter required?
00064     // Boolean, if MIN/MAX are set, enforce (die) these?
00065     // Only applies if TYPE='integer' Use with extreme caution
00066     const PARAM_RANGE_ENFORCE = 9;
00067 
00068     // Name of property group that is on the root element of the result,
00069     // i.e. not part of a list
00070     const PROP_ROOT = 'ROOT';
00071     // Boolean, is the result multiple items? Defaults to true for query modules,
00072     // to false for other modules
00073     const PROP_LIST = 'LIST';
00074     const PROP_TYPE = 0; // Type of the property, uses same format as PARAM_TYPE
00075     // Boolean, can the property be not included in the result? Defaults to false
00076     const PROP_NULLABLE = 1;
00077 
00078     const LIMIT_BIG1 = 500; // Fast query, std user limit
00079     const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
00080     const LIMIT_SML1 = 50; // Slow query, std user limit
00081     const LIMIT_SML2 = 500; // Slow query, bot/sysop limit
00082 
00088     const GET_VALUES_FOR_HELP = 1;
00089 
00090     private $mMainModule, $mModuleName, $mModulePrefix;
00091     private $mSlaveDB = null;
00092     private $mParamCache = array();
00093 
00100     public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
00101         $this->mMainModule = $mainModule;
00102         $this->mModuleName = $moduleName;
00103         $this->mModulePrefix = $modulePrefix;
00104 
00105         if ( !$this->isMain() ) {
00106             $this->setContext( $mainModule->getContext() );
00107         }
00108     }
00109 
00110     /*****************************************************************************
00111      * ABSTRACT METHODS                                                          *
00112      *****************************************************************************/
00113 
00130     abstract public function execute();
00131 
00139     public function getVersion() {
00140         wfDeprecated( __METHOD__, '1.21' );
00141 
00142         return '';
00143     }
00144 
00149     public function getModuleName() {
00150         return $this->mModuleName;
00151     }
00152 
00158     public function getModuleManager() {
00159         return null;
00160     }
00161 
00166     public function getModulePrefix() {
00167         return $this->mModulePrefix;
00168     }
00169 
00177     public function getModuleProfileName( $db = false ) {
00178         if ( $db ) {
00179             return 'API:' . $this->mModuleName . '-DB';
00180         }
00181 
00182         return 'API:' . $this->mModuleName;
00183     }
00184 
00189     public function getMain() {
00190         return $this->mMainModule;
00191     }
00192 
00198     public function isMain() {
00199         return $this === $this->mMainModule;
00200     }
00201 
00206     public function getResult() {
00207         // Main module has getResult() method overridden
00208         // Safety - avoid infinite loop:
00209         if ( $this->isMain() ) {
00210             ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
00211         }
00212 
00213         return $this->getMain()->getResult();
00214     }
00215 
00220     public function getResultData() {
00221         return $this->getResult()->getData();
00222     }
00223 
00233     public function createContext() {
00234         wfDeprecated( __METHOD__, '1.19' );
00235 
00236         return new DerivativeContext( $this->getContext() );
00237     }
00238 
00246     public function setWarning( $warning ) {
00247         $result = $this->getResult();
00248         $data = $result->getData();
00249         $moduleName = $this->getModuleName();
00250         if ( isset( $data['warnings'][$moduleName] ) ) {
00251             // Don't add duplicate warnings
00252             $oldWarning = $data['warnings'][$moduleName]['*'];
00253             $warnPos = strpos( $oldWarning, $warning );
00254             // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
00255             if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
00256                 // Check if $warning is followed by "\n" or the end of the $oldWarning
00257                 $warnPos += strlen( $warning );
00258                 if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
00259                     return;
00260                 }
00261             }
00262             // If there is a warning already, append it to the existing one
00263             $warning = "$oldWarning\n$warning";
00264         }
00265         $msg = array();
00266         ApiResult::setContent( $msg, $warning );
00267         $result->disableSizeCheck();
00268         $result->addValue( 'warnings', $moduleName,
00269             $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
00270         $result->enableSizeCheck();
00271     }
00272 
00279     public function getCustomPrinter() {
00280         return null;
00281     }
00282 
00287     public function makeHelpMsg() {
00288         static $lnPrfx = "\n  ";
00289 
00290         $msg = $this->getFinalDescription();
00291 
00292         if ( $msg !== false ) {
00293 
00294             if ( !is_array( $msg ) ) {
00295                 $msg = array(
00296                     $msg
00297                 );
00298             }
00299             $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
00300 
00301             $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
00302 
00303             if ( $this->isReadMode() ) {
00304                 $msg .= "\nThis module requires read rights";
00305             }
00306             if ( $this->isWriteMode() ) {
00307                 $msg .= "\nThis module requires write rights";
00308             }
00309             if ( $this->mustBePosted() ) {
00310                 $msg .= "\nThis module only accepts POST requests";
00311             }
00312             if ( $this->isReadMode() || $this->isWriteMode() ||
00313                 $this->mustBePosted()
00314             ) {
00315                 $msg .= "\n";
00316             }
00317 
00318             // Parameters
00319             $paramsMsg = $this->makeHelpMsgParameters();
00320             if ( $paramsMsg !== false ) {
00321                 $msg .= "Parameters:\n$paramsMsg";
00322             }
00323 
00324             $examples = $this->getExamples();
00325             if ( $examples ) {
00326                 if ( !is_array( $examples ) ) {
00327                     $examples = array(
00328                         $examples
00329                     );
00330                 }
00331                 $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
00332                 foreach ( $examples as $k => $v ) {
00333                     if ( is_numeric( $k ) ) {
00334                         $msg .= "  $v\n";
00335                     } else {
00336                         if ( is_array( $v ) ) {
00337                             $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
00338                         } else {
00339                             $msgExample = "  $v";
00340                         }
00341                         $msgExample .= ":";
00342                         $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
00343                     }
00344                 }
00345             }
00346         }
00347 
00348         return $msg;
00349     }
00350 
00355     private function indentExampleText( $item ) {
00356         return "  " . $item;
00357     }
00358 
00365     protected function makeHelpArrayToString( $prefix, $title, $input ) {
00366         if ( $input === false ) {
00367             return '';
00368         }
00369         if ( !is_array( $input ) ) {
00370             $input = array( $input );
00371         }
00372 
00373         if ( count( $input ) > 0 ) {
00374             if ( $title ) {
00375                 $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
00376             } else {
00377                 $msg = '  ';
00378             }
00379             $msg .= implode( $prefix, $input ) . "\n";
00380 
00381             return $msg;
00382         }
00383 
00384         return '';
00385     }
00386 
00392     public function makeHelpMsgParameters() {
00393         $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
00394         if ( $params ) {
00395 
00396             $paramsDescription = $this->getFinalParamDescription();
00397             $msg = '';
00398             $paramPrefix = "\n" . str_repeat( ' ', 24 );
00399             $descWordwrap = "\n" . str_repeat( ' ', 28 );
00400             foreach ( $params as $paramName => $paramSettings ) {
00401                 $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
00402                 if ( is_array( $desc ) ) {
00403                     $desc = implode( $paramPrefix, $desc );
00404                 }
00405 
00406                 //handle shorthand
00407                 if ( !is_array( $paramSettings ) ) {
00408                     $paramSettings = array(
00409                         self::PARAM_DFLT => $paramSettings,
00410                     );
00411                 }
00412 
00413                 //handle missing type
00414                 if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
00415                     $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
00416                         ? $paramSettings[ApiBase::PARAM_DFLT]
00417                         : null;
00418                     if ( is_bool( $dflt ) ) {
00419                         $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
00420                     } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
00421                         $paramSettings[ApiBase::PARAM_TYPE] = 'string';
00422                     } elseif ( is_int( $dflt ) ) {
00423                         $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
00424                     }
00425                 }
00426 
00427                 if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
00428                     && $paramSettings[self::PARAM_DEPRECATED]
00429                 ) {
00430                     $desc = "DEPRECATED! $desc";
00431                 }
00432 
00433                 if ( isset( $paramSettings[self::PARAM_REQUIRED] )
00434                     && $paramSettings[self::PARAM_REQUIRED]
00435                 ) {
00436                     $desc .= $paramPrefix . "This parameter is required";
00437                 }
00438 
00439                 $type = isset( $paramSettings[self::PARAM_TYPE] )
00440                     ? $paramSettings[self::PARAM_TYPE]
00441                     : null;
00442                 if ( isset( $type ) ) {
00443                     $hintPipeSeparated = true;
00444                     $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
00445                         ? $paramSettings[self::PARAM_ISMULTI]
00446                         : false;
00447                     if ( $multi ) {
00448                         $prompt = 'Values (separate with \'|\'): ';
00449                     } else {
00450                         $prompt = 'One value: ';
00451                     }
00452 
00453                     if ( is_array( $type ) ) {
00454                         $choices = array();
00455                         $nothingPrompt = '';
00456                         foreach ( $type as $t ) {
00457                             if ( $t === '' ) {
00458                                 $nothingPrompt = 'Can be empty, or ';
00459                             } else {
00460                                 $choices[] = $t;
00461                             }
00462                         }
00463                         $desc .= $paramPrefix . $nothingPrompt . $prompt;
00464                         $choicesstring = implode( ', ', $choices );
00465                         $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
00466                         $hintPipeSeparated = false;
00467                     } else {
00468                         switch ( $type ) {
00469                             case 'namespace':
00470                                 // Special handling because namespaces are
00471                                 // type-limited, yet they are not given
00472                                 $desc .= $paramPrefix . $prompt;
00473                                 $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
00474                                     100, $descWordwrap );
00475                                 $hintPipeSeparated = false;
00476                                 break;
00477                             case 'limit':
00478                                 $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
00479                                 if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
00480                                     $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
00481                                 }
00482                                 $desc .= ' allowed';
00483                                 break;
00484                             case 'integer':
00485                                 $s = $multi ? 's' : '';
00486                                 $hasMin = isset( $paramSettings[self::PARAM_MIN] );
00487                                 $hasMax = isset( $paramSettings[self::PARAM_MAX] );
00488                                 if ( $hasMin || $hasMax ) {
00489                                     if ( !$hasMax ) {
00490                                         $intRangeStr = "The value$s must be no less than " .
00491                                             "{$paramSettings[self::PARAM_MIN]}";
00492                                     } elseif ( !$hasMin ) {
00493                                         $intRangeStr = "The value$s must be no more than " .
00494                                             "{$paramSettings[self::PARAM_MAX]}";
00495                                     } else {
00496                                         $intRangeStr = "The value$s must be between " .
00497                                             "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
00498                                     }
00499 
00500                                     $desc .= $paramPrefix . $intRangeStr;
00501                                 }
00502                                 break;
00503                             case 'upload':
00504                                 $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
00505                                 break;
00506                         }
00507                     }
00508 
00509                     if ( $multi ) {
00510                         if ( $hintPipeSeparated ) {
00511                             $desc .= $paramPrefix . "Separate values with '|'";
00512                         }
00513 
00514                         $isArray = is_array( $type );
00515                         if ( !$isArray
00516                             || $isArray && count( $type ) > self::LIMIT_SML1
00517                         ) {
00518                             $desc .= $paramPrefix . "Maximum number of values " .
00519                                 self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
00520                         }
00521                     }
00522                 }
00523 
00524                 $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
00525                 if ( !is_null( $default ) && $default !== false ) {
00526                     $desc .= $paramPrefix . "Default: $default";
00527                 }
00528 
00529                 $msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
00530             }
00531 
00532             return $msg;
00533         }
00534 
00535         return false;
00536     }
00537 
00542     protected function getDescription() {
00543         return false;
00544     }
00545 
00550     protected function getExamples() {
00551         return false;
00552     }
00553 
00566     protected function getAllowedParams( /* $flags = 0 */ ) {
00567         // int $flags is not declared because it causes "Strict standards"
00568         // warning. Most derived classes do not implement it.
00569         return false;
00570     }
00571 
00578     protected function getParamDescription() {
00579         return false;
00580     }
00581 
00590     public function getFinalParams( $flags = 0 ) {
00591         $params = $this->getAllowedParams( $flags );
00592         wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
00593 
00594         return $params;
00595     }
00596 
00603     public function getFinalParamDescription() {
00604         $desc = $this->getParamDescription();
00605         wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
00606 
00607         return $desc;
00608     }
00609 
00626     protected function getResultProperties() {
00627         return false;
00628     }
00629 
00636     public function getFinalResultProperties() {
00637         $properties = $this->getResultProperties();
00638         wfRunHooks( 'APIGetResultProperties', array( $this, &$properties ) );
00639 
00640         return $properties;
00641     }
00642 
00647     protected static function addTokenProperties( &$props, $tokenFunctions ) {
00648         foreach ( array_keys( $tokenFunctions ) as $token ) {
00649             $props[''][$token . 'token'] = array(
00650                 ApiBase::PROP_TYPE => 'string',
00651                 ApiBase::PROP_NULLABLE => true
00652             );
00653         }
00654     }
00655 
00662     public function getFinalDescription() {
00663         $desc = $this->getDescription();
00664         wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
00665 
00666         return $desc;
00667     }
00668 
00675     public function encodeParamName( $paramName ) {
00676         return $this->mModulePrefix . $paramName;
00677     }
00678 
00688     public function extractRequestParams( $parseLimit = true ) {
00689         // Cache parameters, for performance and to avoid bug 24564.
00690         if ( !isset( $this->mParamCache[$parseLimit] ) ) {
00691             $params = $this->getFinalParams();
00692             $results = array();
00693 
00694             if ( $params ) { // getFinalParams() can return false
00695                 foreach ( $params as $paramName => $paramSettings ) {
00696                     $results[$paramName] = $this->getParameterFromSettings(
00697                         $paramName, $paramSettings, $parseLimit );
00698                 }
00699             }
00700             $this->mParamCache[$parseLimit] = $results;
00701         }
00702 
00703         return $this->mParamCache[$parseLimit];
00704     }
00705 
00712     protected function getParameter( $paramName, $parseLimit = true ) {
00713         $params = $this->getFinalParams();
00714         $paramSettings = $params[$paramName];
00715 
00716         return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
00717     }
00718 
00723     public function requireOnlyOneParameter( $params ) {
00724         $required = func_get_args();
00725         array_shift( $required );
00726         $p = $this->getModulePrefix();
00727 
00728         $intersection = array_intersect( array_keys( array_filter( $params,
00729             array( $this, "parameterNotEmpty" ) ) ), $required );
00730 
00731         if ( count( $intersection ) > 1 ) {
00732             $this->dieUsage(
00733                 "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
00734                 'invalidparammix' );
00735         } elseif ( count( $intersection ) == 0 ) {
00736             $this->dieUsage(
00737                 "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
00738                 'missingparam'
00739             );
00740         }
00741     }
00742 
00749     public function getRequireOnlyOneParameterErrorMessages( $params ) {
00750         $p = $this->getModulePrefix();
00751         $params = implode( ", {$p}", $params );
00752 
00753         return array(
00754             array(
00755                 'code' => "{$p}missingparam",
00756                 'info' => "One of the parameters {$p}{$params} is required"
00757             ),
00758             array(
00759                 'code' => "{$p}invalidparammix",
00760                 'info' => "The parameters {$p}{$params} can not be used together"
00761             )
00762         );
00763     }
00764 
00770     public function requireMaxOneParameter( $params ) {
00771         $required = func_get_args();
00772         array_shift( $required );
00773         $p = $this->getModulePrefix();
00774 
00775         $intersection = array_intersect( array_keys( array_filter( $params,
00776             array( $this, "parameterNotEmpty" ) ) ), $required );
00777 
00778         if ( count( $intersection ) > 1 ) {
00779             $this->dieUsage(
00780                 "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
00781                 'invalidparammix'
00782             );
00783         }
00784     }
00785 
00792     public function getRequireMaxOneParameterErrorMessages( $params ) {
00793         $p = $this->getModulePrefix();
00794         $params = implode( ", {$p}", $params );
00795 
00796         return array(
00797             array(
00798                 'code' => "{$p}invalidparammix",
00799                 'info' => "The parameters {$p}{$params} can not be used together"
00800             )
00801         );
00802     }
00803 
00811     public function requireAtLeastOneParameter( $params ) {
00812         $required = func_get_args();
00813         array_shift( $required );
00814         $p = $this->getModulePrefix();
00815 
00816         $intersection = array_intersect(
00817             array_keys( array_filter( $params, array( $this, "parameterNotEmpty" ) ) ),
00818             $required
00819         );
00820 
00821         if ( count( $intersection ) == 0 ) {
00822             $this->dieUsage( "At least one of the parameters {$p}" .
00823                 implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
00824         }
00825     }
00826 
00834     public function getRequireAtLeastOneParameterErrorMessages( $params ) {
00835         $p = $this->getModulePrefix();
00836         $params = implode( ", {$p}", $params );
00837 
00838         return array(
00839             array(
00840                 'code' => "{$p}missingparam",
00841                 'info' => "At least one of the parameters {$p}{$params} is required",
00842             ),
00843         );
00844     }
00845 
00854     public function getTitleOrPageId( $params, $load = false ) {
00855         $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
00856 
00857         $pageObj = null;
00858         if ( isset( $params['title'] ) ) {
00859             $titleObj = Title::newFromText( $params['title'] );
00860             if ( !$titleObj || $titleObj->isExternal() ) {
00861                 $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
00862             }
00863             if ( !$titleObj->canExist() ) {
00864                 $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
00865             }
00866             $pageObj = WikiPage::factory( $titleObj );
00867             if ( $load !== false ) {
00868                 $pageObj->loadPageData( $load );
00869             }
00870         } elseif ( isset( $params['pageid'] ) ) {
00871             if ( $load === false ) {
00872                 $load = 'fromdb';
00873             }
00874             $pageObj = WikiPage::newFromID( $params['pageid'], $load );
00875             if ( !$pageObj ) {
00876                 $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
00877             }
00878         }
00879 
00880         return $pageObj;
00881     }
00882 
00886     public function getTitleOrPageIdErrorMessage() {
00887         return array_merge(
00888             $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
00889             array(
00890                 array( 'invalidtitle', 'title' ),
00891                 array( 'nosuchpageid', 'pageid' ),
00892             )
00893         );
00894     }
00895 
00902     private function parameterNotEmpty( $x ) {
00903         return !is_null( $x ) && $x !== false;
00904     }
00905 
00914     protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
00915 
00916         $userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS );
00917 
00918         switch ( $watchlist ) {
00919             case 'watch':
00920                 return true;
00921 
00922             case 'unwatch':
00923                 return false;
00924 
00925             case 'preferences':
00926                 # If the user is already watching, don't bother checking
00927                 if ( $userWatching ) {
00928                     return true;
00929                 }
00930                 # If no user option was passed, use watchdefault and watchcreations
00931                 if ( is_null( $userOption ) ) {
00932                     return $this->getUser()->getBoolOption( 'watchdefault' ) ||
00933                         $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
00934                 }
00935 
00936                 # Watch the article based on the user preference
00937                 return $this->getUser()->getBoolOption( $userOption );
00938 
00939             case 'nochange':
00940                 return $userWatching;
00941 
00942             default:
00943                 return $userWatching;
00944         }
00945     }
00946 
00953     protected function setWatch( $watch, $titleObj, $userOption = null ) {
00954         $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
00955         if ( $value === null ) {
00956             return;
00957         }
00958 
00959         WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
00960     }
00961 
00971     protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
00972         // Some classes may decide to change parameter names
00973         $encParamName = $this->encodeParamName( $paramName );
00974 
00975         if ( !is_array( $paramSettings ) ) {
00976             $default = $paramSettings;
00977             $multi = false;
00978             $type = gettype( $paramSettings );
00979             $dupes = false;
00980             $deprecated = false;
00981             $required = false;
00982         } else {
00983             $default = isset( $paramSettings[self::PARAM_DFLT] )
00984                 ? $paramSettings[self::PARAM_DFLT]
00985                 : null;
00986             $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
00987                 ? $paramSettings[self::PARAM_ISMULTI]
00988                 : false;
00989             $type = isset( $paramSettings[self::PARAM_TYPE] )
00990                 ? $paramSettings[self::PARAM_TYPE]
00991                 : null;
00992             $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
00993                 ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
00994                 : false;
00995             $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
00996                 ? $paramSettings[self::PARAM_DEPRECATED]
00997                 : false;
00998             $required = isset( $paramSettings[self::PARAM_REQUIRED] )
00999                 ? $paramSettings[self::PARAM_REQUIRED]
01000                 : false;
01001 
01002             // When type is not given, and no choices, the type is the same as $default
01003             if ( !isset( $type ) ) {
01004                 if ( isset( $default ) ) {
01005                     $type = gettype( $default );
01006                 } else {
01007                     $type = 'NULL'; // allow everything
01008                 }
01009             }
01010         }
01011 
01012         if ( $type == 'boolean' ) {
01013             if ( isset( $default ) && $default !== false ) {
01014                 // Having a default value of anything other than 'false' is not allowed
01015                 ApiBase::dieDebug(
01016                     __METHOD__,
01017                     "Boolean param $encParamName's default is set to '$default'. " .
01018                         "Boolean parameters must default to false."
01019                 );
01020             }
01021 
01022             $value = $this->getMain()->getCheck( $encParamName );
01023         } elseif ( $type == 'upload' ) {
01024             if ( isset( $default ) ) {
01025                 // Having a default value is not allowed
01026                 ApiBase::dieDebug(
01027                     __METHOD__,
01028                     "File upload param $encParamName's default is set to " .
01029                         "'$default'. File upload parameters may not have a default." );
01030             }
01031             if ( $multi ) {
01032                 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
01033             }
01034             $value = $this->getMain()->getUpload( $encParamName );
01035             if ( !$value->exists() ) {
01036                 // This will get the value without trying to normalize it
01037                 // (because trying to normalize a large binary file
01038                 // accidentally uploaded as a field fails spectacularly)
01039                 $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
01040                 if ( $value !== null ) {
01041                     $this->dieUsage(
01042                         "File upload param $encParamName is not a file upload; " .
01043                             "be sure to use multipart/form-data for your POST and include " .
01044                             "a filename in the Content-Disposition header.",
01045                         "badupload_{$encParamName}"
01046                     );
01047                 }
01048             }
01049         } else {
01050             $value = $this->getMain()->getVal( $encParamName, $default );
01051 
01052             if ( isset( $value ) && $type == 'namespace' ) {
01053                 $type = MWNamespace::getValidNamespaces();
01054             }
01055         }
01056 
01057         if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
01058             $value = $this->parseMultiValue(
01059                 $encParamName,
01060                 $value,
01061                 $multi,
01062                 is_array( $type ) ? $type : null
01063             );
01064         }
01065 
01066         // More validation only when choices were not given
01067         // choices were validated in parseMultiValue()
01068         if ( isset( $value ) ) {
01069             if ( !is_array( $type ) ) {
01070                 switch ( $type ) {
01071                     case 'NULL': // nothing to do
01072                         break;
01073                     case 'string':
01074                         if ( $required && $value === '' ) {
01075                             $this->dieUsageMsg( array( 'missingparam', $paramName ) );
01076                         }
01077                         break;
01078                     case 'integer': // Force everything using intval() and optionally validate limits
01079                         $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
01080                         $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
01081                         $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
01082                             ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
01083 
01084                         if ( is_array( $value ) ) {
01085                             $value = array_map( 'intval', $value );
01086                             if ( !is_null( $min ) || !is_null( $max ) ) {
01087                                 foreach ( $value as &$v ) {
01088                                     $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
01089                                 }
01090                             }
01091                         } else {
01092                             $value = intval( $value );
01093                             if ( !is_null( $min ) || !is_null( $max ) ) {
01094                                 $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
01095                             }
01096                         }
01097                         break;
01098                     case 'limit':
01099                         if ( !$parseLimit ) {
01100                             // Don't do any validation whatsoever
01101                             break;
01102                         }
01103                         if ( !isset( $paramSettings[self::PARAM_MAX] )
01104                             || !isset( $paramSettings[self::PARAM_MAX2] )
01105                         ) {
01106                             ApiBase::dieDebug(
01107                                 __METHOD__,
01108                                 "MAX1 or MAX2 are not defined for the limit $encParamName"
01109                             );
01110                         }
01111                         if ( $multi ) {
01112                             ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
01113                         }
01114                         $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
01115                         if ( $value == 'max' ) {
01116                             $value = $this->getMain()->canApiHighLimits()
01117                                 ? $paramSettings[self::PARAM_MAX2]
01118                                 : $paramSettings[self::PARAM_MAX];
01119                             $this->getResult()->setParsedLimit( $this->getModuleName(), $value );
01120                         } else {
01121                             $value = intval( $value );
01122                             $this->validateLimit(
01123                                 $paramName,
01124                                 $value,
01125                                 $min,
01126                                 $paramSettings[self::PARAM_MAX],
01127                                 $paramSettings[self::PARAM_MAX2]
01128                             );
01129                         }
01130                         break;
01131                     case 'boolean':
01132                         if ( $multi ) {
01133                             ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
01134                         }
01135                         break;
01136                     case 'timestamp':
01137                         if ( is_array( $value ) ) {
01138                             foreach ( $value as $key => $val ) {
01139                                 $value[$key] = $this->validateTimestamp( $val, $encParamName );
01140                             }
01141                         } else {
01142                             $value = $this->validateTimestamp( $value, $encParamName );
01143                         }
01144                         break;
01145                     case 'user':
01146                         if ( is_array( $value ) ) {
01147                             foreach ( $value as $key => $val ) {
01148                                 $value[$key] = $this->validateUser( $val, $encParamName );
01149                             }
01150                         } else {
01151                             $value = $this->validateUser( $value, $encParamName );
01152                         }
01153                         break;
01154                     case 'upload': // nothing to do
01155                         break;
01156                     default:
01157                         ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
01158                 }
01159             }
01160 
01161             // Throw out duplicates if requested
01162             if ( !$dupes && is_array( $value ) ) {
01163                 $value = array_unique( $value );
01164             }
01165 
01166             // Set a warning if a deprecated parameter has been passed
01167             if ( $deprecated && $value !== false ) {
01168                 $this->setWarning( "The $encParamName parameter has been deprecated." );
01169             }
01170         } elseif ( $required ) {
01171             $this->dieUsageMsg( array( 'missingparam', $paramName ) );
01172         }
01173 
01174         return $value;
01175     }
01176 
01190     protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
01191         if ( trim( $value ) === '' && $allowMultiple ) {
01192             return array();
01193         }
01194 
01195         // This is a bit awkward, but we want to avoid calling canApiHighLimits()
01196         // because it unstubs $wgUser
01197         $valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
01198         $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
01199             ? self::LIMIT_SML2
01200             : self::LIMIT_SML1;
01201 
01202         if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
01203             $this->setWarning( "Too many values supplied for parameter '$valueName': " .
01204                 "the limit is $sizeLimit" );
01205         }
01206 
01207         if ( !$allowMultiple && count( $valuesList ) != 1 ) {
01208             // Bug 33482 - Allow entries with | in them for non-multiple values
01209             if ( in_array( $value, $allowedValues, true ) ) {
01210                 return $value;
01211             }
01212 
01213             $possibleValues = is_array( $allowedValues )
01214                 ? "of '" . implode( "', '", $allowedValues ) . "'"
01215                 : '';
01216             $this->dieUsage(
01217                 "Only one $possibleValues is allowed for parameter '$valueName'",
01218                 "multival_$valueName"
01219             );
01220         }
01221 
01222         if ( is_array( $allowedValues ) ) {
01223             // Check for unknown values
01224             $unknown = array_diff( $valuesList, $allowedValues );
01225             if ( count( $unknown ) ) {
01226                 if ( $allowMultiple ) {
01227                     $s = count( $unknown ) > 1 ? 's' : '';
01228                     $vals = implode( ", ", $unknown );
01229                     $this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
01230                 } else {
01231                     $this->dieUsage(
01232                         "Unrecognized value for parameter '$valueName': {$valuesList[0]}",
01233                         "unknown_$valueName"
01234                     );
01235                 }
01236             }
01237             // Now throw them out
01238             $valuesList = array_intersect( $valuesList, $allowedValues );
01239         }
01240 
01241         return $allowMultiple ? $valuesList : $valuesList[0];
01242     }
01243 
01254     function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
01255         if ( !is_null( $min ) && $value < $min ) {
01256 
01257             $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
01258             $this->warnOrDie( $msg, $enforceLimits );
01259             $value = $min;
01260         }
01261 
01262         // Minimum is always validated, whereas maximum is checked only if not
01263         // running in internal call mode
01264         if ( $this->getMain()->isInternalMode() ) {
01265             return;
01266         }
01267 
01268         // Optimization: do not check user's bot status unless really needed -- skips db query
01269         // assumes $botMax >= $max
01270         if ( !is_null( $max ) && $value > $max ) {
01271             if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
01272                 if ( $value > $botMax ) {
01273                     $msg = $this->encodeParamName( $paramName ) .
01274                         " may not be over $botMax (set to $value) for bots or sysops";
01275                     $this->warnOrDie( $msg, $enforceLimits );
01276                     $value = $botMax;
01277                 }
01278             } else {
01279                 $msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
01280                 $this->warnOrDie( $msg, $enforceLimits );
01281                 $value = $max;
01282             }
01283         }
01284     }
01285 
01292     function validateTimestamp( $value, $encParamName ) {
01293         $unixTimestamp = wfTimestamp( TS_UNIX, $value );
01294         if ( $unixTimestamp === false ) {
01295             $this->dieUsage(
01296                 "Invalid value '$value' for timestamp parameter $encParamName",
01297                 "badtimestamp_{$encParamName}"
01298             );
01299         }
01300 
01301         return wfTimestamp( TS_MW, $unixTimestamp );
01302     }
01303 
01310     private function validateUser( $value, $encParamName ) {
01311         $title = Title::makeTitleSafe( NS_USER, $value );
01312         if ( $title === null ) {
01313             $this->dieUsage(
01314                 "Invalid value '$value' for user parameter $encParamName",
01315                 "baduser_{$encParamName}"
01316             );
01317         }
01318 
01319         return $title->getText();
01320     }
01321 
01328     private function warnOrDie( $msg, $enforceLimits = false ) {
01329         if ( $enforceLimits ) {
01330             $this->dieUsage( $msg, 'integeroutofrange' );
01331         }
01332 
01333         $this->setWarning( $msg );
01334     }
01335 
01342     public static function truncateArray( &$arr, $limit ) {
01343         $modified = false;
01344         while ( count( $arr ) > $limit ) {
01345             array_pop( $arr );
01346             $modified = true;
01347         }
01348 
01349         return $modified;
01350     }
01351 
01364     public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
01365         Profiler::instance()->close();
01366         throw new UsageException(
01367             $description,
01368             $this->encodeParamName( $errorCode ),
01369             $httpRespCode,
01370             $extradata
01371         );
01372     }
01373 
01381     public function getErrorFromStatus( $status ) {
01382         if ( $status->isGood() ) {
01383             throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
01384         }
01385 
01386         $errors = $status->getErrorsArray();
01387         if ( !$errors ) {
01388             // No errors? Assume the warnings should be treated as errors
01389             $errors = $status->getWarningsArray();
01390         }
01391         if ( !$errors ) {
01392             // Still no errors? Punt
01393             $errors = array( array( 'unknownerror-nocode' ) );
01394         }
01395 
01396         // Cannot use dieUsageMsg() because extensions might return custom
01397         // error messages.
01398         if ( $errors[0] instanceof Message ) {
01399             $msg = $errors[0];
01400             $code = $msg->getKey();
01401         } else {
01402             $code = array_shift( $errors[0] );
01403             $msg = wfMessage( $code, $errors[0] );
01404         }
01405         if ( isset( ApiBase::$messageMap[$code] ) ) {
01406             // Translate message to code, for backwards compatability
01407             $code = ApiBase::$messageMap[$code]['code'];
01408         }
01409 
01410         return array( $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() );
01411     }
01412 
01420     public function dieStatus( $status ) {
01421 
01422         list( $code, $msg ) = $this->getErrorFromStatus( $status );
01423         $this->dieUsage( $msg, $code );
01424     }
01425 
01426     // @codingStandardsIgnoreStart Allow long lines. Cannot split these.
01430     public static $messageMap = array(
01431         // This one MUST be present, or dieUsageMsg() will recurse infinitely
01432         'unknownerror' => array( 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ),
01433         'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
01434 
01435         // Messages from Title::getUserPermissionsErrors()
01436         'ns-specialprotected' => array(
01437             'code' => 'unsupportednamespace',
01438             'info' => "Pages in the Special namespace can't be edited"
01439         ),
01440         'protectedinterface' => array(
01441             'code' => 'protectednamespace-interface',
01442             'info' => "You're not allowed to edit interface messages"
01443         ),
01444         'namespaceprotected' => array(
01445             'code' => 'protectednamespace',
01446             'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
01447         ),
01448         'customcssprotected' => array(
01449             'code' => 'customcssprotected',
01450             'info' => "You're not allowed to edit custom CSS pages"
01451         ),
01452         'customjsprotected' => array(
01453             'code' => 'customjsprotected',
01454             'info' => "You're not allowed to edit custom JavaScript pages"
01455         ),
01456         'cascadeprotected' => array(
01457             'code' => 'cascadeprotected',
01458             'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
01459         ),
01460         'protectedpagetext' => array(
01461             'code' => 'protectedpage',
01462             'info' => "The \"\$1\" right is required to edit this page"
01463         ),
01464         'protect-cantedit' => array(
01465             'code' => 'cantedit',
01466             'info' => "You can't protect this page because you can't edit it"
01467         ),
01468         'badaccess-group0' => array(
01469             'code' => 'permissiondenied',
01470             'info' => "Permission denied"
01471         ), // Generic permission denied message
01472         'badaccess-groups' => array(
01473             'code' => 'permissiondenied',
01474             'info' => "Permission denied"
01475         ),
01476         'titleprotected' => array(
01477             'code' => 'protectedtitle',
01478             'info' => "This title has been protected from creation"
01479         ),
01480         'nocreate-loggedin' => array(
01481             'code' => 'cantcreate',
01482             'info' => "You don't have permission to create new pages"
01483         ),
01484         'nocreatetext' => array(
01485             'code' => 'cantcreate-anon',
01486             'info' => "Anonymous users can't create new pages"
01487         ),
01488         'movenologintext' => array(
01489             'code' => 'cantmove-anon',
01490             'info' => "Anonymous users can't move pages"
01491         ),
01492         'movenotallowed' => array(
01493             'code' => 'cantmove',
01494             'info' => "You don't have permission to move pages"
01495         ),
01496         'confirmedittext' => array(
01497             'code' => 'confirmemail',
01498             'info' => "You must confirm your email address before you can edit"
01499         ),
01500         'blockedtext' => array(
01501             'code' => 'blocked',
01502             'info' => "You have been blocked from editing"
01503         ),
01504         'autoblockedtext' => array(
01505             'code' => 'autoblocked',
01506             'info' => "Your IP address has been blocked automatically, because it was used by a blocked user"
01507         ),
01508 
01509         // Miscellaneous interface messages
01510         'actionthrottledtext' => array(
01511             'code' => 'ratelimited',
01512             'info' => "You've exceeded your rate limit. Please wait some time and try again"
01513         ),
01514         'alreadyrolled' => array(
01515             'code' => 'alreadyrolled',
01516             'info' => "The page you tried to rollback was already rolled back"
01517         ),
01518         'cantrollback' => array(
01519             'code' => 'onlyauthor',
01520             'info' => "The page you tried to rollback only has one author"
01521         ),
01522         'readonlytext' => array(
01523             'code' => 'readonly',
01524             'info' => "The wiki is currently in read-only mode"
01525         ),
01526         'sessionfailure' => array(
01527             'code' => 'badtoken',
01528             'info' => "Invalid token" ),
01529         'cannotdelete' => array(
01530             'code' => 'cantdelete',
01531             'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
01532         ),
01533         'notanarticle' => array(
01534             'code' => 'missingtitle',
01535             'info' => "The page you requested doesn't exist"
01536         ),
01537         'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself"
01538         ),
01539         'immobile_namespace' => array(
01540             'code' => 'immobilenamespace',
01541             'info' => "You tried to move pages from or to a namespace that is protected from moving"
01542         ),
01543         'articleexists' => array(
01544             'code' => 'articleexists',
01545             'info' => "The destination article already exists and is not a redirect to the source article"
01546         ),
01547         'protectedpage' => array(
01548             'code' => 'protectedpage',
01549             'info' => "You don't have permission to perform this move"
01550         ),
01551         'hookaborted' => array(
01552             'code' => 'hookaborted',
01553             'info' => "The modification you tried to make was aborted by an extension hook"
01554         ),
01555         'cantmove-titleprotected' => array(
01556             'code' => 'protectedtitle',
01557             'info' => "The destination article has been protected from creation"
01558         ),
01559         'imagenocrossnamespace' => array(
01560             'code' => 'nonfilenamespace',
01561             'info' => "Can't move a file to a non-file namespace"
01562         ),
01563         'imagetypemismatch' => array(
01564             'code' => 'filetypemismatch',
01565             'info' => "The new file extension doesn't match its type"
01566         ),
01567         // 'badarticleerror' => shouldn't happen
01568         // 'badtitletext' => shouldn't happen
01569         'ip_range_invalid' => array( 'code' => 'invalidrange', 'info' => "Invalid IP range" ),
01570         'range_block_disabled' => array(
01571             'code' => 'rangedisabled',
01572             'info' => "Blocking IP ranges has been disabled"
01573         ),
01574         'nosuchusershort' => array(
01575             'code' => 'nosuchuser',
01576             'info' => "The user you specified doesn't exist"
01577         ),
01578         'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
01579         'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
01580         'ipb_already_blocked' => array(
01581             'code' => 'alreadyblocked',
01582             'info' => "The user you tried to block was already blocked"
01583         ),
01584         'ipb_blocked_as_range' => array(
01585             'code' => 'blockedasrange',
01586             '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."
01587         ),
01588         'ipb_cant_unblock' => array(
01589             'code' => 'cantunblock',
01590             'info' => "The block you specified was not found. It may have been unblocked already"
01591         ),
01592         'mailnologin' => array(
01593             'code' => 'cantsend',
01594             '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"
01595         ),
01596         'ipbblocked' => array(
01597             'code' => 'ipbblocked',
01598             'info' => 'You cannot block or unblock users while you are yourself blocked'
01599         ),
01600         'ipbnounblockself' => array(
01601             'code' => 'ipbnounblockself',
01602             'info' => 'You are not allowed to unblock yourself'
01603         ),
01604         'usermaildisabled' => array(
01605             'code' => 'usermaildisabled',
01606             'info' => "User email has been disabled"
01607         ),
01608         'blockedemailuser' => array(
01609             'code' => 'blockedfrommail',
01610             'info' => "You have been blocked from sending email"
01611         ),
01612         'notarget' => array(
01613             'code' => 'notarget',
01614             'info' => "You have not specified a valid target for this action"
01615         ),
01616         'noemail' => array(
01617             'code' => 'noemail',
01618             'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users"
01619         ),
01620         'rcpatroldisabled' => array(
01621             'code' => 'patroldisabled',
01622             'info' => "Patrolling is disabled on this wiki"
01623         ),
01624         'markedaspatrollederror-noautopatrol' => array(
01625             'code' => 'noautopatrol',
01626             'info' => "You don't have permission to patrol your own changes"
01627         ),
01628         'delete-toobig' => array(
01629             'code' => 'bigdelete',
01630             'info' => "You can't delete this page because it has more than \$1 revisions"
01631         ),
01632         'movenotallowedfile' => array(
01633             'code' => 'cantmovefile',
01634             'info' => "You don't have permission to move files"
01635         ),
01636         'userrights-no-interwiki' => array(
01637             'code' => 'nointerwikiuserrights',
01638             'info' => "You don't have permission to change user rights on other wikis"
01639         ),
01640         'userrights-nodatabase' => array(
01641             'code' => 'nosuchdatabase',
01642             'info' => "Database \"\$1\" does not exist or is not local"
01643         ),
01644         'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01645         'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01646         'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
01647         'import-rootpage-invalid' => array(
01648             'code' => 'import-rootpage-invalid',
01649             'info' => 'Root page is an invalid title'
01650         ),
01651         'import-rootpage-nosubpage' => array(
01652             'code' => 'import-rootpage-nosubpage',
01653             'info' => 'Namespace "$1" of the root page does not allow subpages'
01654         ),
01655 
01656         // API-specific messages
01657         'readrequired' => array(
01658             'code' => 'readapidenied',
01659             'info' => "You need read permission to use this module"
01660         ),
01661         'writedisabled' => array(
01662             'code' => 'noapiwrite',
01663             '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"
01664         ),
01665         'writerequired' => array(
01666             'code' => 'writeapidenied',
01667             'info' => "You're not allowed to edit this wiki through the API"
01668         ),
01669         'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
01670         'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ),
01671         'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
01672         'nosuchrevid' => array( 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ),
01673         'nosuchuser' => array( 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ),
01674         'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01675         'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ),
01676         'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ),
01677         'create-titleexists' => array(
01678             'code' => 'create-titleexists',
01679             'info' => "Existing titles can't be protected with 'create'"
01680         ),
01681         'missingtitle-createonly' => array(
01682             'code' => 'missingtitle-createonly',
01683             'info' => "Missing titles can only be protected with 'create'"
01684         ),
01685         'cantblock' => array( 'code' => 'cantblock',
01686             'info' => "You don't have permission to block users"
01687         ),
01688         'canthide' => array(
01689             'code' => 'canthide',
01690             'info' => "You don't have permission to hide user names from the block log"
01691         ),
01692         'cantblock-email' => array(
01693             'code' => 'cantblock-email',
01694             'info' => "You don't have permission to block users from sending email through the wiki"
01695         ),
01696         'unblock-notarget' => array(
01697             'code' => 'notarget',
01698             'info' => "Either the id or the user parameter must be set"
01699         ),
01700         'unblock-idanduser' => array(
01701             'code' => 'idanduser',
01702             'info' => "The id and user parameters can't be used together"
01703         ),
01704         'cantunblock' => array(
01705             'code' => 'permissiondenied',
01706             'info' => "You don't have permission to unblock users"
01707         ),
01708         'cannotundelete' => array(
01709             'code' => 'cantundelete',
01710             'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
01711         ),
01712         'permdenied-undelete' => array(
01713             'code' => 'permissiondenied',
01714             'info' => "You don't have permission to restore deleted revisions"
01715         ),
01716         'createonly-exists' => array(
01717             'code' => 'articleexists',
01718             'info' => "The article you tried to create has been created already"
01719         ),
01720         'nocreate-missing' => array(
01721             'code' => 'missingtitle',
01722             'info' => "The article you tried to edit doesn't exist"
01723         ),
01724         'nosuchrcid' => array(
01725             'code' => 'nosuchrcid',
01726             'info' => "There is no change with rcid \"\$1\""
01727         ),
01728         'protect-invalidaction' => array(
01729             'code' => 'protect-invalidaction',
01730             'info' => "Invalid protection type \"\$1\""
01731         ),
01732         'protect-invalidlevel' => array(
01733             'code' => 'protect-invalidlevel',
01734             'info' => "Invalid protection level \"\$1\""
01735         ),
01736         'toofewexpiries' => array(
01737             'code' => 'toofewexpiries',
01738             'info' => "\$1 expiry timestamps were provided where \$2 were needed"
01739         ),
01740         'cantimport' => array(
01741             'code' => 'cantimport',
01742             'info' => "You don't have permission to import pages"
01743         ),
01744         'cantimport-upload' => array(
01745             'code' => 'cantimport-upload',
01746             'info' => "You don't have permission to import uploaded pages"
01747         ),
01748         'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ),
01749         'importuploaderrorsize' => array(
01750             'code' => 'filetoobig',
01751             'info' => 'The file you uploaded is bigger than the maximum upload size'
01752         ),
01753         'importuploaderrorpartial' => array(
01754             'code' => 'partialupload',
01755             'info' => 'The file was only partially uploaded'
01756         ),
01757         'importuploaderrortemp' => array(
01758             'code' => 'notempdir',
01759             'info' => 'The temporary upload directory is missing'
01760         ),
01761         'importcantopen' => array(
01762             'code' => 'cantopenfile',
01763             'info' => "Couldn't open the uploaded file"
01764         ),
01765         'import-noarticle' => array(
01766             'code' => 'badinterwiki',
01767             'info' => 'Invalid interwiki title specified'
01768         ),
01769         'importbadinterwiki' => array(
01770             'code' => 'badinterwiki',
01771             'info' => 'Invalid interwiki title specified'
01772         ),
01773         'import-unknownerror' => array(
01774             'code' => 'import-unknownerror',
01775             'info' => "Unknown error on import: \"\$1\""
01776         ),
01777         'cantoverwrite-sharedfile' => array(
01778             'code' => 'cantoverwrite-sharedfile',
01779             'info' => 'The target file exists on a shared repository and you do not have permission to override it'
01780         ),
01781         'sharedfile-exists' => array(
01782             'code' => 'fileexists-sharedrepo-perm',
01783             'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
01784         ),
01785         'mustbeposted' => array(
01786             'code' => 'mustbeposted',
01787             'info' => "The \$1 module requires a POST request"
01788         ),
01789         'show' => array(
01790             'code' => 'show',
01791             'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
01792         ),
01793         'specialpage-cantexecute' => array(
01794             'code' => 'specialpage-cantexecute',
01795             'info' => "You don't have permission to view the results of this special page"
01796         ),
01797         'invalidoldimage' => array(
01798             'code' => 'invalidoldimage',
01799             'info' => 'The oldimage parameter has invalid format'
01800         ),
01801         'nodeleteablefile' => array(
01802             'code' => 'nodeleteablefile',
01803             'info' => 'No such old version of the file'
01804         ),
01805         'fileexists-forbidden' => array(
01806             'code' => 'fileexists-forbidden',
01807             'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
01808         ),
01809         'fileexists-shared-forbidden' => array(
01810             'code' => 'fileexists-shared-forbidden',
01811             'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
01812         ),
01813         'filerevert-badversion' => array(
01814             'code' => 'filerevert-badversion',
01815             'info' => 'There is no previous local version of this file with the provided timestamp.'
01816         ),
01817 
01818         // ApiEditPage messages
01819         'noimageredirect-anon' => array(
01820             'code' => 'noimageredirect-anon',
01821             'info' => "Anonymous users can't create image redirects"
01822         ),
01823         'noimageredirect-logged' => array(
01824             'code' => 'noimageredirect',
01825             'info' => "You don't have permission to create image redirects"
01826         ),
01827         'spamdetected' => array(
01828             'code' => 'spamdetected',
01829             'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
01830         ),
01831         'contenttoobig' => array(
01832             'code' => 'contenttoobig',
01833             'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
01834         ),
01835         'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
01836         'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),
01837         'wasdeleted' => array(
01838             'code' => 'pagedeleted',
01839             'info' => "The page has been deleted since you fetched its timestamp"
01840         ),
01841         'blankpage' => array(
01842             'code' => 'emptypage',
01843             'info' => "Creating new, empty pages is not allowed"
01844         ),
01845         'editconflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
01846         'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
01847         'missingtext' => array(
01848             'code' => 'notext',
01849             'info' => "One of the text, appendtext, prependtext and undo parameters must be set"
01850         ),
01851         'emptynewsection' => array(
01852             'code' => 'emptynewsection',
01853             'info' => 'Creating empty new sections is not possible.'
01854         ),
01855         'revwrongpage' => array(
01856             'code' => 'revwrongpage',
01857             'info' => "r\$1 is not a revision of \"\$2\""
01858         ),
01859         'undo-failure' => array(
01860             'code' => 'undofailure',
01861             'info' => 'Undo failed due to conflicting intermediate edits'
01862         ),
01863 
01864         // Messages from WikiPage::doEit()
01865         'edit-hook-aborted' => array(
01866             'code' => 'edit-hook-aborted',
01867             'info' => "Your edit was aborted by an ArticleSave hook"
01868         ),
01869         'edit-gone-missing' => array(
01870             'code' => 'edit-gone-missing',
01871             'info' => "The page you tried to edit doesn't seem to exist anymore"
01872         ),
01873         'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
01874         'edit-already-exists' => array(
01875             'code' => 'edit-already-exists',
01876             'info' => 'It seems the page you tried to create already exist'
01877         ),
01878 
01879         // uploadMsgs
01880         'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ),
01881         'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
01882         'uploaddisabled' => array(
01883             'code' => 'uploaddisabled',
01884             'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
01885         ),
01886         'copyuploaddisabled' => array(
01887             'code' => 'copyuploaddisabled',
01888             'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
01889         ),
01890         'copyuploadbaddomain' => array(
01891             'code' => 'copyuploadbaddomain',
01892             'info' => 'Uploads by URL are not allowed from this domain.'
01893         ),
01894         'copyuploadbadurl' => array(
01895             'code' => 'copyuploadbadurl',
01896             'info' => 'Upload not allowed from this URL.'
01897         ),
01898 
01899         'filename-tooshort' => array(
01900             'code' => 'filename-tooshort',
01901             'info' => 'The filename is too short'
01902         ),
01903         'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
01904         'illegal-filename' => array(
01905             'code' => 'illegal-filename',
01906             'info' => 'The filename is not allowed'
01907         ),
01908         'filetype-missing' => array(
01909             'code' => 'filetype-missing',
01910             'info' => 'The file is missing an extension'
01911         ),
01912 
01913         'mustbeloggedin' => array( 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' )
01914     );
01915     // @codingStandardsIgnoreEnd
01916 
01920     public function dieReadOnly() {
01921         $parsed = $this->parseMsg( array( 'readonlytext' ) );
01922         $this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
01923             array( 'readonlyreason' => wfReadOnlyReason() ) );
01924     }
01925 
01930     public function dieUsageMsg( $error ) {
01931         # most of the time we send a 1 element, so we might as well send it as
01932         # a string and make this an array here.
01933         if ( is_string( $error ) ) {
01934             $error = array( $error );
01935         }
01936         $parsed = $this->parseMsg( $error );
01937         $this->dieUsage( $parsed['info'], $parsed['code'] );
01938     }
01939 
01946     public function dieUsageMsgOrDebug( $error ) {
01947         global $wgDebugAPI;
01948         if ( $wgDebugAPI !== true ) {
01949             $this->dieUsageMsg( $error );
01950         }
01951 
01952         if ( is_string( $error ) ) {
01953             $error = array( $error );
01954         }
01955 
01956         $parsed = $this->parseMsg( $error );
01957         $this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
01958     }
01959 
01966     protected function dieContinueUsageIf( $condition ) {
01967         if ( $condition ) {
01968             $this->dieUsage(
01969                 'Invalid continue param. You should pass the original value returned by the previous query',
01970                 'badcontinue' );
01971         }
01972     }
01973 
01979     public function parseMsg( $error ) {
01980         $error = (array)$error; // It seems strings sometimes make their way in here
01981         $key = array_shift( $error );
01982 
01983         // Check whether the error array was nested
01984         // array( array( <code>, <params> ), array( <another_code>, <params> ) )
01985         if ( is_array( $key ) ) {
01986             $error = $key;
01987             $key = array_shift( $error );
01988         }
01989 
01990         if ( isset( self::$messageMap[$key] ) ) {
01991             return array(
01992                 'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
01993                 'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
01994             );
01995         }
01996 
01997         // If the key isn't present, throw an "unknown error"
01998         return $this->parseMsg( array( 'unknownerror', $key ) );
01999     }
02000 
02007     protected static function dieDebug( $method, $message ) {
02008         throw new MWException( "Internal error in $method: $message" );
02009     }
02010 
02015     public function shouldCheckMaxlag() {
02016         return true;
02017     }
02018 
02023     public function isReadMode() {
02024         return true;
02025     }
02026 
02031     public function isWriteMode() {
02032         return false;
02033     }
02034 
02039     public function mustBePosted() {
02040         return false;
02041     }
02042 
02049     public function needsToken() {
02050         return false;
02051     }
02052 
02061     public function getTokenSalt() {
02062         return false;
02063     }
02064 
02071     public function getWatchlistUser( $params ) {
02072         if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
02073             $user = User::newFromName( $params['owner'], false );
02074             if ( !( $user && $user->getId() ) ) {
02075                 $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
02076             }
02077             $token = $user->getOption( 'watchlisttoken' );
02078             if ( $token == '' || $token != $params['token'] ) {
02079                 $this->dieUsage(
02080                     'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
02081                     'bad_wltoken'
02082                 );
02083             }
02084         } else {
02085             if ( !$this->getUser()->isLoggedIn() ) {
02086                 $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
02087             }
02088             if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
02089                 $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
02090             }
02091             $user = $this->getUser();
02092         }
02093 
02094         return $user;
02095     }
02096 
02101     public function getHelpUrls() {
02102         return false;
02103     }
02104 
02114     public function getPossibleErrors() {
02115         $ret = array();
02116 
02117         $params = $this->getFinalParams();
02118         if ( $params ) {
02119             foreach ( $params as $paramName => $paramSettings ) {
02120                 if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] )
02121                     && $paramSettings[ApiBase::PARAM_REQUIRED]
02122                 ) {
02123                     $ret[] = array( 'missingparam', $paramName );
02124                 }
02125             }
02126             if ( array_key_exists( 'continue', $params ) ) {
02127                 $ret[] = array(
02128                     'code' => 'badcontinue',
02129                     'info' => 'Invalid continue param. You should pass the ' .
02130                         'original value returned by the previous query'
02131                 );
02132             }
02133         }
02134 
02135         if ( $this->mustBePosted() ) {
02136             $ret[] = array( 'mustbeposted', $this->getModuleName() );
02137         }
02138 
02139         if ( $this->isReadMode() ) {
02140             $ret[] = array( 'readrequired' );
02141         }
02142 
02143         if ( $this->isWriteMode() ) {
02144             $ret[] = array( 'writerequired' );
02145             $ret[] = array( 'writedisabled' );
02146         }
02147 
02148         if ( $this->needsToken() ) {
02149             if ( !isset( $params['token'][ApiBase::PARAM_REQUIRED] )
02150                 || !$params['token'][ApiBase::PARAM_REQUIRED]
02151             ) {
02152                 // Add token as possible missing parameter, if not already done
02153                 $ret[] = array( 'missingparam', 'token' );
02154             }
02155             $ret[] = array( 'sessionfailure' );
02156         }
02157 
02158         return $ret;
02159     }
02160 
02168     public function getFinalPossibleErrors() {
02169         $possibleErrors = $this->getPossibleErrors();
02170         wfRunHooks( 'APIGetPossibleErrors', array( $this, &$possibleErrors ) );
02171 
02172         return $possibleErrors;
02173     }
02174 
02181     public function parseErrors( $errors ) {
02182         $ret = array();
02183 
02184         foreach ( $errors as $row ) {
02185             if ( isset( $row['code'] ) && isset( $row['info'] ) ) {
02186                 $ret[] = $row;
02187             } else {
02188                 $ret[] = $this->parseMsg( $row );
02189             }
02190         }
02191 
02192         return $ret;
02193     }
02194 
02198     private $mTimeIn = 0, $mModuleTime = 0;
02199 
02203     public function profileIn() {
02204         if ( $this->mTimeIn !== 0 ) {
02205             ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileOut()' );
02206         }
02207         $this->mTimeIn = microtime( true );
02208         wfProfileIn( $this->getModuleProfileName() );
02209     }
02210 
02214     public function profileOut() {
02215         if ( $this->mTimeIn === 0 ) {
02216             ApiBase::dieDebug( __METHOD__, 'Called without calling profileIn() first' );
02217         }
02218         if ( $this->mDBTimeIn !== 0 ) {
02219             ApiBase::dieDebug(
02220                 __METHOD__,
02221                 'Must be called after database profiling is done with profileDBOut()'
02222             );
02223         }
02224 
02225         $this->mModuleTime += microtime( true ) - $this->mTimeIn;
02226         $this->mTimeIn = 0;
02227         wfProfileOut( $this->getModuleProfileName() );
02228     }
02229 
02234     public function safeProfileOut() {
02235         if ( $this->mTimeIn !== 0 ) {
02236             if ( $this->mDBTimeIn !== 0 ) {
02237                 $this->profileDBOut();
02238             }
02239             $this->profileOut();
02240         }
02241     }
02242 
02247     public function getProfileTime() {
02248         if ( $this->mTimeIn !== 0 ) {
02249             ApiBase::dieDebug( __METHOD__, 'Called without calling profileOut() first' );
02250         }
02251 
02252         return $this->mModuleTime;
02253     }
02254 
02258     private $mDBTimeIn = 0, $mDBTime = 0;
02259 
02263     public function profileDBIn() {
02264         if ( $this->mTimeIn === 0 ) {
02265             ApiBase::dieDebug(
02266                 __METHOD__,
02267                 'Must be called while profiling the entire module with profileIn()'
02268             );
02269         }
02270         if ( $this->mDBTimeIn !== 0 ) {
02271             ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileDBOut()' );
02272         }
02273         $this->mDBTimeIn = microtime( true );
02274         wfProfileIn( $this->getModuleProfileName( true ) );
02275     }
02276 
02280     public function profileDBOut() {
02281         if ( $this->mTimeIn === 0 ) {
02282             ApiBase::dieDebug( __METHOD__, 'Must be called while profiling ' .
02283                 'the entire module with profileIn()' );
02284         }
02285         if ( $this->mDBTimeIn === 0 ) {
02286             ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBIn() first' );
02287         }
02288 
02289         $time = microtime( true ) - $this->mDBTimeIn;
02290         $this->mDBTimeIn = 0;
02291 
02292         $this->mDBTime += $time;
02293         $this->getMain()->mDBTime += $time;
02294         wfProfileOut( $this->getModuleProfileName( true ) );
02295     }
02296 
02301     public function getProfileDBTime() {
02302         if ( $this->mDBTimeIn !== 0 ) {
02303             ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBOut() first' );
02304         }
02305 
02306         return $this->mDBTime;
02307     }
02308 
02313     protected function getDB() {
02314         if ( !isset( $this->mSlaveDB ) ) {
02315             $this->profileDBIn();
02316             $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
02317             $this->profileDBOut();
02318         }
02319 
02320         return $this->mSlaveDB;
02321     }
02322 
02329     public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
02330         print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
02331         var_export( $value );
02332         if ( $backtrace ) {
02333             print "\n" . wfBacktrace();
02334         }
02335         print "\n</pre>\n";
02336     }
02337 }