MediaWiki
REL1_23
|
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 }