MediaWiki  REL1_24
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     const LIMIT_BIG1 = 500; // Fast query, std user limit
00069     const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
00070     const LIMIT_SML1 = 50; // Slow query, std user limit
00071     const LIMIT_SML2 = 500; // Slow query, bot/sysop limit
00072 
00078     const GET_VALUES_FOR_HELP = 1;
00079 
00081     private $mMainModule;
00083     private $mModuleName, $mModulePrefix;
00084     private $mSlaveDB = null;
00085     private $mParamCache = array();
00086 
00092     public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
00093         $this->mMainModule = $mainModule;
00094         $this->mModuleName = $moduleName;
00095         $this->mModulePrefix = $modulePrefix;
00096 
00097         if ( !$this->isMain() ) {
00098             $this->setContext( $mainModule->getContext() );
00099         }
00100     }
00101 
00102 
00103     /************************************************************************/
00124     abstract public function execute();
00125 
00131     public function getModuleManager() {
00132         return null;
00133     }
00134 
00141     public function getCustomPrinter() {
00142         return null;
00143     }
00144 
00149     protected function getDescription() {
00150         return false;
00151     }
00152 
00157     protected function getExamples() {
00158         return false;
00159     }
00160 
00165     public function getHelpUrls() {
00166         return false;
00167     }
00168 
00181     protected function getAllowedParams( /* $flags = 0 */ ) {
00182         // int $flags is not declared because it causes "Strict standards"
00183         // warning. Most derived classes do not implement it.
00184         return false;
00185     }
00186 
00193     protected function getParamDescription() {
00194         return false;
00195     }
00196 
00201     public function shouldCheckMaxlag() {
00202         return true;
00203     }
00204 
00209     public function isReadMode() {
00210         return true;
00211     }
00212 
00217     public function isWriteMode() {
00218         return false;
00219     }
00220 
00225     public function mustBePosted() {
00226         return $this->needsToken() !== false;
00227     }
00228 
00249     public function needsToken() {
00250         return false;
00251     }
00252 
00262     protected function getWebUITokenSalt( array $params ) {
00263         return null;
00264     }
00265 
00268     /************************************************************************/
00277     public function getModuleName() {
00278         return $this->mModuleName;
00279     }
00280 
00285     public function getModulePrefix() {
00286         return $this->mModulePrefix;
00287     }
00288 
00293     public function getMain() {
00294         return $this->mMainModule;
00295     }
00296 
00302     public function isMain() {
00303         return $this === $this->mMainModule;
00304     }
00305 
00310     public function getResult() {
00311         // Main module has getResult() method overridden
00312         // Safety - avoid infinite loop:
00313         if ( $this->isMain() ) {
00314             ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
00315         }
00316 
00317         return $this->getMain()->getResult();
00318     }
00319 
00324     public function getResultData() {
00325         return $this->getResult()->getData();
00326     }
00327 
00332     protected function getDB() {
00333         if ( !isset( $this->mSlaveDB ) ) {
00334             $this->profileDBIn();
00335             $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
00336             $this->profileDBOut();
00337         }
00338 
00339         return $this->mSlaveDB;
00340     }
00341 
00348     public function getFinalDescription() {
00349         $desc = $this->getDescription();
00350         wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
00351 
00352         return $desc;
00353     }
00354 
00363     public function getFinalParams( $flags = 0 ) {
00364         $params = $this->getAllowedParams( $flags );
00365 
00366         if ( $this->needsToken() ) {
00367             $params['token'] = array(
00368                 ApiBase::PARAM_TYPE => 'string',
00369                 ApiBase::PARAM_REQUIRED => true,
00370             );
00371         }
00372 
00373         wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
00374 
00375         return $params;
00376     }
00377 
00384     public function getFinalParamDescription() {
00385         $desc = $this->getParamDescription();
00386 
00387         $tokenType = $this->needsToken();
00388         if ( $tokenType ) {
00389             if ( !isset( $desc['token'] ) ) {
00390                 $desc['token'] = array();
00391             } elseif ( !is_array( $desc['token'] ) ) {
00392                 // We ignore a plain-string token, because it's probably an
00393                 // extension that is supplying the string for BC.
00394                 $desc['token'] = array();
00395             }
00396             array_unshift( $desc['token'],
00397                 "A '$tokenType' token retrieved from action=query&meta=tokens"
00398             );
00399         }
00400 
00401         wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
00402 
00403         return $desc;
00404     }
00405 
00408     /************************************************************************/
00419     public function encodeParamName( $paramName ) {
00420         return $this->mModulePrefix . $paramName;
00421     }
00422 
00432     public function extractRequestParams( $parseLimit = true ) {
00433         // Cache parameters, for performance and to avoid bug 24564.
00434         if ( !isset( $this->mParamCache[$parseLimit] ) ) {
00435             $params = $this->getFinalParams();
00436             $results = array();
00437 
00438             if ( $params ) { // getFinalParams() can return false
00439                 foreach ( $params as $paramName => $paramSettings ) {
00440                     $results[$paramName] = $this->getParameterFromSettings(
00441                         $paramName, $paramSettings, $parseLimit );
00442                 }
00443             }
00444             $this->mParamCache[$parseLimit] = $results;
00445         }
00446 
00447         return $this->mParamCache[$parseLimit];
00448     }
00449 
00456     protected function getParameter( $paramName, $parseLimit = true ) {
00457         $params = $this->getFinalParams();
00458         $paramSettings = $params[$paramName];
00459 
00460         return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
00461     }
00462 
00469     public function requireOnlyOneParameter( $params, $required /*...*/ ) {
00470         $required = func_get_args();
00471         array_shift( $required );
00472         $p = $this->getModulePrefix();
00473 
00474         $intersection = array_intersect( array_keys( array_filter( $params,
00475             array( $this, "parameterNotEmpty" ) ) ), $required );
00476 
00477         if ( count( $intersection ) > 1 ) {
00478             $this->dieUsage(
00479                 "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
00480                 'invalidparammix' );
00481         } elseif ( count( $intersection ) == 0 ) {
00482             $this->dieUsage(
00483                 "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
00484                 'missingparam'
00485             );
00486         }
00487     }
00488 
00495     public function requireMaxOneParameter( $params, $required /*...*/ ) {
00496         $required = func_get_args();
00497         array_shift( $required );
00498         $p = $this->getModulePrefix();
00499 
00500         $intersection = array_intersect( array_keys( array_filter( $params,
00501             array( $this, "parameterNotEmpty" ) ) ), $required );
00502 
00503         if ( count( $intersection ) > 1 ) {
00504             $this->dieUsage(
00505                 "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
00506                 'invalidparammix'
00507             );
00508         }
00509     }
00510 
00518     public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
00519         $required = func_get_args();
00520         array_shift( $required );
00521         $p = $this->getModulePrefix();
00522 
00523         $intersection = array_intersect(
00524             array_keys( array_filter( $params, array( $this, "parameterNotEmpty" ) ) ),
00525             $required
00526         );
00527 
00528         if ( count( $intersection ) == 0 ) {
00529             $this->dieUsage( "At least one of the parameters {$p}" .
00530                 implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
00531         }
00532     }
00533 
00540     private function parameterNotEmpty( $x ) {
00541         return !is_null( $x ) && $x !== false;
00542     }
00543 
00555     public function getTitleOrPageId( $params, $load = false ) {
00556         $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
00557 
00558         $pageObj = null;
00559         if ( isset( $params['title'] ) ) {
00560             $titleObj = Title::newFromText( $params['title'] );
00561             if ( !$titleObj || $titleObj->isExternal() ) {
00562                 $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
00563             }
00564             if ( !$titleObj->canExist() ) {
00565                 $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
00566             }
00567             $pageObj = WikiPage::factory( $titleObj );
00568             if ( $load !== false ) {
00569                 $pageObj->loadPageData( $load );
00570             }
00571         } elseif ( isset( $params['pageid'] ) ) {
00572             if ( $load === false ) {
00573                 $load = 'fromdb';
00574             }
00575             $pageObj = WikiPage::newFromID( $params['pageid'], $load );
00576             if ( !$pageObj ) {
00577                 $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
00578             }
00579         }
00580 
00581         return $pageObj;
00582     }
00583 
00592     protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
00593 
00594         $userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS );
00595 
00596         switch ( $watchlist ) {
00597             case 'watch':
00598                 return true;
00599 
00600             case 'unwatch':
00601                 return false;
00602 
00603             case 'preferences':
00604                 # If the user is already watching, don't bother checking
00605                 if ( $userWatching ) {
00606                     return true;
00607                 }
00608                 # If no user option was passed, use watchdefault and watchcreations
00609                 if ( is_null( $userOption ) ) {
00610                     return $this->getUser()->getBoolOption( 'watchdefault' ) ||
00611                         $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
00612                 }
00613 
00614                 # Watch the article based on the user preference
00615                 return $this->getUser()->getBoolOption( $userOption );
00616 
00617             case 'nochange':
00618                 return $userWatching;
00619 
00620             default:
00621                 return $userWatching;
00622         }
00623     }
00624 
00634     protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
00635         // Some classes may decide to change parameter names
00636         $encParamName = $this->encodeParamName( $paramName );
00637 
00638         if ( !is_array( $paramSettings ) ) {
00639             $default = $paramSettings;
00640             $multi = false;
00641             $type = gettype( $paramSettings );
00642             $dupes = false;
00643             $deprecated = false;
00644             $required = false;
00645         } else {
00646             $default = isset( $paramSettings[self::PARAM_DFLT] )
00647                 ? $paramSettings[self::PARAM_DFLT]
00648                 : null;
00649             $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
00650                 ? $paramSettings[self::PARAM_ISMULTI]
00651                 : false;
00652             $type = isset( $paramSettings[self::PARAM_TYPE] )
00653                 ? $paramSettings[self::PARAM_TYPE]
00654                 : null;
00655             $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
00656                 ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
00657                 : false;
00658             $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
00659                 ? $paramSettings[self::PARAM_DEPRECATED]
00660                 : false;
00661             $required = isset( $paramSettings[self::PARAM_REQUIRED] )
00662                 ? $paramSettings[self::PARAM_REQUIRED]
00663                 : false;
00664 
00665             // When type is not given, and no choices, the type is the same as $default
00666             if ( !isset( $type ) ) {
00667                 if ( isset( $default ) ) {
00668                     $type = gettype( $default );
00669                 } else {
00670                     $type = 'NULL'; // allow everything
00671                 }
00672             }
00673         }
00674 
00675         if ( $type == 'boolean' ) {
00676             if ( isset( $default ) && $default !== false ) {
00677                 // Having a default value of anything other than 'false' is not allowed
00678                 ApiBase::dieDebug(
00679                     __METHOD__,
00680                     "Boolean param $encParamName's default is set to '$default'. " .
00681                         "Boolean parameters must default to false."
00682                 );
00683             }
00684 
00685             $value = $this->getMain()->getCheck( $encParamName );
00686         } elseif ( $type == 'upload' ) {
00687             if ( isset( $default ) ) {
00688                 // Having a default value is not allowed
00689                 ApiBase::dieDebug(
00690                     __METHOD__,
00691                     "File upload param $encParamName's default is set to " .
00692                         "'$default'. File upload parameters may not have a default." );
00693             }
00694             if ( $multi ) {
00695                 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00696             }
00697             $value = $this->getMain()->getUpload( $encParamName );
00698             if ( !$value->exists() ) {
00699                 // This will get the value without trying to normalize it
00700                 // (because trying to normalize a large binary file
00701                 // accidentally uploaded as a field fails spectacularly)
00702                 $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
00703                 if ( $value !== null ) {
00704                     $this->dieUsage(
00705                         "File upload param $encParamName is not a file upload; " .
00706                             "be sure to use multipart/form-data for your POST and include " .
00707                             "a filename in the Content-Disposition header.",
00708                         "badupload_{$encParamName}"
00709                     );
00710                 }
00711             }
00712         } else {
00713             $value = $this->getMain()->getVal( $encParamName, $default );
00714 
00715             if ( isset( $value ) && $type == 'namespace' ) {
00716                 $type = MWNamespace::getValidNamespaces();
00717             }
00718             if ( isset( $value ) && $type == 'submodule' ) {
00719                 $type = $this->getModuleManager()->getNames( $paramName );
00720             }
00721         }
00722 
00723         if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
00724             $value = $this->parseMultiValue(
00725                 $encParamName,
00726                 $value,
00727                 $multi,
00728                 is_array( $type ) ? $type : null
00729             );
00730         }
00731 
00732         // More validation only when choices were not given
00733         // choices were validated in parseMultiValue()
00734         if ( isset( $value ) ) {
00735             if ( !is_array( $type ) ) {
00736                 switch ( $type ) {
00737                     case 'NULL': // nothing to do
00738                         break;
00739                     case 'string':
00740                         if ( $required && $value === '' ) {
00741                             $this->dieUsageMsg( array( 'missingparam', $paramName ) );
00742                         }
00743                         break;
00744                     case 'integer': // Force everything using intval() and optionally validate limits
00745                         $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
00746                         $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
00747                         $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
00748                             ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
00749 
00750                         if ( is_array( $value ) ) {
00751                             $value = array_map( 'intval', $value );
00752                             if ( !is_null( $min ) || !is_null( $max ) ) {
00753                                 foreach ( $value as &$v ) {
00754                                     $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
00755                                 }
00756                             }
00757                         } else {
00758                             $value = intval( $value );
00759                             if ( !is_null( $min ) || !is_null( $max ) ) {
00760                                 $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
00761                             }
00762                         }
00763                         break;
00764                     case 'limit':
00765                         if ( !$parseLimit ) {
00766                             // Don't do any validation whatsoever
00767                             break;
00768                         }
00769                         if ( !isset( $paramSettings[self::PARAM_MAX] )
00770                             || !isset( $paramSettings[self::PARAM_MAX2] )
00771                         ) {
00772                             ApiBase::dieDebug(
00773                                 __METHOD__,
00774                                 "MAX1 or MAX2 are not defined for the limit $encParamName"
00775                             );
00776                         }
00777                         if ( $multi ) {
00778                             ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00779                         }
00780                         $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
00781                         if ( $value == 'max' ) {
00782                             $value = $this->getMain()->canApiHighLimits()
00783                                 ? $paramSettings[self::PARAM_MAX2]
00784                                 : $paramSettings[self::PARAM_MAX];
00785                             $this->getResult()->setParsedLimit( $this->getModuleName(), $value );
00786                         } else {
00787                             $value = intval( $value );
00788                             $this->validateLimit(
00789                                 $paramName,
00790                                 $value,
00791                                 $min,
00792                                 $paramSettings[self::PARAM_MAX],
00793                                 $paramSettings[self::PARAM_MAX2]
00794                             );
00795                         }
00796                         break;
00797                     case 'boolean':
00798                         if ( $multi ) {
00799                             ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00800                         }
00801                         break;
00802                     case 'timestamp':
00803                         if ( is_array( $value ) ) {
00804                             foreach ( $value as $key => $val ) {
00805                                 $value[$key] = $this->validateTimestamp( $val, $encParamName );
00806                             }
00807                         } else {
00808                             $value = $this->validateTimestamp( $value, $encParamName );
00809                         }
00810                         break;
00811                     case 'user':
00812                         if ( is_array( $value ) ) {
00813                             foreach ( $value as $key => $val ) {
00814                                 $value[$key] = $this->validateUser( $val, $encParamName );
00815                             }
00816                         } else {
00817                             $value = $this->validateUser( $value, $encParamName );
00818                         }
00819                         break;
00820                     case 'upload': // nothing to do
00821                         break;
00822                     default:
00823                         ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
00824                 }
00825             }
00826 
00827             // Throw out duplicates if requested
00828             if ( !$dupes && is_array( $value ) ) {
00829                 $value = array_unique( $value );
00830             }
00831 
00832             // Set a warning if a deprecated parameter has been passed
00833             if ( $deprecated && $value !== false ) {
00834                 $this->setWarning( "The $encParamName parameter has been deprecated." );
00835             }
00836         } elseif ( $required ) {
00837             $this->dieUsageMsg( array( 'missingparam', $paramName ) );
00838         }
00839 
00840         return $value;
00841     }
00842 
00856     protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
00857         if ( trim( $value ) === '' && $allowMultiple ) {
00858             return array();
00859         }
00860 
00861         // This is a bit awkward, but we want to avoid calling canApiHighLimits()
00862         // because it unstubs $wgUser
00863         $valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
00864         $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
00865             ? self::LIMIT_SML2
00866             : self::LIMIT_SML1;
00867 
00868         if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
00869             $this->setWarning( "Too many values supplied for parameter '$valueName': " .
00870                 "the limit is $sizeLimit" );
00871         }
00872 
00873         if ( !$allowMultiple && count( $valuesList ) != 1 ) {
00874             // Bug 33482 - Allow entries with | in them for non-multiple values
00875             if ( in_array( $value, $allowedValues, true ) ) {
00876                 return $value;
00877             }
00878 
00879             $possibleValues = is_array( $allowedValues )
00880                 ? "of '" . implode( "', '", $allowedValues ) . "'"
00881                 : '';
00882             $this->dieUsage(
00883                 "Only one $possibleValues is allowed for parameter '$valueName'",
00884                 "multival_$valueName"
00885             );
00886         }
00887 
00888         if ( is_array( $allowedValues ) ) {
00889             // Check for unknown values
00890             $unknown = array_diff( $valuesList, $allowedValues );
00891             if ( count( $unknown ) ) {
00892                 if ( $allowMultiple ) {
00893                     $s = count( $unknown ) > 1 ? 's' : '';
00894                     $vals = implode( ", ", $unknown );
00895                     $this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
00896                 } else {
00897                     $this->dieUsage(
00898                         "Unrecognized value for parameter '$valueName': {$valuesList[0]}",
00899                         "unknown_$valueName"
00900                     );
00901                 }
00902             }
00903             // Now throw them out
00904             $valuesList = array_intersect( $valuesList, $allowedValues );
00905         }
00906 
00907         return $allowMultiple ? $valuesList : $valuesList[0];
00908     }
00909 
00920     protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
00921         if ( !is_null( $min ) && $value < $min ) {
00922 
00923             $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
00924             $this->warnOrDie( $msg, $enforceLimits );
00925             $value = $min;
00926         }
00927 
00928         // Minimum is always validated, whereas maximum is checked only if not
00929         // running in internal call mode
00930         if ( $this->getMain()->isInternalMode() ) {
00931             return;
00932         }
00933 
00934         // Optimization: do not check user's bot status unless really needed -- skips db query
00935         // assumes $botMax >= $max
00936         if ( !is_null( $max ) && $value > $max ) {
00937             if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
00938                 if ( $value > $botMax ) {
00939                     $msg = $this->encodeParamName( $paramName ) .
00940                         " may not be over $botMax (set to $value) for bots or sysops";
00941                     $this->warnOrDie( $msg, $enforceLimits );
00942                     $value = $botMax;
00943                 }
00944             } else {
00945                 $msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
00946                 $this->warnOrDie( $msg, $enforceLimits );
00947                 $value = $max;
00948             }
00949         }
00950     }
00951 
00958     protected function validateTimestamp( $value, $encParamName ) {
00959         $unixTimestamp = wfTimestamp( TS_UNIX, $value );
00960         if ( $unixTimestamp === false ) {
00961             $this->dieUsage(
00962                 "Invalid value '$value' for timestamp parameter $encParamName",
00963                 "badtimestamp_{$encParamName}"
00964             );
00965         }
00966 
00967         return wfTimestamp( TS_MW, $unixTimestamp );
00968     }
00969 
00978     public final function validateToken( $token, array $params ) {
00979         $tokenType = $this->needsToken();
00980         $salts = ApiQueryTokens::getTokenTypeSalts();
00981         if ( !isset( $salts[$tokenType] ) ) {
00982             throw new MWException(
00983                 "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
00984                     'without registering it'
00985             );
00986         }
00987 
00988         if ( $this->getUser()->matchEditToken(
00989             $token,
00990             $salts[$tokenType],
00991             $this->getRequest()
00992         ) ) {
00993             return true;
00994         }
00995 
00996         $webUiSalt = $this->getWebUITokenSalt( $params );
00997         if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
00998             $token,
00999             $webUiSalt,
01000             $this->getRequest()
01001         ) ) {
01002             return true;
01003         }
01004 
01005         return false;
01006     }
01007 
01014     private function validateUser( $value, $encParamName ) {
01015         $title = Title::makeTitleSafe( NS_USER, $value );
01016         if ( $title === null ) {
01017             $this->dieUsage(
01018                 "Invalid value '$value' for user parameter $encParamName",
01019                 "baduser_{$encParamName}"
01020             );
01021         }
01022 
01023         return $title->getText();
01024     }
01025 
01028     /************************************************************************/
01039     protected function setWatch( $watch, $titleObj, $userOption = null ) {
01040         $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
01041         if ( $value === null ) {
01042             return;
01043         }
01044 
01045         WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
01046     }
01047 
01054     public static function truncateArray( &$arr, $limit ) {
01055         $modified = false;
01056         while ( count( $arr ) > $limit ) {
01057             array_pop( $arr );
01058             $modified = true;
01059         }
01060 
01061         return $modified;
01062     }
01063 
01070     public function getWatchlistUser( $params ) {
01071         if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
01072             $user = User::newFromName( $params['owner'], false );
01073             if ( !( $user && $user->getId() ) ) {
01074                 $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
01075             }
01076             $token = $user->getOption( 'watchlisttoken' );
01077             if ( $token == '' || $token != $params['token'] ) {
01078                 $this->dieUsage(
01079                     'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
01080                     'bad_wltoken'
01081                 );
01082             }
01083         } else {
01084             if ( !$this->getUser()->isLoggedIn() ) {
01085                 $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
01086             }
01087             if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
01088                 $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
01089             }
01090             $user = $this->getUser();
01091         }
01092 
01093         return $user;
01094     }
01095 
01098     /************************************************************************/
01110     public function setWarning( $warning ) {
01111         $result = $this->getResult();
01112         $data = $result->getData();
01113         $moduleName = $this->getModuleName();
01114         if ( isset( $data['warnings'][$moduleName] ) ) {
01115             // Don't add duplicate warnings
01116             $oldWarning = $data['warnings'][$moduleName]['*'];
01117             $warnPos = strpos( $oldWarning, $warning );
01118             // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
01119             if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
01120                 // Check if $warning is followed by "\n" or the end of the $oldWarning
01121                 $warnPos += strlen( $warning );
01122                 if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
01123                     return;
01124                 }
01125             }
01126             // If there is a warning already, append it to the existing one
01127             $warning = "$oldWarning\n$warning";
01128         }
01129         $msg = array();
01130         ApiResult::setContent( $msg, $warning );
01131         $result->addValue( 'warnings', $moduleName,
01132             $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
01133     }
01134 
01141     private function warnOrDie( $msg, $enforceLimits = false ) {
01142         if ( $enforceLimits ) {
01143             $this->dieUsage( $msg, 'integeroutofrange' );
01144         }
01145 
01146         $this->setWarning( $msg );
01147     }
01148 
01161     public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
01162         Profiler::instance()->close();
01163         throw new UsageException(
01164             $description,
01165             $this->encodeParamName( $errorCode ),
01166             $httpRespCode,
01167             $extradata
01168         );
01169     }
01170 
01178     public function getErrorFromStatus( $status ) {
01179         if ( $status->isGood() ) {
01180             throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
01181         }
01182 
01183         $errors = $status->getErrorsArray();
01184         if ( !$errors ) {
01185             // No errors? Assume the warnings should be treated as errors
01186             $errors = $status->getWarningsArray();
01187         }
01188         if ( !$errors ) {
01189             // Still no errors? Punt
01190             $errors = array( array( 'unknownerror-nocode' ) );
01191         }
01192 
01193         // Cannot use dieUsageMsg() because extensions might return custom
01194         // error messages.
01195         if ( $errors[0] instanceof Message ) {
01196             $msg = $errors[0];
01197             $code = $msg->getKey();
01198         } else {
01199             $code = array_shift( $errors[0] );
01200             $msg = wfMessage( $code, $errors[0] );
01201         }
01202         if ( isset( ApiBase::$messageMap[$code] ) ) {
01203             // Translate message to code, for backwards compatibility
01204             $code = ApiBase::$messageMap[$code]['code'];
01205         }
01206 
01207         return array( $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() );
01208     }
01209 
01217     public function dieStatus( $status ) {
01218 
01219         list( $code, $msg ) = $this->getErrorFromStatus( $status );
01220         $this->dieUsage( $msg, $code );
01221     }
01222 
01223     // @codingStandardsIgnoreStart Allow long lines. Cannot split these.
01227     public static $messageMap = array(
01228         // This one MUST be present, or dieUsageMsg() will recurse infinitely
01229         'unknownerror' => array( 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ),
01230         'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
01231 
01232         // Messages from Title::getUserPermissionsErrors()
01233         'ns-specialprotected' => array(
01234             'code' => 'unsupportednamespace',
01235             'info' => "Pages in the Special namespace can't be edited"
01236         ),
01237         'protectedinterface' => array(
01238             'code' => 'protectednamespace-interface',
01239             'info' => "You're not allowed to edit interface messages"
01240         ),
01241         'namespaceprotected' => array(
01242             'code' => 'protectednamespace',
01243             'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
01244         ),
01245         'customcssprotected' => array(
01246             'code' => 'customcssprotected',
01247             'info' => "You're not allowed to edit custom CSS pages"
01248         ),
01249         'customjsprotected' => array(
01250             'code' => 'customjsprotected',
01251             'info' => "You're not allowed to edit custom JavaScript pages"
01252         ),
01253         'cascadeprotected' => array(
01254             'code' => 'cascadeprotected',
01255             'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
01256         ),
01257         'protectedpagetext' => array(
01258             'code' => 'protectedpage',
01259             'info' => "The \"\$1\" right is required to edit this page"
01260         ),
01261         'protect-cantedit' => array(
01262             'code' => 'cantedit',
01263             'info' => "You can't protect this page because you can't edit it"
01264         ),
01265         'deleteprotected' => array(
01266             'code' => 'cantedit',
01267             'info' => "You can't delete this page because it has been protected"
01268         ),
01269         'badaccess-group0' => array(
01270             'code' => 'permissiondenied',
01271             'info' => "Permission denied"
01272         ), // Generic permission denied message
01273         'badaccess-groups' => array(
01274             'code' => 'permissiondenied',
01275             'info' => "Permission denied"
01276         ),
01277         'titleprotected' => array(
01278             'code' => 'protectedtitle',
01279             'info' => "This title has been protected from creation"
01280         ),
01281         'nocreate-loggedin' => array(
01282             'code' => 'cantcreate',
01283             'info' => "You don't have permission to create new pages"
01284         ),
01285         'nocreatetext' => array(
01286             'code' => 'cantcreate-anon',
01287             'info' => "Anonymous users can't create new pages"
01288         ),
01289         'movenologintext' => array(
01290             'code' => 'cantmove-anon',
01291             'info' => "Anonymous users can't move pages"
01292         ),
01293         'movenotallowed' => array(
01294             'code' => 'cantmove',
01295             'info' => "You don't have permission to move pages"
01296         ),
01297         'confirmedittext' => array(
01298             'code' => 'confirmemail',
01299             'info' => "You must confirm your email address before you can edit"
01300         ),
01301         'blockedtext' => array(
01302             'code' => 'blocked',
01303             'info' => "You have been blocked from editing"
01304         ),
01305         'autoblockedtext' => array(
01306             'code' => 'autoblocked',
01307             'info' => "Your IP address has been blocked automatically, because it was used by a blocked user"
01308         ),
01309 
01310         // Miscellaneous interface messages
01311         'actionthrottledtext' => array(
01312             'code' => 'ratelimited',
01313             'info' => "You've exceeded your rate limit. Please wait some time and try again"
01314         ),
01315         'alreadyrolled' => array(
01316             'code' => 'alreadyrolled',
01317             'info' => "The page you tried to rollback was already rolled back"
01318         ),
01319         'cantrollback' => array(
01320             'code' => 'onlyauthor',
01321             'info' => "The page you tried to rollback only has one author"
01322         ),
01323         'readonlytext' => array(
01324             'code' => 'readonly',
01325             'info' => "The wiki is currently in read-only mode"
01326         ),
01327         'sessionfailure' => array(
01328             'code' => 'badtoken',
01329             'info' => "Invalid token" ),
01330         'cannotdelete' => array(
01331             'code' => 'cantdelete',
01332             'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
01333         ),
01334         'notanarticle' => array(
01335             'code' => 'missingtitle',
01336             'info' => "The page you requested doesn't exist"
01337         ),
01338         'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself"
01339         ),
01340         'immobile_namespace' => array(
01341             'code' => 'immobilenamespace',
01342             'info' => "You tried to move pages from or to a namespace that is protected from moving"
01343         ),
01344         'articleexists' => array(
01345             'code' => 'articleexists',
01346             'info' => "The destination article already exists and is not a redirect to the source article"
01347         ),
01348         'protectedpage' => array(
01349             'code' => 'protectedpage',
01350             'info' => "You don't have permission to perform this move"
01351         ),
01352         'hookaborted' => array(
01353             'code' => 'hookaborted',
01354             'info' => "The modification you tried to make was aborted by an extension hook"
01355         ),
01356         'cantmove-titleprotected' => array(
01357             'code' => 'protectedtitle',
01358             'info' => "The destination article has been protected from creation"
01359         ),
01360         'imagenocrossnamespace' => array(
01361             'code' => 'nonfilenamespace',
01362             'info' => "Can't move a file to a non-file namespace"
01363         ),
01364         'imagetypemismatch' => array(
01365             'code' => 'filetypemismatch',
01366             'info' => "The new file extension doesn't match its type"
01367         ),
01368         // 'badarticleerror' => shouldn't happen
01369         // 'badtitletext' => shouldn't happen
01370         'ip_range_invalid' => array( 'code' => 'invalidrange', 'info' => "Invalid IP range" ),
01371         'range_block_disabled' => array(
01372             'code' => 'rangedisabled',
01373             'info' => "Blocking IP ranges has been disabled"
01374         ),
01375         'nosuchusershort' => array(
01376             'code' => 'nosuchuser',
01377             'info' => "The user you specified doesn't exist"
01378         ),
01379         'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
01380         'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
01381         'ipb_already_blocked' => array(
01382             'code' => 'alreadyblocked',
01383             'info' => "The user you tried to block was already blocked"
01384         ),
01385         'ipb_blocked_as_range' => array(
01386             'code' => 'blockedasrange',
01387             '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."
01388         ),
01389         'ipb_cant_unblock' => array(
01390             'code' => 'cantunblock',
01391             'info' => "The block you specified was not found. It may have been unblocked already"
01392         ),
01393         'mailnologin' => array(
01394             'code' => 'cantsend',
01395             '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"
01396         ),
01397         'ipbblocked' => array(
01398             'code' => 'ipbblocked',
01399             'info' => 'You cannot block or unblock users while you are yourself blocked'
01400         ),
01401         'ipbnounblockself' => array(
01402             'code' => 'ipbnounblockself',
01403             'info' => 'You are not allowed to unblock yourself'
01404         ),
01405         'usermaildisabled' => array(
01406             'code' => 'usermaildisabled',
01407             'info' => "User email has been disabled"
01408         ),
01409         'blockedemailuser' => array(
01410             'code' => 'blockedfrommail',
01411             'info' => "You have been blocked from sending email"
01412         ),
01413         'notarget' => array(
01414             'code' => 'notarget',
01415             'info' => "You have not specified a valid target for this action"
01416         ),
01417         'noemail' => array(
01418             'code' => 'noemail',
01419             'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users"
01420         ),
01421         'rcpatroldisabled' => array(
01422             'code' => 'patroldisabled',
01423             'info' => "Patrolling is disabled on this wiki"
01424         ),
01425         'markedaspatrollederror-noautopatrol' => array(
01426             'code' => 'noautopatrol',
01427             'info' => "You don't have permission to patrol your own changes"
01428         ),
01429         'delete-toobig' => array(
01430             'code' => 'bigdelete',
01431             'info' => "You can't delete this page because it has more than \$1 revisions"
01432         ),
01433         'movenotallowedfile' => array(
01434             'code' => 'cantmovefile',
01435             'info' => "You don't have permission to move files"
01436         ),
01437         'userrights-no-interwiki' => array(
01438             'code' => 'nointerwikiuserrights',
01439             'info' => "You don't have permission to change user rights on other wikis"
01440         ),
01441         'userrights-nodatabase' => array(
01442             'code' => 'nosuchdatabase',
01443             'info' => "Database \"\$1\" does not exist or is not local"
01444         ),
01445         'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01446         'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01447         'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
01448         'import-rootpage-invalid' => array(
01449             'code' => 'import-rootpage-invalid',
01450             'info' => 'Root page is an invalid title'
01451         ),
01452         'import-rootpage-nosubpage' => array(
01453             'code' => 'import-rootpage-nosubpage',
01454             'info' => 'Namespace "$1" of the root page does not allow subpages'
01455         ),
01456 
01457         // API-specific messages
01458         'readrequired' => array(
01459             'code' => 'readapidenied',
01460             'info' => "You need read permission to use this module"
01461         ),
01462         'writedisabled' => array(
01463             'code' => 'noapiwrite',
01464             '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"
01465         ),
01466         'writerequired' => array(
01467             'code' => 'writeapidenied',
01468             'info' => "You're not allowed to edit this wiki through the API"
01469         ),
01470         'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
01471         'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ),
01472         'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
01473         'nosuchrevid' => array( 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ),
01474         'nosuchuser' => array( 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ),
01475         'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
01476         'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ),
01477         'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ),
01478         'create-titleexists' => array(
01479             'code' => 'create-titleexists',
01480             'info' => "Existing titles can't be protected with 'create'"
01481         ),
01482         'missingtitle-createonly' => array(
01483             'code' => 'missingtitle-createonly',
01484             'info' => "Missing titles can only be protected with 'create'"
01485         ),
01486         'cantblock' => array( 'code' => 'cantblock',
01487             'info' => "You don't have permission to block users"
01488         ),
01489         'canthide' => array(
01490             'code' => 'canthide',
01491             'info' => "You don't have permission to hide user names from the block log"
01492         ),
01493         'cantblock-email' => array(
01494             'code' => 'cantblock-email',
01495             'info' => "You don't have permission to block users from sending email through the wiki"
01496         ),
01497         'unblock-notarget' => array(
01498             'code' => 'notarget',
01499             'info' => "Either the id or the user parameter must be set"
01500         ),
01501         'unblock-idanduser' => array(
01502             'code' => 'idanduser',
01503             'info' => "The id and user parameters can't be used together"
01504         ),
01505         'cantunblock' => array(
01506             'code' => 'permissiondenied',
01507             'info' => "You don't have permission to unblock users"
01508         ),
01509         'cannotundelete' => array(
01510             'code' => 'cantundelete',
01511             'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
01512         ),
01513         'permdenied-undelete' => array(
01514             'code' => 'permissiondenied',
01515             'info' => "You don't have permission to restore deleted revisions"
01516         ),
01517         'createonly-exists' => array(
01518             'code' => 'articleexists',
01519             'info' => "The article you tried to create has been created already"
01520         ),
01521         'nocreate-missing' => array(
01522             'code' => 'missingtitle',
01523             'info' => "The article you tried to edit doesn't exist"
01524         ),
01525         'nosuchrcid' => array(
01526             'code' => 'nosuchrcid',
01527             'info' => "There is no change with rcid \"\$1\""
01528         ),
01529         'protect-invalidaction' => array(
01530             'code' => 'protect-invalidaction',
01531             'info' => "Invalid protection type \"\$1\""
01532         ),
01533         'protect-invalidlevel' => array(
01534             'code' => 'protect-invalidlevel',
01535             'info' => "Invalid protection level \"\$1\""
01536         ),
01537         'toofewexpiries' => array(
01538             'code' => 'toofewexpiries',
01539             'info' => "\$1 expiry timestamps were provided where \$2 were needed"
01540         ),
01541         'cantimport' => array(
01542             'code' => 'cantimport',
01543             'info' => "You don't have permission to import pages"
01544         ),
01545         'cantimport-upload' => array(
01546             'code' => 'cantimport-upload',
01547             'info' => "You don't have permission to import uploaded pages"
01548         ),
01549         'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ),
01550         'importuploaderrorsize' => array(
01551             'code' => 'filetoobig',
01552             'info' => 'The file you uploaded is bigger than the maximum upload size'
01553         ),
01554         'importuploaderrorpartial' => array(
01555             'code' => 'partialupload',
01556             'info' => 'The file was only partially uploaded'
01557         ),
01558         'importuploaderrortemp' => array(
01559             'code' => 'notempdir',
01560             'info' => 'The temporary upload directory is missing'
01561         ),
01562         'importcantopen' => array(
01563             'code' => 'cantopenfile',
01564             'info' => "Couldn't open the uploaded file"
01565         ),
01566         'import-noarticle' => array(
01567             'code' => 'badinterwiki',
01568             'info' => 'Invalid interwiki title specified'
01569         ),
01570         'importbadinterwiki' => array(
01571             'code' => 'badinterwiki',
01572             'info' => 'Invalid interwiki title specified'
01573         ),
01574         'import-unknownerror' => array(
01575             'code' => 'import-unknownerror',
01576             'info' => "Unknown error on import: \"\$1\""
01577         ),
01578         'cantoverwrite-sharedfile' => array(
01579             'code' => 'cantoverwrite-sharedfile',
01580             'info' => 'The target file exists on a shared repository and you do not have permission to override it'
01581         ),
01582         'sharedfile-exists' => array(
01583             'code' => 'fileexists-sharedrepo-perm',
01584             'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
01585         ),
01586         'mustbeposted' => array(
01587             'code' => 'mustbeposted',
01588             'info' => "The \$1 module requires a POST request"
01589         ),
01590         'show' => array(
01591             'code' => 'show',
01592             'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
01593         ),
01594         'specialpage-cantexecute' => array(
01595             'code' => 'specialpage-cantexecute',
01596             'info' => "You don't have permission to view the results of this special page"
01597         ),
01598         'invalidoldimage' => array(
01599             'code' => 'invalidoldimage',
01600             'info' => 'The oldimage parameter has invalid format'
01601         ),
01602         'nodeleteablefile' => array(
01603             'code' => 'nodeleteablefile',
01604             'info' => 'No such old version of the file'
01605         ),
01606         'fileexists-forbidden' => array(
01607             'code' => 'fileexists-forbidden',
01608             'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
01609         ),
01610         'fileexists-shared-forbidden' => array(
01611             'code' => 'fileexists-shared-forbidden',
01612             'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
01613         ),
01614         'filerevert-badversion' => array(
01615             'code' => 'filerevert-badversion',
01616             'info' => 'There is no previous local version of this file with the provided timestamp.'
01617         ),
01618 
01619         // ApiEditPage messages
01620         'noimageredirect-anon' => array(
01621             'code' => 'noimageredirect-anon',
01622             'info' => "Anonymous users can't create image redirects"
01623         ),
01624         'noimageredirect-logged' => array(
01625             'code' => 'noimageredirect',
01626             'info' => "You don't have permission to create image redirects"
01627         ),
01628         'spamdetected' => array(
01629             'code' => 'spamdetected',
01630             'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
01631         ),
01632         'contenttoobig' => array(
01633             'code' => 'contenttoobig',
01634             'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
01635         ),
01636         'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
01637         'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),
01638         'wasdeleted' => array(
01639             'code' => 'pagedeleted',
01640             'info' => "The page has been deleted since you fetched its timestamp"
01641         ),
01642         'blankpage' => array(
01643             'code' => 'emptypage',
01644             'info' => "Creating new, empty pages is not allowed"
01645         ),
01646         'editconflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
01647         'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
01648         'missingtext' => array(
01649             'code' => 'notext',
01650             'info' => "One of the text, appendtext, prependtext and undo parameters must be set"
01651         ),
01652         'emptynewsection' => array(
01653             'code' => 'emptynewsection',
01654             'info' => 'Creating empty new sections is not possible.'
01655         ),
01656         'revwrongpage' => array(
01657             'code' => 'revwrongpage',
01658             'info' => "r\$1 is not a revision of \"\$2\""
01659         ),
01660         'undo-failure' => array(
01661             'code' => 'undofailure',
01662             'info' => 'Undo failed due to conflicting intermediate edits'
01663         ),
01664         'content-not-allowed-here' => array(
01665             'code' => 'contentnotallowedhere',
01666             'info' => 'Content model "$1" is not allowed at title "$2"'
01667         ),
01668 
01669         // Messages from WikiPage::doEit()
01670         'edit-hook-aborted' => array(
01671             'code' => 'edit-hook-aborted',
01672             'info' => "Your edit was aborted by an ArticleSave hook"
01673         ),
01674         'edit-gone-missing' => array(
01675             'code' => 'edit-gone-missing',
01676             'info' => "The page you tried to edit doesn't seem to exist anymore"
01677         ),
01678         'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
01679         'edit-already-exists' => array(
01680             'code' => 'edit-already-exists',
01681             'info' => 'It seems the page you tried to create already exist'
01682         ),
01683 
01684         // uploadMsgs
01685         'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ),
01686         'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
01687         'uploaddisabled' => array(
01688             'code' => 'uploaddisabled',
01689             'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
01690         ),
01691         'copyuploaddisabled' => array(
01692             'code' => 'copyuploaddisabled',
01693             'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
01694         ),
01695         'copyuploadbaddomain' => array(
01696             'code' => 'copyuploadbaddomain',
01697             'info' => 'Uploads by URL are not allowed from this domain.'
01698         ),
01699         'copyuploadbadurl' => array(
01700             'code' => 'copyuploadbadurl',
01701             'info' => 'Upload not allowed from this URL.'
01702         ),
01703 
01704         'filename-tooshort' => array(
01705             'code' => 'filename-tooshort',
01706             'info' => 'The filename is too short'
01707         ),
01708         'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
01709         'illegal-filename' => array(
01710             'code' => 'illegal-filename',
01711             'info' => 'The filename is not allowed'
01712         ),
01713         'filetype-missing' => array(
01714             'code' => 'filetype-missing',
01715             'info' => 'The file is missing an extension'
01716         ),
01717 
01718         'mustbeloggedin' => array( 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' )
01719     );
01720     // @codingStandardsIgnoreEnd
01721 
01725     public function dieReadOnly() {
01726         $parsed = $this->parseMsg( array( 'readonlytext' ) );
01727         $this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
01728             array( 'readonlyreason' => wfReadOnlyReason() ) );
01729     }
01730 
01735     public function dieUsageMsg( $error ) {
01736         # most of the time we send a 1 element, so we might as well send it as
01737         # a string and make this an array here.
01738         if ( is_string( $error ) ) {
01739             $error = array( $error );
01740         }
01741         $parsed = $this->parseMsg( $error );
01742         $this->dieUsage( $parsed['info'], $parsed['code'] );
01743     }
01744 
01751     public function dieUsageMsgOrDebug( $error ) {
01752         if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
01753             $this->dieUsageMsg( $error );
01754         }
01755 
01756         if ( is_string( $error ) ) {
01757             $error = array( $error );
01758         }
01759         $parsed = $this->parseMsg( $error );
01760         $this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
01761     }
01762 
01769     protected function dieContinueUsageIf( $condition ) {
01770         if ( $condition ) {
01771             $this->dieUsage(
01772                 'Invalid continue param. You should pass the original value returned by the previous query',
01773                 'badcontinue' );
01774         }
01775     }
01776 
01782     public function parseMsg( $error ) {
01783         $error = (array)$error; // It seems strings sometimes make their way in here
01784         $key = array_shift( $error );
01785 
01786         // Check whether the error array was nested
01787         // array( array( <code>, <params> ), array( <another_code>, <params> ) )
01788         if ( is_array( $key ) ) {
01789             $error = $key;
01790             $key = array_shift( $error );
01791         }
01792 
01793         if ( isset( self::$messageMap[$key] ) ) {
01794             return array(
01795                 'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
01796                 'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
01797             );
01798         }
01799 
01800         // If the key isn't present, throw an "unknown error"
01801         return $this->parseMsg( array( 'unknownerror', $key ) );
01802     }
01803 
01810     protected static function dieDebug( $method, $message ) {
01811         throw new MWException( "Internal error in $method: $message" );
01812     }
01813 
01816     /************************************************************************/
01825     public function makeHelpMsg() {
01826         static $lnPrfx = "\n  ";
01827 
01828         $msg = $this->getFinalDescription();
01829 
01830         if ( $msg !== false ) {
01831 
01832             if ( !is_array( $msg ) ) {
01833                 $msg = array(
01834                     $msg
01835                 );
01836             }
01837             $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
01838 
01839             $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
01840 
01841             if ( $this->isReadMode() ) {
01842                 $msg .= "\nThis module requires read rights";
01843             }
01844             if ( $this->isWriteMode() ) {
01845                 $msg .= "\nThis module requires write rights";
01846             }
01847             if ( $this->mustBePosted() ) {
01848                 $msg .= "\nThis module only accepts POST requests";
01849             }
01850             if ( $this->isReadMode() || $this->isWriteMode() ||
01851                 $this->mustBePosted()
01852             ) {
01853                 $msg .= "\n";
01854             }
01855 
01856             // Parameters
01857             $paramsMsg = $this->makeHelpMsgParameters();
01858             if ( $paramsMsg !== false ) {
01859                 $msg .= "Parameters:\n$paramsMsg";
01860             }
01861 
01862             $examples = $this->getExamples();
01863             if ( $examples ) {
01864                 if ( !is_array( $examples ) ) {
01865                     $examples = array(
01866                         $examples
01867                     );
01868                 }
01869                 $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
01870                 foreach ( $examples as $k => $v ) {
01871                     if ( is_numeric( $k ) ) {
01872                         $msg .= "  $v\n";
01873                     } else {
01874                         if ( is_array( $v ) ) {
01875                             $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
01876                         } else {
01877                             $msgExample = "  $v";
01878                         }
01879                         $msgExample .= ":";
01880                         $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
01881                     }
01882                 }
01883             }
01884         }
01885 
01886         return $msg;
01887     }
01888 
01893     private function indentExampleText( $item ) {
01894         return "  " . $item;
01895     }
01896 
01903     protected function makeHelpArrayToString( $prefix, $title, $input ) {
01904         if ( $input === false ) {
01905             return '';
01906         }
01907         if ( !is_array( $input ) ) {
01908             $input = array( $input );
01909         }
01910 
01911         if ( count( $input ) > 0 ) {
01912             if ( $title ) {
01913                 $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
01914             } else {
01915                 $msg = '  ';
01916             }
01917             $msg .= implode( $prefix, $input ) . "\n";
01918 
01919             return $msg;
01920         }
01921 
01922         return '';
01923     }
01924 
01930     public function makeHelpMsgParameters() {
01931         $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
01932         if ( $params ) {
01933 
01934             $paramsDescription = $this->getFinalParamDescription();
01935             $msg = '';
01936             $paramPrefix = "\n" . str_repeat( ' ', 24 );
01937             $descWordwrap = "\n" . str_repeat( ' ', 28 );
01938             foreach ( $params as $paramName => $paramSettings ) {
01939                 $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
01940                 if ( is_array( $desc ) ) {
01941                     $desc = implode( $paramPrefix, $desc );
01942                 }
01943 
01944                 //handle shorthand
01945                 if ( !is_array( $paramSettings ) ) {
01946                     $paramSettings = array(
01947                         self::PARAM_DFLT => $paramSettings,
01948                     );
01949                 }
01950 
01951                 //handle missing type
01952                 if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
01953                     $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
01954                         ? $paramSettings[ApiBase::PARAM_DFLT]
01955                         : null;
01956                     if ( is_bool( $dflt ) ) {
01957                         $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
01958                     } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
01959                         $paramSettings[ApiBase::PARAM_TYPE] = 'string';
01960                     } elseif ( is_int( $dflt ) ) {
01961                         $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
01962                     }
01963                 }
01964 
01965                 if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
01966                     && $paramSettings[self::PARAM_DEPRECATED]
01967                 ) {
01968                     $desc = "DEPRECATED! $desc";
01969                 }
01970 
01971                 if ( isset( $paramSettings[self::PARAM_REQUIRED] )
01972                     && $paramSettings[self::PARAM_REQUIRED]
01973                 ) {
01974                     $desc .= $paramPrefix . "This parameter is required";
01975                 }
01976 
01977                 $type = isset( $paramSettings[self::PARAM_TYPE] )
01978                     ? $paramSettings[self::PARAM_TYPE]
01979                     : null;
01980                 if ( isset( $type ) ) {
01981                     $hintPipeSeparated = true;
01982                     $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
01983                         ? $paramSettings[self::PARAM_ISMULTI]
01984                         : false;
01985                     if ( $multi ) {
01986                         $prompt = 'Values (separate with \'|\'): ';
01987                     } else {
01988                         $prompt = 'One value: ';
01989                     }
01990 
01991                     if ( $type === 'submodule' ) {
01992                         $type = $this->getModuleManager()->getNames( $paramName );
01993                         sort( $type );
01994                     }
01995                     if ( is_array( $type ) ) {
01996                         $choices = array();
01997                         $nothingPrompt = '';
01998                         foreach ( $type as $t ) {
01999                             if ( $t === '' ) {
02000                                 $nothingPrompt = 'Can be empty, or ';
02001                             } else {
02002                                 $choices[] = $t;
02003                             }
02004                         }
02005                         $desc .= $paramPrefix . $nothingPrompt . $prompt;
02006                         $choicesstring = implode( ', ', $choices );
02007                         $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
02008                         $hintPipeSeparated = false;
02009                     } else {
02010                         switch ( $type ) {
02011                             case 'namespace':
02012                                 // Special handling because namespaces are
02013                                 // type-limited, yet they are not given
02014                                 $desc .= $paramPrefix . $prompt;
02015                                 $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
02016                                     100, $descWordwrap );
02017                                 $hintPipeSeparated = false;
02018                                 break;
02019                             case 'limit':
02020                                 $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
02021                                 if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
02022                                     $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
02023                                 }
02024                                 $desc .= ' allowed';
02025                                 break;
02026                             case 'integer':
02027                                 $s = $multi ? 's' : '';
02028                                 $hasMin = isset( $paramSettings[self::PARAM_MIN] );
02029                                 $hasMax = isset( $paramSettings[self::PARAM_MAX] );
02030                                 if ( $hasMin || $hasMax ) {
02031                                     if ( !$hasMax ) {
02032                                         $intRangeStr = "The value$s must be no less than " .
02033                                             "{$paramSettings[self::PARAM_MIN]}";
02034                                     } elseif ( !$hasMin ) {
02035                                         $intRangeStr = "The value$s must be no more than " .
02036                                             "{$paramSettings[self::PARAM_MAX]}";
02037                                     } else {
02038                                         $intRangeStr = "The value$s must be between " .
02039                                             "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
02040                                     }
02041 
02042                                     $desc .= $paramPrefix . $intRangeStr;
02043                                 }
02044                                 break;
02045                             case 'upload':
02046                                 $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
02047                                 break;
02048                         }
02049                     }
02050 
02051                     if ( $multi ) {
02052                         if ( $hintPipeSeparated ) {
02053                             $desc .= $paramPrefix . "Separate values with '|'";
02054                         }
02055 
02056                         $isArray = is_array( $type );
02057                         if ( !$isArray
02058                             || $isArray && count( $type ) > self::LIMIT_SML1
02059                         ) {
02060                             $desc .= $paramPrefix . "Maximum number of values " .
02061                                 self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
02062                         }
02063                     }
02064                 }
02065 
02066                 $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
02067                 if ( !is_null( $default ) && $default !== false ) {
02068                     $desc .= $paramPrefix . "Default: $default";
02069                 }
02070 
02071                 $msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
02072             }
02073 
02074             return $msg;
02075         }
02076 
02077         return false;
02078     }
02079 
02082     /************************************************************************/
02090     private $mTimeIn = 0, $mModuleTime = 0;
02091 
02099     public function getModuleProfileName( $db = false ) {
02100         if ( $db ) {
02101             return 'API:' . $this->mModuleName . '-DB';
02102         }
02103 
02104         return 'API:' . $this->mModuleName;
02105     }
02106 
02110     public function profileIn() {
02111         if ( $this->mTimeIn !== 0 ) {
02112             ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileOut()' );
02113         }
02114         $this->mTimeIn = microtime( true );
02115         wfProfileIn( $this->getModuleProfileName() );
02116     }
02117 
02121     public function profileOut() {
02122         if ( $this->mTimeIn === 0 ) {
02123             ApiBase::dieDebug( __METHOD__, 'Called without calling profileIn() first' );
02124         }
02125         if ( $this->mDBTimeIn !== 0 ) {
02126             ApiBase::dieDebug(
02127                 __METHOD__,
02128                 'Must be called after database profiling is done with profileDBOut()'
02129             );
02130         }
02131 
02132         $this->mModuleTime += microtime( true ) - $this->mTimeIn;
02133         $this->mTimeIn = 0;
02134         wfProfileOut( $this->getModuleProfileName() );
02135     }
02136 
02141     public function safeProfileOut() {
02142         if ( $this->mTimeIn !== 0 ) {
02143             if ( $this->mDBTimeIn !== 0 ) {
02144                 $this->profileDBOut();
02145             }
02146             $this->profileOut();
02147         }
02148     }
02149 
02154     public function getProfileTime() {
02155         if ( $this->mTimeIn !== 0 ) {
02156             ApiBase::dieDebug( __METHOD__, 'Called without calling profileOut() first' );
02157         }
02158 
02159         return $this->mModuleTime;
02160     }
02161 
02165     private $mDBTimeIn = 0, $mDBTime = 0;
02166 
02170     public function profileDBIn() {
02171         if ( $this->mTimeIn === 0 ) {
02172             ApiBase::dieDebug(
02173                 __METHOD__,
02174                 'Must be called while profiling the entire module with profileIn()'
02175             );
02176         }
02177         if ( $this->mDBTimeIn !== 0 ) {
02178             ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileDBOut()' );
02179         }
02180         $this->mDBTimeIn = microtime( true );
02181         wfProfileIn( $this->getModuleProfileName( true ) );
02182     }
02183 
02187     public function profileDBOut() {
02188         if ( $this->mTimeIn === 0 ) {
02189             ApiBase::dieDebug( __METHOD__, 'Must be called while profiling ' .
02190                 'the entire module with profileIn()' );
02191         }
02192         if ( $this->mDBTimeIn === 0 ) {
02193             ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBIn() first' );
02194         }
02195 
02196         $time = microtime( true ) - $this->mDBTimeIn;
02197         $this->mDBTimeIn = 0;
02198 
02199         $this->mDBTime += $time;
02200         $this->getMain()->mDBTime += $time;
02201         wfProfileOut( $this->getModuleProfileName( true ) );
02202     }
02203 
02208     public function getProfileDBTime() {
02209         if ( $this->mDBTimeIn !== 0 ) {
02210             ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBOut() first' );
02211         }
02212 
02213         return $this->mDBTime;
02214     }
02215 
02221     protected function logFeatureUsage( $feature ) {
02222         $request = $this->getRequest();
02223         $s = '"' . addslashes( $feature ) . '"' .
02224             ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
02225             ' "' . $request->getIP() . '"' .
02226             ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
02227             ' "' . addslashes( $request->getHeader( 'User-agent' ) ) . '"';
02228         wfDebugLog( 'api-feature-usage', $s, 'private' );
02229     }
02230 
02233     /************************************************************************/
02238 
02239     const PROP_ROOT = 'ROOT';
02241     const PROP_LIST = 'LIST';
02243     const PROP_TYPE = 0;
02245     const PROP_NULLABLE = 1;
02246 
02255     public function getVersion() {
02256         wfDeprecated( __METHOD__, '1.21' );
02257         return '';
02258     }
02259 
02270     protected function getResultProperties() {
02271         wfDeprecated( __METHOD__, '1.24' );
02272         return false;
02273     }
02274 
02280     public function getFinalResultProperties() {
02281         wfDeprecated( __METHOD__, '1.24' );
02282         return array();
02283     }
02284 
02289     protected static function addTokenProperties( &$props, $tokenFunctions ) {
02290         wfDeprecated( __METHOD__, '1.24' );
02291     }
02292 
02298     public function getRequireOnlyOneParameterErrorMessages( $params ) {
02299         wfDeprecated( __METHOD__, '1.24' );
02300         return array();
02301     }
02302 
02308     public function getRequireMaxOneParameterErrorMessages( $params ) {
02309         wfDeprecated( __METHOD__, '1.24' );
02310         return array();
02311     }
02312 
02318     public function getRequireAtLeastOneParameterErrorMessages( $params ) {
02319         wfDeprecated( __METHOD__, '1.24' );
02320         return array();
02321     }
02322 
02328     public function getTitleOrPageIdErrorMessage() {
02329         wfDeprecated( __METHOD__, '1.24' );
02330         return array();
02331     }
02332 
02343     public function getPossibleErrors() {
02344         wfDeprecated( __METHOD__, '1.24' );
02345         return array();
02346     }
02347 
02353     public function getFinalPossibleErrors() {
02354         wfDeprecated( __METHOD__, '1.24' );
02355         return array();
02356     }
02357 
02363     public function parseErrors( $errors ) {
02364         wfDeprecated( __METHOD__, '1.24' );
02365         return array();
02366     }
02367 
02369 }
02370