MediaWiki
REL1_24
|
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