00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 if (!defined('MEDIAWIKI')) {
00027
00028 require_once ('ApiBase.php');
00029 }
00030
00048 class ApiMain extends ApiBase {
00049
00053 const API_DEFAULT_FORMAT = 'xmlfm';
00054
00058 private static $Modules = array (
00059 'login' => 'ApiLogin',
00060 'logout' => 'ApiLogout',
00061 'query' => 'ApiQuery',
00062 'expandtemplates' => 'ApiExpandTemplates',
00063 'parse' => 'ApiParse',
00064 'opensearch' => 'ApiOpenSearch',
00065 'feedwatchlist' => 'ApiFeedWatchlist',
00066 'help' => 'ApiHelp',
00067 'paraminfo' => 'ApiParamInfo',
00068
00069
00070 'purge' => 'ApiPurge',
00071 'rollback' => 'ApiRollback',
00072 'delete' => 'ApiDelete',
00073 'undelete' => 'ApiUndelete',
00074 'protect' => 'ApiProtect',
00075 'block' => 'ApiBlock',
00076 'unblock' => 'ApiUnblock',
00077 'move' => 'ApiMove',
00078 'edit' => 'ApiEditPage',
00079 'emailuser' => 'ApiEmailUser',
00080 'watch' => 'ApiWatch',
00081 'patrol' => 'ApiPatrol',
00082 'import' => 'ApiImport',
00083 );
00084
00088 private static $Formats = array (
00089 'json' => 'ApiFormatJson',
00090 'jsonfm' => 'ApiFormatJson',
00091 'php' => 'ApiFormatPhp',
00092 'phpfm' => 'ApiFormatPhp',
00093 'wddx' => 'ApiFormatWddx',
00094 'wddxfm' => 'ApiFormatWddx',
00095 'xml' => 'ApiFormatXml',
00096 'xmlfm' => 'ApiFormatXml',
00097 'yaml' => 'ApiFormatYaml',
00098 'yamlfm' => 'ApiFormatYaml',
00099 'rawfm' => 'ApiFormatJson',
00100 'txt' => 'ApiFormatTxt',
00101 'txtfm' => 'ApiFormatTxt',
00102 'dbg' => 'ApiFormatDbg',
00103 'dbgfm' => 'ApiFormatDbg'
00104 );
00105
00112 private static $mRights = array('writeapi' => array(
00113 'msg' => 'Use of the write API',
00114 'params' => array()
00115 ),
00116 'apihighlimits' => array(
00117 'msg' => 'Use higher limits in API queries (Slow queries: $1 results; Fast queries: $2 results). The limits for slow queries also apply to multivalue parameters.',
00118 'params' => array (ApiMain::LIMIT_SML2, ApiMain::LIMIT_BIG2)
00119 )
00120 );
00121
00122
00123 private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
00124 private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest, $mInternalMode, $mSquidMaxage;
00125
00132 public function __construct($request, $enableWrite = false) {
00133
00134 $this->mInternalMode = ($request instanceof FauxRequest);
00135
00136
00137 parent :: __construct($this, $this->mInternalMode ? 'main_int' : 'main');
00138
00139 if (!$this->mInternalMode) {
00140
00141
00142
00143
00144 global $wgUser;
00145
00146 if( $request->getVal( 'callback' ) !== null ) {
00147
00148
00149 wfDebug( "API: stripping user credentials for JSON callback\n" );
00150 $wgUser = new User();
00151 }
00152 }
00153
00154 global $wgAPIModules;
00155 $this->mModules = $wgAPIModules + self :: $Modules;
00156
00157 $this->mModuleNames = array_keys($this->mModules);
00158 $this->mFormats = self :: $Formats;
00159 $this->mFormatNames = array_keys($this->mFormats);
00160
00161 $this->mResult = new ApiResult($this);
00162 $this->mShowVersions = false;
00163 $this->mEnableWrite = $enableWrite;
00164
00165 $this->mRequest = & $request;
00166
00167 $this->mSquidMaxage = -1;
00168 $this->mCommit = false;
00169 }
00170
00174 public function isInternalMode() {
00175 return $this->mInternalMode;
00176 }
00177
00181 public function getRequest() {
00182 return $this->mRequest;
00183 }
00184
00188 public function getResult() {
00189 return $this->mResult;
00190 }
00191
00196 public function requestWriteMode() {}
00197
00201 public function setCacheMaxAge($maxage) {
00202 $this->mSquidMaxage = $maxage;
00203 }
00204
00208 public function createPrinterByName($format) {
00209 if( !isset( $this->mFormats[$format] ) )
00210 $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
00211 return new $this->mFormats[$format] ($this, $format);
00212 }
00213
00217 public function execute() {
00218 $this->profileIn();
00219 if ($this->mInternalMode)
00220 $this->executeAction();
00221 else
00222 $this->executeActionWithErrorHandling();
00223
00224 $this->profileOut();
00225 }
00226
00231 protected function executeActionWithErrorHandling() {
00232
00233
00234
00235 ob_start();
00236
00237 try {
00238 $this->executeAction();
00239 } catch (Exception $e) {
00240
00241 if ( $e instanceof MWException ) {
00242 wfDebugLog( 'exception', $e->getLogMessage() );
00243 }
00244
00245
00246
00247
00248
00249
00250
00251 $errCode = $this->substituteResultWithError($e);
00252
00253
00254 $this->setCacheMaxAge(0);
00255
00256 $headerStr = 'MediaWiki-API-Error: ' . $errCode;
00257 if ($e->getCode() === 0)
00258 header($headerStr);
00259 else
00260 header($headerStr, true, $e->getCode());
00261
00262
00263 ob_clean();
00264
00265
00266 $this->mPrinter->safeProfileOut();
00267 $this->printResult(true);
00268 }
00269
00270 if($this->mSquidMaxage == -1)
00271 {
00272 # Nobody called setCacheMaxAge(), use the (s)maxage parameters
00273 $smaxage = $this->getParameter('smaxage');
00274 $maxage = $this->getParameter('maxage');
00275 }
00276 else
00277 $smaxage = $maxage = $this->mSquidMaxage;
00278
00279
00280
00281 $exp = min($smaxage, $maxage);
00282 $expires = ($exp == 0 ? 1 : time() + $exp);
00283 header('Expires: ' . wfTimestamp(TS_RFC2822, $expires));
00284 header('Cache-Control: s-maxage=' . $smaxage . ', must-revalidate, max-age=' . $maxage);
00285
00286 if($this->mPrinter->getIsHtml())
00287 echo wfReportTime();
00288
00289 ob_end_flush();
00290 }
00291
00296 protected function substituteResultWithError($e) {
00297
00298
00299 if (!isset ($this->mPrinter)) {
00300
00301 $value = $this->getRequest()->getVal('format', self::API_DEFAULT_FORMAT);
00302 if (!in_array($value, $this->mFormatNames))
00303 $value = self::API_DEFAULT_FORMAT;
00304
00305 $this->mPrinter = $this->createPrinterByName($value);
00306 if ($this->mPrinter->getNeedsRawData())
00307 $this->getResult()->setRawMode();
00308 }
00309
00310 if ($e instanceof UsageException) {
00311
00312
00313
00314 $errMessage = array (
00315 'code' => $e->getCodeString(),
00316 'info' => $e->getMessage());
00317
00318
00319 if ($this->mPrinter->getIsHtml() || $this->mAction == 'help')
00320 ApiResult :: setContent($errMessage, $this->makeHelpMsg());
00321
00322 } else {
00323 global $wgShowSQLErrors, $wgShowExceptionDetails;
00324
00325
00326
00327 if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
00328 $info = "Database query error";
00329 } else {
00330 $info = "Exception Caught: {$e->getMessage()}";
00331 }
00332
00333 $errMessage = array (
00334 'code' => 'internal_api_error_'. get_class($e),
00335 'info' => $info,
00336 );
00337 ApiResult :: setContent($errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : "" );
00338 }
00339
00340 $this->getResult()->reset();
00341 $this->getResult()->disableSizeCheck();
00342
00343 $requestid = $this->getParameter('requestid');
00344 if(!is_null($requestid))
00345 $this->getResult()->addValue(null, 'requestid', $requestid);
00346 $this->getResult()->addValue(null, 'error', $errMessage);
00347
00348 return $errMessage['code'];
00349 }
00350
00354 protected function executeAction() {
00355
00356 $requestid = $this->getParameter('requestid');
00357 if(!is_null($requestid))
00358 $this->getResult()->addValue(null, 'requestid', $requestid);
00359
00360 $params = $this->extractRequestParams();
00361
00362 $this->mShowVersions = $params['version'];
00363 $this->mAction = $params['action'];
00364
00365 if( !is_string( $this->mAction ) ) {
00366 $this->dieUsage( "The API requires a valid action parameter", 'unknown_action' );
00367 }
00368
00369
00370 $module = new $this->mModules[$this->mAction] ($this, $this->mAction);
00371
00372 if( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
00373
00374 global $wgShowHostnames;
00375 $maxLag = $params['maxlag'];
00376 list( $host, $lag ) = wfGetLB()->getMaxLag();
00377 if ( $lag > $maxLag ) {
00378 header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
00379 header( 'X-Database-Lag: ' . intval( $lag ) );
00380
00381 if( $wgShowHostnames ) {
00382 $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
00383 } else {
00384 $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
00385 }
00386 return;
00387 }
00388 }
00389
00390 global $wgUser;
00391 if ($module->isReadMode() && !$wgUser->isAllowed('read'))
00392 $this->dieUsageMsg(array('readrequired'));
00393 if ($module->isWriteMode()) {
00394 if (!$this->mEnableWrite)
00395 $this->dieUsageMsg(array('writedisabled'));
00396 if (!$wgUser->isAllowed('writeapi'))
00397 $this->dieUsageMsg(array('writerequired'));
00398 if (wfReadOnly())
00399 $this->dieUsageMsg(array('readonlytext'));
00400 }
00401
00402 if (!$this->mInternalMode) {
00403
00404 if($module->mustBePosted() && !$this->mRequest->wasPosted())
00405 $this->dieUsage("The {$this->mAction} module requires a POST request", 'mustbeposted');
00406
00407
00408 $this->mPrinter = $module->getCustomPrinter();
00409 if (is_null($this->mPrinter)) {
00410
00411 $this->mPrinter = $this->createPrinterByName($params['format']);
00412 }
00413
00414 if ($this->mPrinter->getNeedsRawData())
00415 $this->getResult()->setRawMode();
00416 }
00417
00418
00419 $module->profileIn();
00420 $module->execute();
00421 wfRunHooks('APIAfterExecute', array(&$module));
00422 $module->profileOut();
00423
00424 if (!$this->mInternalMode) {
00425
00426 $this->printResult(false);
00427 }
00428 }
00429
00433 protected function printResult($isError) {
00434 $this->getResult()->cleanUpUTF8();
00435 $printer = $this->mPrinter;
00436 $printer->profileIn();
00437
00438
00439
00440
00441 $printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError )
00442 && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
00443
00444 $printer->initPrinter($isError);
00445
00446 $printer->execute();
00447 $printer->closePrinter();
00448 $printer->profileOut();
00449 }
00450
00451 public function isReadMode() {
00452 return false;
00453 }
00454
00458 public function getAllowedParams() {
00459 return array (
00460 'format' => array (
00461 ApiBase :: PARAM_DFLT => ApiMain :: API_DEFAULT_FORMAT,
00462 ApiBase :: PARAM_TYPE => $this->mFormatNames
00463 ),
00464 'action' => array (
00465 ApiBase :: PARAM_DFLT => 'help',
00466 ApiBase :: PARAM_TYPE => $this->mModuleNames
00467 ),
00468 'version' => false,
00469 'maxlag' => array (
00470 ApiBase :: PARAM_TYPE => 'integer'
00471 ),
00472 'smaxage' => array (
00473 ApiBase :: PARAM_TYPE => 'integer',
00474 ApiBase :: PARAM_DFLT => 0
00475 ),
00476 'maxage' => array (
00477 ApiBase :: PARAM_TYPE => 'integer',
00478 ApiBase :: PARAM_DFLT => 0
00479 ),
00480 'requestid' => null,
00481 );
00482 }
00483
00487 public function getParamDescription() {
00488 return array (
00489 'format' => 'The format of the output',
00490 'action' => 'What action you would like to perform',
00491 'version' => 'When showing help, include version for each module',
00492 'maxlag' => 'Maximum lag',
00493 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
00494 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
00495 'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
00496 );
00497 }
00498
00502 public function getDescription() {
00503 return array (
00504 '',
00505 '',
00506 '******************************************************************',
00507 '** **',
00508 '** This is an auto-generated MediaWiki API documentation page **',
00509 '** **',
00510 '** Documentation and Examples: **',
00511 '** http://www.mediawiki.org/wiki/API **',
00512 '** **',
00513 '******************************************************************',
00514 '',
00515 'Status: All features shown on this page should be working, but the API',
00516 ' is still in active development, and may change at any time.',
00517 ' Make sure to monitor our mailing list for any updates.',
00518 '',
00519 'Documentation: http://www.mediawiki.org/wiki/API',
00520 'Mailing list: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
00521 'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
00522 '',
00523 '',
00524 '',
00525 '',
00526 '',
00527 );
00528 }
00529
00533 protected function getCredits() {
00534 return array(
00535 'API developers:',
00536 ' Roan Kattouw <Firstname>.<Lastname>@home.nl (lead developer Sep 2007-present)',
00537 ' Victor Vasiliev - vasilvv at gee mail dot com',
00538 ' Bryan Tong Minh - bryan . tongminh @ gmail . com',
00539 ' Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)',
00540 '',
00541 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
00542 'or file a bug report at http://bugzilla.wikimedia.org/'
00543 );
00544 }
00545
00549 public function makeHelpMsg() {
00550
00551 $this->mPrinter->setHelp();
00552
00553
00554 $msg = parent :: makeHelpMsg();
00555
00556 $astriks = str_repeat('*** ', 10);
00557 $msg .= "\n\n$astriks Modules $astriks\n\n";
00558 foreach( $this->mModules as $moduleName => $unused ) {
00559 $module = new $this->mModules[$moduleName] ($this, $moduleName);
00560 $msg .= self::makeHelpMsgHeader($module, 'action');
00561 $msg2 = $module->makeHelpMsg();
00562 if ($msg2 !== false)
00563 $msg .= $msg2;
00564 $msg .= "\n";
00565 }
00566
00567 $msg .= "\n$astriks Permissions $astriks\n\n";
00568 foreach ( self :: $mRights as $right => $rightMsg ) {
00569 $groups = User::getGroupsWithPermission( $right );
00570 $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
00571 "\nGranted to:\n " . str_replace( "*", "all", implode( ", ", $groups ) ) . "\n";
00572
00573 }
00574
00575 $msg .= "\n$astriks Formats $astriks\n\n";
00576 foreach( $this->mFormats as $formatName => $unused ) {
00577 $module = $this->createPrinterByName($formatName);
00578 $msg .= self::makeHelpMsgHeader($module, 'format');
00579 $msg2 = $module->makeHelpMsg();
00580 if ($msg2 !== false)
00581 $msg .= $msg2;
00582 $msg .= "\n";
00583 }
00584
00585 $msg .= "\n*** Credits: ***\n " . implode("\n ", $this->getCredits()) . "\n";
00586
00587
00588 return $msg;
00589 }
00590
00591 public static function makeHelpMsgHeader($module, $paramName) {
00592 $modulePrefix = $module->getModulePrefix();
00593 if (strval($modulePrefix) !== '')
00594 $modulePrefix = "($modulePrefix) ";
00595
00596 return "* $paramName={$module->getModuleName()} $modulePrefix*";
00597 }
00598
00599 private $mIsBot = null;
00600 private $mIsSysop = null;
00601 private $mCanApiHighLimits = null;
00602
00607 public function isBot() {
00608 if (!isset ($this->mIsBot)) {
00609 global $wgUser;
00610 $this->mIsBot = $wgUser->isAllowed('bot');
00611 }
00612 return $this->mIsBot;
00613 }
00614
00620 public function isSysop() {
00621 if (!isset ($this->mIsSysop)) {
00622 global $wgUser;
00623 $this->mIsSysop = in_array( 'sysop', $wgUser->getGroups());
00624 }
00625
00626 return $this->mIsSysop;
00627 }
00628
00633 public function canApiHighLimits() {
00634 if (!isset($this->mCanApiHighLimits)) {
00635 global $wgUser;
00636 $this->mCanApiHighLimits = $wgUser->isAllowed('apihighlimits');
00637 }
00638
00639 return $this->mCanApiHighLimits;
00640 }
00641
00646 public function getShowVersions() {
00647 return $this->mShowVersions;
00648 }
00649
00654 public function getVersion() {
00655 $vers = array ();
00656 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
00657 $vers[] = __CLASS__ . ': $Id: ApiMain.php 50834 2009-05-20 20:10:47Z catrope $';
00658 $vers[] = ApiBase :: getBaseVersion();
00659 $vers[] = ApiFormatBase :: getBaseVersion();
00660 $vers[] = ApiQueryBase :: getBaseVersion();
00661 $vers[] = ApiFormatFeedWrapper :: getVersion();
00662 return $vers;
00663 }
00664
00674 protected function addModule( $mdlName, $mdlClass ) {
00675 $this->mModules[$mdlName] = $mdlClass;
00676 }
00677
00686 protected function addFormat( $fmtName, $fmtClass ) {
00687 $this->mFormats[$fmtName] = $fmtClass;
00688 }
00689
00693 function getModules() {
00694 return $this->mModules;
00695 }
00696 }
00697
00704 class UsageException extends Exception {
00705
00706 private $mCodestr;
00707
00708 public function __construct($message, $codestr, $code = 0) {
00709 parent :: __construct($message, $code);
00710 $this->mCodestr = $codestr;
00711 }
00712 public function getCodeString() {
00713 return $this->mCodestr;
00714 }
00715 public function __toString() {
00716 return "{$this->getCodeString()}: {$this->getMessage()}";
00717 }
00718 }