00001 <?php
00002
00041 abstract class ApiBase {
00042
00043
00044
00045 const PARAM_DFLT = 0;
00046 const PARAM_ISMULTI = 1;
00047 const PARAM_TYPE = 2;
00048 const PARAM_MAX = 3;
00049 const PARAM_MAX2 = 4;
00050 const PARAM_MIN = 5;
00051 const PARAM_ALLOW_DUPLICATES = 6;
00052 const PARAM_DEPRECATED = 7;
00053
00054 const LIMIT_BIG1 = 500;
00055 const LIMIT_BIG2 = 5000;
00056 const LIMIT_SML1 = 50;
00057 const LIMIT_SML2 = 500;
00058
00059 private $mMainModule, $mModuleName, $mModulePrefix;
00060 private $mParamCache = array();
00061
00068 public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
00069 $this->mMainModule = $mainModule;
00070 $this->mModuleName = $moduleName;
00071 $this->mModulePrefix = $modulePrefix;
00072 }
00073
00074
00075
00076
00077
00094 public abstract function execute();
00095
00102 public abstract function getVersion();
00103
00108 public function getModuleName() {
00109 return $this->mModuleName;
00110 }
00111
00116 public function getModulePrefix() {
00117 return $this->mModulePrefix;
00118 }
00119
00124 public function getModuleProfileName( $db = false ) {
00125 if ( $db ) {
00126 return 'API:' . $this->mModuleName . '-DB';
00127 } else {
00128 return 'API:' . $this->mModuleName;
00129 }
00130 }
00131
00136 public function getMain() {
00137 return $this->mMainModule;
00138 }
00139
00145 public function isMain() {
00146 return $this === $this->mMainModule;
00147 }
00148
00153 public function getResult() {
00154
00155
00156 if ( $this->isMain() ) {
00157 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
00158 }
00159 return $this->getMain()->getResult();
00160 }
00161
00166 public function getResultData() {
00167 return $this->getResult()->getData();
00168 }
00169
00177 public function setWarning( $warning ) {
00178 $data = $this->getResult()->getData();
00179 if ( isset( $data['warnings'][$this->getModuleName()] ) ) {
00180
00181 $warn_regex = preg_quote( $warning, '/' );
00182 if ( preg_match( "/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'] ) )
00183 {
00184 return;
00185 }
00186 $oldwarning = $data['warnings'][$this->getModuleName()]['*'];
00187
00188 $warning = "$oldwarning\n$warning";
00189 $this->getResult()->unsetValue( 'warnings', $this->getModuleName() );
00190 }
00191 $msg = array();
00192 ApiResult::setContent( $msg, $warning );
00193 $this->getResult()->disableSizeCheck();
00194 $this->getResult()->addValue( 'warnings', $this->getModuleName(), $msg );
00195 $this->getResult()->enableSizeCheck();
00196 }
00197
00204 public function getCustomPrinter() {
00205 return null;
00206 }
00207
00212 public function makeHelpMsg() {
00213 static $lnPrfx = "\n ";
00214
00215 $msg = $this->getDescription();
00216
00217 if ( $msg !== false ) {
00218
00219 if ( !is_array( $msg ) ) {
00220 $msg = array(
00221 $msg
00222 );
00223 }
00224 $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
00225
00226 if ( $this->isReadMode() ) {
00227 $msg .= "\nThis module requires read rights.";
00228 }
00229 if ( $this->isWriteMode() ) {
00230 $msg .= "\nThis module requires write rights.";
00231 }
00232 if ( $this->mustBePosted() ) {
00233 $msg .= "\nThis module only accepts POST requests.";
00234 }
00235 if ( $this->isReadMode() || $this->isWriteMode() ||
00236 $this->mustBePosted() )
00237 {
00238 $msg .= "\n";
00239 }
00240
00241
00242 $paramsMsg = $this->makeHelpMsgParameters();
00243 if ( $paramsMsg !== false ) {
00244 $msg .= "Parameters:\n$paramsMsg";
00245 }
00246
00247
00248 $examples = $this->getExamples();
00249 if ( $examples !== false ) {
00250 if ( !is_array( $examples ) ) {
00251 $examples = array(
00252 $examples
00253 );
00254 }
00255 $msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n ";
00256 $msg .= implode( $lnPrfx, $examples ) . "\n";
00257 }
00258
00259 if ( $this->getMain()->getShowVersions() ) {
00260 $versions = $this->getVersion();
00261 $pattern = '/(\$.*) ([0-9a-z_]+\.php) (.*\$)/i';
00262 $callback = array( $this, 'makeHelpMsg_callback' );
00263
00264 if ( is_array( $versions ) ) {
00265 foreach ( $versions as &$v ) {
00266 $v = preg_replace_callback( $pattern, $callback, $v );
00267 }
00268 $versions = implode( "\n ", $versions );
00269 } else {
00270 $versions = preg_replace_callback( $pattern, $callback, $versions );
00271 }
00272
00273 $msg .= "Version:\n $versions\n";
00274 }
00275 }
00276
00277 return $msg;
00278 }
00279
00285 public function makeHelpMsgParameters() {
00286 $params = $this->getFinalParams();
00287 if ( $params ) {
00288
00289 $paramsDescription = $this->getFinalParamDescription();
00290 $msg = '';
00291 $paramPrefix = "\n" . str_repeat( ' ', 19 );
00292 foreach ( $params as $paramName => $paramSettings ) {
00293 $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
00294 if ( is_array( $desc ) ) {
00295 $desc = implode( $paramPrefix, $desc );
00296 }
00297
00298 $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ?
00299 $paramSettings[self::PARAM_DEPRECATED] : false;
00300 if ( $deprecated ) {
00301 $desc = "DEPRECATED! $desc";
00302 }
00303
00304 $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
00305 if ( isset( $type ) ) {
00306 if ( isset( $paramSettings[self::PARAM_ISMULTI] ) ) {
00307 $prompt = 'Values (separate with \'|\'): ';
00308 } else {
00309 $prompt = 'One value: ';
00310 }
00311
00312 if ( is_array( $type ) ) {
00313 $choices = array();
00314 $nothingPrompt = false;
00315 foreach ( $type as $t )
00316 if ( $t === '' ) {
00317 $nothingPrompt = 'Can be empty, or ';
00318 } else {
00319 $choices[] = $t;
00320 }
00321 $desc .= $paramPrefix . $nothingPrompt . $prompt . implode( ', ', $choices );
00322 } else {
00323 switch ( $type ) {
00324 case 'namespace':
00325
00326 $desc .= $paramPrefix . $prompt . implode( ', ', ApiBase::getValidNamespaces() );
00327 break;
00328 case 'limit':
00329 $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]} ({$paramSettings[self::PARAM_MAX2]} for bots) allowed.";
00330 break;
00331 case 'integer':
00332 $hasMin = isset( $paramSettings[self::PARAM_MIN] );
00333 $hasMax = isset( $paramSettings[self::PARAM_MAX] );
00334 if ( $hasMin || $hasMax ) {
00335 if ( !$hasMax ) {
00336 $intRangeStr = "The value must be no less than {$paramSettings[self::PARAM_MIN]}";
00337 } elseif ( !$hasMin ) {
00338 $intRangeStr = "The value must be no more than {$paramSettings[self::PARAM_MAX]}";
00339 } else {
00340 $intRangeStr = "The value must be between {$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
00341 }
00342
00343 $desc .= $paramPrefix . $intRangeStr;
00344 }
00345 break;
00346 }
00347 }
00348 }
00349
00350 $default = is_array( $paramSettings ) ? ( isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null ) : $paramSettings;
00351 if ( !is_null( $default ) && $default !== false ) {
00352 $desc .= $paramPrefix . "Default: $default";
00353 }
00354
00355 $msg .= sprintf( " %-14s - %s\n", $this->encodeParamName( $paramName ), $desc );
00356 }
00357 return $msg;
00358
00359 } else {
00360 return false;
00361 }
00362 }
00363
00368 public function makeHelpMsg_callback( $matches ) {
00369 global $wgAutoloadClasses, $wgAutoloadLocalClasses;
00370 if ( isset( $wgAutoloadLocalClasses[get_class( $this )] ) ) {
00371 $file = $wgAutoloadLocalClasses[get_class( $this )];
00372 } elseif ( isset( $wgAutoloadClasses[get_class( $this )] ) ) {
00373 $file = $wgAutoloadClasses[get_class( $this )];
00374 }
00375
00376
00377 $path = strstr( $file, 'includes/api/' );
00378 if ( $path === false ) {
00379 $path = strstr( $file, 'extensions/' );
00380 } else {
00381 $path = 'phase3/' . $path;
00382 }
00383
00384
00385
00386
00387
00388
00389 if ( $path ) {
00390 return "{$matches[0]}\n http://svn.wikimedia.org/" .
00391 "viewvc/mediawiki/trunk/" . dirname( $path ) .
00392 "/{$matches[2]}";
00393 }
00394 return $matches[0];
00395 }
00396
00401 protected function getDescription() {
00402 return false;
00403 }
00404
00409 protected function getExamples() {
00410 return false;
00411 }
00412
00420 protected function getAllowedParams() {
00421 return false;
00422 }
00423
00430 protected function getParamDescription() {
00431 return false;
00432 }
00433
00439 public function getFinalParams() {
00440 $params = $this->getAllowedParams();
00441 wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params ) );
00442 return $params;
00443 }
00444
00450 public function getFinalParamDescription() {
00451 $desc = $this->getParamDescription();
00452 wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
00453 return $desc;
00454 }
00455
00462 public function encodeParamName( $paramName ) {
00463 return $this->mModulePrefix . $paramName;
00464 }
00465
00475 public function extractRequestParams( $parseLimit = true ) {
00476
00477 if ( !isset( $this->mParamCache[$parseLimit] ) ) {
00478 $params = $this->getFinalParams();
00479 $results = array();
00480
00481 if ( $params ) {
00482 foreach ( $params as $paramName => $paramSettings ) {
00483 $results[$paramName] = $this->getParameterFromSettings(
00484 $paramName, $paramSettings, $parseLimit );
00485 }
00486 }
00487 $this->mParamCache[$parseLimit] = $results;
00488 }
00489 return $this->mParamCache[$parseLimit];
00490 }
00491
00498 protected function getParameter( $paramName, $parseLimit = true ) {
00499 $params = $this->getFinalParams();
00500 $paramSettings = $params[$paramName];
00501 return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
00502 }
00503
00508 public function requireOnlyOneParameter( $params ) {
00509 $required = func_get_args();
00510 array_shift( $required );
00511
00512 $intersection = array_intersect( array_keys( array_filter( $params,
00513 create_function( '$x', 'return !is_null($x) && $x !== false;' )
00514 ) ), $required );
00515 if ( count( $intersection ) > 1 ) {
00516 $this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' );
00517 } elseif ( count( $intersection ) == 0 ) {
00518 $this->dieUsage( 'One of the parameters ' . implode( ', ', $required ) . ' is required', 'missingparam' );
00519 }
00520 }
00521
00527 public static function getValidNamespaces() {
00528 static $mValidNamespaces = null;
00529
00530 if ( is_null( $mValidNamespaces ) ) {
00531 global $wgContLang;
00532 $mValidNamespaces = array();
00533 foreach ( array_keys( $wgContLang->getNamespaces() ) as $ns ) {
00534 if ( $ns >= 0 ) {
00535 $mValidNamespaces[] = $ns;
00536 }
00537 }
00538 }
00539
00540 return $mValidNamespaces;
00541 }
00542
00552 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
00553
00554 $encParamName = $this->encodeParamName( $paramName );
00555
00556 if ( !is_array( $paramSettings ) ) {
00557 $default = $paramSettings;
00558 $multi = false;
00559 $type = gettype( $paramSettings );
00560 $dupes = false;
00561 $deprecated = false;
00562 } else {
00563 $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
00564 $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
00565 $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
00566 $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] ) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false;
00567 $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? $paramSettings[self::PARAM_DEPRECATED] : false;
00568
00569
00570 if ( !isset( $type ) ) {
00571 if ( isset( $default ) ) {
00572 $type = gettype( $default );
00573 } else {
00574 $type = 'NULL';
00575 }
00576 }
00577 }
00578
00579 if ( $type == 'boolean' ) {
00580 if ( isset( $default ) && $default !== false ) {
00581
00582 ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'" );
00583 }
00584
00585 $value = $this->getMain()->getRequest()->getCheck( $encParamName );
00586 } else {
00587 $value = $this->getMain()->getRequest()->getVal( $encParamName, $default );
00588
00589 if ( isset( $value ) && $type == 'namespace' ) {
00590 $type = ApiBase::getValidNamespaces();
00591 }
00592 }
00593
00594 if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
00595 $value = $this->parseMultiValue( $encParamName, $value, $multi, is_array( $type ) ? $type : null );
00596 }
00597
00598
00599
00600 if ( isset( $value ) ) {
00601 if ( !is_array( $type ) ) {
00602 switch ( $type ) {
00603 case 'NULL':
00604 break;
00605 case 'string':
00606 break;
00607 case 'integer':
00608
00609 $value = is_array( $value ) ? array_map( 'intval', $value ) : intval( $value );
00610 $min = isset ( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
00611 $max = isset ( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
00612
00613 if ( !is_null( $min ) || !is_null( $max ) ) {
00614 $values = is_array( $value ) ? $value : array( $value );
00615 foreach ( $values as &$v ) {
00616 $this->validateLimit( $paramName, $v, $min, $max );
00617 }
00618 }
00619 break;
00620 case 'limit':
00621 if ( !$parseLimit ) {
00622
00623 break;
00624 }
00625 if ( !isset( $paramSettings[self::PARAM_MAX] ) || !isset( $paramSettings[self::PARAM_MAX2] ) ) {
00626 ApiBase::dieDebug( __METHOD__, "MAX1 or MAX2 are not defined for the limit $encParamName" );
00627 }
00628 if ( $multi ) {
00629 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00630 }
00631 $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
00632 if ( $value == 'max' ) {
00633 $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX];
00634 $this->getResult()->addValue( 'limits', $this->getModuleName(), $value );
00635 } else {
00636 $value = intval( $value );
00637 $this->validateLimit( $paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2] );
00638 }
00639 break;
00640 case 'boolean':
00641 if ( $multi )
00642 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00643 break;
00644 case 'timestamp':
00645 if ( $multi ) {
00646 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
00647 }
00648 $value = wfTimestamp( TS_UNIX, $value );
00649 if ( $value === 0 ) {
00650 $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
00651 }
00652 $value = wfTimestamp( TS_MW, $value );
00653 break;
00654 case 'user':
00655 $title = Title::makeTitleSafe( NS_USER, $value );
00656 if ( is_null( $title ) ) {
00657 $this->dieUsage( "Invalid value for user parameter $encParamName", "baduser_{$encParamName}" );
00658 }
00659 $value = $title->getText();
00660 break;
00661 default:
00662 ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
00663 }
00664 }
00665
00666
00667 if ( is_array( $value ) && !$dupes ) {
00668 $value = array_unique( $value );
00669 }
00670
00671
00672 if ( $deprecated && $value !== false ) {
00673 $this->setWarning( "The $encParamName parameter has been deprecated." );
00674 }
00675 }
00676
00677 return $value;
00678 }
00679
00693 protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
00694 if ( trim( $value ) === '' && $allowMultiple ) {
00695 return array();
00696 }
00697
00698
00699 $valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
00700 $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() ?
00701 self::LIMIT_SML2 : self::LIMIT_SML1;
00702
00703 if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
00704 $this->setWarning( "Too many values supplied for parameter '$valueName': the limit is $sizeLimit" );
00705 }
00706
00707 if ( !$allowMultiple && count( $valuesList ) != 1 ) {
00708 $possibleValues = is_array( $allowedValues ) ? "of '" . implode( "', '", $allowedValues ) . "'" : '';
00709 $this->dieUsage( "Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName" );
00710 }
00711
00712 if ( is_array( $allowedValues ) ) {
00713
00714 $unknown = array_diff( $valuesList, $allowedValues );
00715 if ( count( $unknown ) ) {
00716 if ( $allowMultiple ) {
00717 $s = count( $unknown ) > 1 ? 's' : '';
00718 $vals = implode( ", ", $unknown );
00719 $this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
00720 } else {
00721 $this->dieUsage( "Unrecognized value for parameter '$valueName': {$valuesList[0]}", "unknown_$valueName" );
00722 }
00723 }
00724
00725 $valuesList = array_intersect( $valuesList, $allowedValues );
00726 }
00727
00728 return $allowMultiple ? $valuesList : $valuesList[0];
00729 }
00730
00740 function validateLimit( $paramName, &$value, $min, $max, $botMax = null ) {
00741 if ( !is_null( $min ) && $value < $min ) {
00742 $this->setWarning( $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)" );
00743 $value = $min;
00744 }
00745
00746
00747 if ( $this->getMain()->isInternalMode() ) {
00748 return;
00749 }
00750
00751
00752
00753 if ( !is_null( $max ) && $value > $max ) {
00754 if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
00755 if ( $value > $botMax ) {
00756 $this->setWarning( $this->encodeParamName( $paramName ) . " may not be over $botMax (set to $value) for bots or sysops" );
00757 $value = $botMax;
00758 }
00759 } else {
00760 $this->setWarning( $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users" );
00761 $value = $max;
00762 }
00763 }
00764 }
00765
00772 public static function truncateArray( &$arr, $limit ) {
00773 $modified = false;
00774 while ( count( $arr ) > $limit ) {
00775 $junk = array_pop( $arr );
00776 $modified = true;
00777 }
00778 return $modified;
00779 }
00780
00792 public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
00793 wfProfileClose();
00794 throw new UsageException( $description, $this->encodeParamName( $errorCode ), $httpRespCode, $extradata );
00795 }
00796
00800 public static $messageMap = array(
00801
00802 'unknownerror' => array( 'code' => 'unknownerror', 'info' => "Unknown error: ``\$1''" ),
00803 'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
00804
00805
00806 'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ),
00807 'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ),
00808 'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the ``\$1'' namespace" ),
00809 'customcssjsprotected' => array( 'code' => 'customcssjsprotected', 'info' => "You're not allowed to edit custom CSS and JavaScript pages" ),
00810 'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ),
00811 'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The ``\$1'' right is required to edit this page" ),
00812 'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ),
00813 'badaccess-group0' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ),
00814 'badaccess-groups' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ),
00815 'titleprotected' => array( 'code' => 'protectedtitle', 'info' => "This title has been protected from creation" ),
00816 'nocreate-loggedin' => array( 'code' => 'cantcreate', 'info' => "You don't have permission to create new pages" ),
00817 'nocreatetext' => array( 'code' => 'cantcreate-anon', 'info' => "Anonymous users can't create new pages" ),
00818 'movenologintext' => array( 'code' => 'cantmove-anon', 'info' => "Anonymous users can't move pages" ),
00819 'movenotallowed' => array( 'code' => 'cantmove', 'info' => "You don't have permission to move pages" ),
00820 'confirmedittext' => array( 'code' => 'confirmemail', 'info' => "You must confirm your e-mail address before you can edit" ),
00821 'blockedtext' => array( 'code' => 'blocked', 'info' => "You have been blocked from editing" ),
00822 'autoblockedtext' => array( 'code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user" ),
00823
00824
00825 'actionthrottledtext' => array( 'code' => 'ratelimited', 'info' => "You've exceeded your rate limit. Please wait some time and try again" ),
00826 'alreadyrolled' => array( 'code' => 'alreadyrolled', 'info' => "The page you tried to rollback was already rolled back" ),
00827 'cantrollback' => array( 'code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author" ),
00828 'readonlytext' => array( 'code' => 'readonly', 'info' => "The wiki is currently in read-only mode" ),
00829 'sessionfailure' => array( 'code' => 'badtoken', 'info' => "Invalid token" ),
00830 'cannotdelete' => array( 'code' => 'cantdelete', 'info' => "Couldn't delete ``\$1''. Maybe it was deleted already by someone else" ),
00831 'notanarticle' => array( 'code' => 'missingtitle', 'info' => "The page you requested doesn't exist" ),
00832 'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself" ),
00833 'immobile_namespace' => array( 'code' => 'immobilenamespace', 'info' => "You tried to move pages from or to a namespace that is protected from moving" ),
00834 'articleexists' => array( 'code' => 'articleexists', 'info' => "The destination article already exists and is not a redirect to the source article" ),
00835 'protectedpage' => array( 'code' => 'protectedpage', 'info' => "You don't have permission to perform this move" ),
00836 'hookaborted' => array( 'code' => 'hookaborted', 'info' => "The modification you tried to make was aborted by an extension hook" ),
00837 'cantmove-titleprotected' => array( 'code' => 'protectedtitle', 'info' => "The destination article has been protected from creation" ),
00838 'imagenocrossnamespace' => array( 'code' => 'nonfilenamespace', 'info' => "Can't move a file to a non-file namespace" ),
00839 'imagetypemismatch' => array( 'code' => 'filetypemismatch', 'info' => "The new file extension doesn't match its type" ),
00840
00841
00842 'ip_range_invalid' => array( 'code' => 'invalidrange', 'info' => "Invalid IP range" ),
00843 'range_block_disabled' => array( 'code' => 'rangedisabled', 'info' => "Blocking IP ranges has been disabled" ),
00844 'nosuchusershort' => array( 'code' => 'nosuchuser', 'info' => "The user you specified doesn't exist" ),
00845 'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
00846 'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
00847 'ipb_already_blocked' => array( 'code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked" ),
00848 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address ``\$1'' was blocked as part of range ``\$2''. You can't unblock the IP invidually, but you can unblock the range as a whole." ),
00849 'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
00850 'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed e-mail address, or you are not allowed to send e-mail to other users, so you cannot send e-mail" ),
00851 'usermaildisabled' => array( 'code' => 'usermaildisabled', 'info' => "User email has been disabled" ),
00852 'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending e-mail" ),
00853 'notarget' => array( 'code' => 'notarget', 'info' => "You have not specified a valid target for this action" ),
00854 'noemail' => array( 'code' => 'noemail', 'info' => "The user has not specified a valid e-mail address, or has chosen not to receive e-mail from other users" ),
00855 'rcpatroldisabled' => array( 'code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki" ),
00856 'markedaspatrollederror-noautopatrol' => array( 'code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes" ),
00857 'delete-toobig' => array( 'code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions" ),
00858 'movenotallowedfile' => array( 'code' => 'cantmovefile', 'info' => "You don't have permission to move files" ),
00859 'userrights-no-interwiki' => array( 'code' => 'nointerwikiuserrights', 'info' => "You don't have permission to change user rights on other wikis" ),
00860 'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database ``\$1'' does not exist or is not local" ),
00861 'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
00862 'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
00863
00864
00865 'readrequired' => array( 'code' => 'readapidenied', 'info' => "You need read permission to use this module" ),
00866 'writedisabled' => array( 'code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file" ),
00867 'writerequired' => array( 'code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API" ),
00868 'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
00869 'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title ``\$1''" ),
00870 'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
00871 'nosuchrevid' => array( 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ),
00872 'nosuchuser' => array( 'code' => 'nosuchuser', 'info' => "User ``\$1'' doesn't exist" ),
00873 'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
00874 'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time ``\$1''" ),
00875 'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time ``\$1'' is in the past" ),
00876 'create-titleexists' => array( 'code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'" ),
00877 'missingtitle-createonly' => array( 'code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'" ),
00878 'cantblock' => array( 'code' => 'cantblock', 'info' => "You don't have permission to block users" ),
00879 'canthide' => array( 'code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log" ),
00880 'cantblock-email' => array( 'code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending e-mail through the wiki" ),
00881 'unblock-notarget' => array( 'code' => 'notarget', 'info' => "Either the id or the user parameter must be set" ),
00882 'unblock-idanduser' => array( 'code' => 'idanduser', 'info' => "The id and user parameters can't be used together" ),
00883 'cantunblock' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to unblock users" ),
00884 'cannotundelete' => array( 'code' => 'cantundelete', 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already" ),
00885 'permdenied-undelete' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions" ),
00886 'createonly-exists' => array( 'code' => 'articleexists', 'info' => "The article you tried to create has been created already" ),
00887 'nocreate-missing' => array( 'code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist" ),
00888 'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid ``\$1''" ),
00889 'cantpurge' => array( 'code' => 'cantpurge', 'info' => "Only users with the 'purge' right can purge pages via the API" ),
00890 'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type ``\$1''" ),
00891 'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level ``\$1''" ),
00892 'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ),
00893 'cantimport' => array( 'code' => 'cantimport', 'info' => "You don't have permission to import pages" ),
00894 'cantimport-upload' => array( 'code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages" ),
00895 'nouploadmodule' => array( 'code' => 'nomodule', 'info' => 'No upload module set' ),
00896 'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ),
00897 'importuploaderrorsize' => array( 'code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size' ),
00898 'importuploaderrorpartial' => array( 'code' => 'partialupload', 'info' => 'The file was only partially uploaded' ),
00899 'importuploaderrortemp' => array( 'code' => 'notempdir', 'info' => 'The temporary upload directory is missing' ),
00900 'importcantopen' => array( 'code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file" ),
00901 'import-noarticle' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
00902 'importbadinterwiki' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
00903 'import-unknownerror' => array( 'code' => 'import-unknownerror', 'info' => "Unknown error on import: ``\$1''" ),
00904 'cantoverwrite-sharedfile' => array( 'code' => 'cantoverwrite-sharedfile', 'info' => 'The target file exists on a shared repository and you do not have permission to override it' ),
00905 'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ),
00906 'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ),
00907 'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ),
00908
00909
00910 'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
00911 'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ),
00912 'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: ``\$1''" ),
00913 'filtered' => array( 'code' => 'filtered', 'info' => "The filter callback function refused your edit" ),
00914 'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ),
00915 'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
00916 'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),
00917 'wasdeleted' => array( 'code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp" ),
00918 'blankpage' => array( 'code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed" ),
00919 'editconflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
00920 'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
00921 'missingtext' => array( 'code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set" ),
00922 'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ),
00923 'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''" ),
00924 'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ),
00925
00926
00927 'invalid-session-key' => array( 'code' => 'invalid-session-key', 'info' => 'Not a valid session key' ),
00928 'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
00929 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
00930 );
00931
00935 public function dieReadOnly() {
00936 $parsed = $this->parseMsg( array( 'readonlytext' ) );
00937 $this->dieUsage( $parsed['info'], $parsed['code'], 0,
00938 array( 'readonlyreason' => wfReadOnlyReason() ) );
00939 }
00940
00945 public function dieUsageMsg( $error ) {
00946 $parsed = $this->parseMsg( $error );
00947 $this->dieUsage( $parsed['info'], $parsed['code'] );
00948 }
00949
00955 public function parseMsg( $error ) {
00956 $key = array_shift( $error );
00957 if ( isset( self::$messageMap[$key] ) ) {
00958 return array( 'code' =>
00959 wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
00960 'info' =>
00961 wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
00962 );
00963 }
00964
00965 return $this->parseMsg( array( 'unknownerror', $key ) );
00966 }
00967
00973 protected static function dieDebug( $method, $message ) {
00974 wfDebugDieBacktrace( "Internal error in $method: $message" );
00975 }
00976
00981 public function shouldCheckMaxlag() {
00982 return true;
00983 }
00984
00989 public function isReadMode() {
00990 return true;
00991 }
00996 public function isWriteMode() {
00997 return false;
00998 }
00999
01004 public function mustBePosted() {
01005 return false;
01006 }
01007
01012 public function needsToken() {
01013 return false;
01014 }
01015
01020 public function getTokenSalt() {
01021 return false;
01022 }
01023
01028 public function getPossibleErrors() {
01029 $ret = array();
01030
01031 if ( $this->mustBePosted() ) {
01032 $ret[] = array( 'mustbeposted', $this->getModuleName() );
01033 }
01034
01035 if ( $this->isReadMode() ) {
01036 $ret[] = array( 'readrequired' );
01037 }
01038
01039 if ( $this->isWriteMode() ) {
01040 $ret[] = array( 'writerequired' );
01041 $ret[] = array( 'writedisabled' );
01042 }
01043
01044 if ( $this->needsToken() ) {
01045 $ret[] = array( 'missingparam', 'token' );
01046 $ret[] = array( 'sessionfailure' );
01047 }
01048
01049 return $ret;
01050 }
01051
01057 public function parseErrors( $errors ) {
01058 $ret = array();
01059
01060 foreach ( $errors as $row ) {
01061 if ( isset( $row['code'] ) && isset( $row['info'] ) ) {
01062 $ret[] = $row;
01063 } else {
01064 $ret[] = $this->parseMsg( $row );
01065 }
01066 }
01067 return $ret;
01068 }
01069
01073 private $mTimeIn = 0, $mModuleTime = 0;
01074
01078 public function profileIn() {
01079 if ( $this->mTimeIn !== 0 ) {
01080 ApiBase::dieDebug( __METHOD__, 'called twice without calling profileOut()' );
01081 }
01082 $this->mTimeIn = microtime( true );
01083 wfProfileIn( $this->getModuleProfileName() );
01084 }
01085
01089 public function profileOut() {
01090 if ( $this->mTimeIn === 0 ) {
01091 ApiBase::dieDebug( __METHOD__, 'called without calling profileIn() first' );
01092 }
01093 if ( $this->mDBTimeIn !== 0 ) {
01094 ApiBase::dieDebug( __METHOD__, 'must be called after database profiling is done with profileDBOut()' );
01095 }
01096
01097 $this->mModuleTime += microtime( true ) - $this->mTimeIn;
01098 $this->mTimeIn = 0;
01099 wfProfileOut( $this->getModuleProfileName() );
01100 }
01101
01106 public function safeProfileOut() {
01107 if ( $this->mTimeIn !== 0 ) {
01108 if ( $this->mDBTimeIn !== 0 ) {
01109 $this->profileDBOut();
01110 }
01111 $this->profileOut();
01112 }
01113 }
01114
01119 public function getProfileTime() {
01120 if ( $this->mTimeIn !== 0 ) {
01121 ApiBase::dieDebug( __METHOD__, 'called without calling profileOut() first' );
01122 }
01123 return $this->mModuleTime;
01124 }
01125
01129 private $mDBTimeIn = 0, $mDBTime = 0;
01130
01134 public function profileDBIn() {
01135 if ( $this->mTimeIn === 0 ) {
01136 ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
01137 }
01138 if ( $this->mDBTimeIn !== 0 ) {
01139 ApiBase::dieDebug( __METHOD__, 'called twice without calling profileDBOut()' );
01140 }
01141 $this->mDBTimeIn = microtime( true );
01142 wfProfileIn( $this->getModuleProfileName( true ) );
01143 }
01144
01148 public function profileDBOut() {
01149 if ( $this->mTimeIn === 0 ) {
01150 ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
01151 }
01152 if ( $this->mDBTimeIn === 0 ) {
01153 ApiBase::dieDebug( __METHOD__, 'called without calling profileDBIn() first' );
01154 }
01155
01156 $time = microtime( true ) - $this->mDBTimeIn;
01157 $this->mDBTimeIn = 0;
01158
01159 $this->mDBTime += $time;
01160 $this->getMain()->mDBTime += $time;
01161 wfProfileOut( $this->getModuleProfileName( true ) );
01162 }
01163
01168 public function getProfileDBTime() {
01169 if ( $this->mDBTimeIn !== 0 ) {
01170 ApiBase::dieDebug( __METHOD__, 'called without calling profileDBOut() first' );
01171 }
01172 return $this->mDBTime;
01173 }
01174
01181 public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
01182 print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
01183 var_export( $value );
01184 if ( $backtrace ) {
01185 print "\n" . wfBacktrace();
01186 }
01187 print "\n</pre>\n";
01188 }
01189
01194 public static function getBaseVersion() {
01195 return __CLASS__ . ': $Id: ApiBase.php 79562 2011-01-04 06:15:54Z tstarling $';
01196 }
01197 }