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;
00125 private $mCacheMode = 'private';
00126 private $mCacheControl = array();
00127
00134 public function __construct($request, $enableWrite = false) {
00135
00136 $this->mInternalMode = ($request instanceof FauxRequest);
00137
00138
00139 parent :: __construct($this, $this->mInternalMode ? 'main_int' : 'main');
00140
00141 if (!$this->mInternalMode) {
00142
00143
00144
00145
00146 global $wgUser;
00147
00148 if( $request->getVal( 'callback' ) !== null ) {
00149
00150
00151 wfDebug( "API: stripping user credentials for JSON callback\n" );
00152 $wgUser = new User();
00153 }
00154 }
00155
00156 global $wgAPIModules;
00157 $this->mModules = $wgAPIModules + self :: $Modules;
00158
00159 $this->mModuleNames = array_keys($this->mModules);
00160 $this->mFormats = self :: $Formats;
00161 $this->mFormatNames = array_keys($this->mFormats);
00162
00163 $this->mResult = new ApiResult($this);
00164 $this->mShowVersions = false;
00165 $this->mEnableWrite = $enableWrite;
00166
00167 $this->mRequest = & $request;
00168
00169 $this->mCommit = false;
00170 }
00171
00175 public function isInternalMode() {
00176 return $this->mInternalMode;
00177 }
00178
00182 public function getRequest() {
00183 return $this->mRequest;
00184 }
00185
00189 public function getResult() {
00190 return $this->mResult;
00191 }
00192
00197 public function requestWriteMode() {
00198 if (!$this->mEnableWrite)
00199 $this->dieUsageMsg(array('writedisabled'));
00200 if (wfReadOnly())
00201 $this->dieUsageMsg(array('readonlytext'));
00202 }
00203
00207 public function setCacheMaxAge($maxage) {
00208 $this->setCacheControl( array(
00209 'max-age' => $maxage,
00210 's-maxage' => $maxage
00211 ) );
00212 }
00213
00239 public function setCacheMode( $mode ) {
00240 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
00241 wfDebug( __METHOD__.": unrecognised cache mode \"$mode\"\n" );
00242
00243 return;
00244 }
00245
00246 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
00247
00248 if ( $mode !== 'private' ) {
00249 wfDebug( __METHOD__.": ignoring request for $mode cache mode, private wiki\n" );
00250 return;
00251 }
00252 }
00253
00254 wfDebug( __METHOD__.": setting cache mode $mode\n" );
00255 $this->mCacheMode = $mode;
00256 }
00257
00263 public function setCachePrivate() {
00264 $this->setCacheMode( 'private' );
00265 }
00266
00275 public function setCacheControl( $directives ) {
00276 $this->mCacheControl = $directives + $this->mCacheControl;
00277 }
00278
00289 public function setVaryCookie() {
00290 $this->setCacheMode( 'anon-public-user-private' );
00291 }
00292
00296 public function createPrinterByName($format) {
00297 if( !isset( $this->mFormats[$format] ) )
00298 $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
00299 return new $this->mFormats[$format] ($this, $format);
00300 }
00301
00305 public function execute() {
00306 $this->profileIn();
00307 if ($this->mInternalMode)
00308 $this->executeAction();
00309 else
00310 $this->executeActionWithErrorHandling();
00311
00312 $this->profileOut();
00313 }
00314
00319 protected function executeActionWithErrorHandling() {
00320
00321
00322
00323 ob_start();
00324
00325 try {
00326 $this->executeAction();
00327 } catch (Exception $e) {
00328
00329 if ( $e instanceof MWException ) {
00330 wfDebugLog( 'exception', $e->getLogMessage() );
00331 }
00332
00333
00334
00335
00336
00337
00338
00339 $errCode = $this->substituteResultWithError($e);
00340
00341
00342 $this->setCacheMode( 'private' );
00343
00344 $headerStr = 'MediaWiki-API-Error: ' . $errCode;
00345 if ($e->getCode() === 0)
00346 header($headerStr);
00347 else
00348 header($headerStr, true, $e->getCode());
00349
00350
00351 ob_clean();
00352
00353
00354 $this->mPrinter->safeProfileOut();
00355 $this->printResult(true);
00356 }
00357
00358
00359
00360 $this->sendCacheHeaders();
00361
00362 if($this->mPrinter->getIsHtml())
00363 echo wfReportTime();
00364
00365 ob_end_flush();
00366 }
00367
00368 protected function sendCacheHeaders() {
00369 if ( $this->mCacheMode == 'private' ) {
00370 header( 'Cache-Control: private' );
00371 return;
00372 }
00373
00374 if ( $this->mCacheMode == 'anon-public-user-private' ) {
00375 global $wgOut;
00376 header( 'Vary: Accept-Encoding, Cookie' );
00377 header( $wgOut->getXVO() );
00378 if ( session_id() != '' || $wgOut->haveCacheVaryCookies() ) {
00379
00380 header( 'Cache-Control: private' );
00381 return;
00382 }
00383
00384 } else {
00385
00386 global $wgUser;
00387 if ( !( $wgUser instanceof StubUser ) ) {
00388 wfDebug( __METHOD__." \$wgUser is unstubbed on a public request!\n" );
00389 }
00390 }
00391
00392
00393 if ( !isset( $this->mCacheControl['s-maxage'] ) ) {
00394 $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' );
00395 }
00396 if ( !isset( $this->mCacheControl['max-age'] ) ) {
00397 $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' );
00398 }
00399
00400 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) {
00401
00402
00403
00404 header( 'Cache-Control: private' );
00405 return;
00406 }
00407
00408 $this->mCacheControl['public'] = true;
00409
00410
00411 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
00412 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
00413 header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
00414
00415
00416 $ccHeader = '';
00417 $separator = '';
00418 foreach ( $this->mCacheControl as $name => $value ) {
00419 if ( is_bool( $value ) ) {
00420 if ( $value ) {
00421 $ccHeader .= $separator . $name;
00422 $separator = ', ';
00423 }
00424 } else {
00425 $ccHeader .= $separator . "$name=$value";
00426 $separator = ', ';
00427 }
00428 }
00429
00430 header( "Cache-Control: $ccHeader" );
00431 }
00432
00437 protected function substituteResultWithError($e) {
00438
00439
00440 if (!isset ($this->mPrinter)) {
00441
00442 $value = $this->getRequest()->getVal('format', self::API_DEFAULT_FORMAT);
00443 if (!in_array($value, $this->mFormatNames))
00444 $value = self::API_DEFAULT_FORMAT;
00445
00446 $this->mPrinter = $this->createPrinterByName($value);
00447 if ($this->mPrinter->getNeedsRawData())
00448 $this->getResult()->setRawMode();
00449 }
00450
00451 if ($e instanceof UsageException) {
00452
00453
00454
00455 $errMessage = array (
00456 'code' => $e->getCodeString(),
00457 'info' => $e->getMessage());
00458
00459
00460 if ($this->mPrinter->getIsHtml() || $this->mAction == 'help')
00461 ApiResult :: setContent($errMessage, $this->makeHelpMsg());
00462
00463 } else {
00464 global $wgShowSQLErrors, $wgShowExceptionDetails;
00465
00466
00467
00468 if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
00469 $info = "Database query error";
00470 } else {
00471 $info = "Exception Caught: {$e->getMessage()}";
00472 }
00473
00474 $errMessage = array (
00475 'code' => 'internal_api_error_'. get_class($e),
00476 'info' => $info,
00477 );
00478 ApiResult :: setContent($errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : "" );
00479 }
00480
00481 $this->getResult()->reset();
00482 $this->getResult()->disableSizeCheck();
00483
00484 $requestid = $this->getParameter('requestid');
00485 if(!is_null($requestid))
00486 $this->getResult()->addValue(null, 'requestid', $requestid);
00487 $this->getResult()->addValue(null, 'error', $errMessage);
00488
00489 return $errMessage['code'];
00490 }
00491
00495 protected function executeAction() {
00496
00497 $requestid = $this->getParameter('requestid');
00498 if(!is_null($requestid))
00499 $this->getResult()->addValue(null, 'requestid', $requestid);
00500
00501 $params = $this->extractRequestParams();
00502
00503 $this->mShowVersions = $params['version'];
00504 $this->mAction = $params['action'];
00505
00506 if( !is_string( $this->mAction ) ) {
00507 $this->dieUsage( "The API requires a valid action parameter", 'unknown_action' );
00508 }
00509
00510
00511 $module = new $this->mModules[$this->mAction] ($this, $this->mAction);
00512
00513 if( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
00514
00515 global $wgShowHostnames;
00516 $maxLag = $params['maxlag'];
00517 list( $host, $lag ) = wfGetLB()->getMaxLag();
00518 if ( $lag > $maxLag ) {
00519 header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
00520 header( 'X-Database-Lag: ' . intval( $lag ) );
00521
00522 if( $wgShowHostnames ) {
00523 $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
00524 } else {
00525 $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
00526 }
00527 return;
00528 }
00529 }
00530
00531 global $wgUser;
00532 if ($module->isReadMode() && !$wgUser->isAllowed('read'))
00533 $this->dieUsageMsg(array('readrequired'));
00534 if ($module->isWriteMode()) {
00535 if (!$this->mEnableWrite)
00536 $this->dieUsageMsg(array('writedisabled'));
00537 if (!$wgUser->isAllowed('writeapi'))
00538 $this->dieUsageMsg(array('writerequired'));
00539 if (wfReadOnly())
00540 $this->dieUsageMsg(array('readonlytext'));
00541 }
00542
00543 if (!$this->mInternalMode) {
00544
00545 if($module->mustBePosted() && !$this->mRequest->wasPosted())
00546 $this->dieUsage("The {$this->mAction} module requires a POST request", 'mustbeposted');
00547
00548
00549 $this->mPrinter = $module->getCustomPrinter();
00550 if (is_null($this->mPrinter)) {
00551
00552 $this->mPrinter = $this->createPrinterByName($params['format']);
00553 }
00554
00555 if ($this->mPrinter->getNeedsRawData())
00556 $this->getResult()->setRawMode();
00557 }
00558
00559
00560 $module->profileIn();
00561 $module->execute();
00562 wfRunHooks('APIAfterExecute', array(&$module));
00563 $module->profileOut();
00564
00565 if (!$this->mInternalMode) {
00566
00567 $this->printResult(false);
00568 }
00569 }
00570
00574 protected function printResult($isError) {
00575 $this->getResult()->cleanUpUTF8();
00576 $printer = $this->mPrinter;
00577 $printer->profileIn();
00578
00579
00580
00581
00582 $printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError )
00583 && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
00584
00585 $printer->initPrinter($isError);
00586
00587 $printer->execute();
00588 $printer->closePrinter();
00589 $printer->profileOut();
00590 }
00591
00592 public function isReadMode() {
00593 return false;
00594 }
00595
00599 public function getAllowedParams() {
00600 return array (
00601 'format' => array (
00602 ApiBase :: PARAM_DFLT => ApiMain :: API_DEFAULT_FORMAT,
00603 ApiBase :: PARAM_TYPE => $this->mFormatNames
00604 ),
00605 'action' => array (
00606 ApiBase :: PARAM_DFLT => 'help',
00607 ApiBase :: PARAM_TYPE => $this->mModuleNames
00608 ),
00609 'version' => false,
00610 'maxlag' => array (
00611 ApiBase :: PARAM_TYPE => 'integer'
00612 ),
00613 'smaxage' => array (
00614 ApiBase :: PARAM_TYPE => 'integer',
00615 ApiBase :: PARAM_DFLT => 0
00616 ),
00617 'maxage' => array (
00618 ApiBase :: PARAM_TYPE => 'integer',
00619 ApiBase :: PARAM_DFLT => 0
00620 ),
00621 'requestid' => null,
00622 );
00623 }
00624
00628 public function getParamDescription() {
00629 return array (
00630 'format' => 'The format of the output',
00631 'action' => 'What action you would like to perform',
00632 'version' => 'When showing help, include version for each module',
00633 'maxlag' => 'Maximum lag',
00634 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
00635 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
00636 'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
00637 );
00638 }
00639
00643 public function getDescription() {
00644 return array (
00645 '',
00646 '',
00647 '******************************************************************',
00648 '** **',
00649 '** This is an auto-generated MediaWiki API documentation page **',
00650 '** **',
00651 '** Documentation and Examples: **',
00652 '** http://www.mediawiki.org/wiki/API **',
00653 '** **',
00654 '******************************************************************',
00655 '',
00656 'Status: All features shown on this page should be working, but the API',
00657 ' is still in active development, and may change at any time.',
00658 ' Make sure to monitor our mailing list for any updates.',
00659 '',
00660 'Documentation: http://www.mediawiki.org/wiki/API',
00661 'Mailing list: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
00662 'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
00663 '',
00664 '',
00665 '',
00666 '',
00667 '',
00668 );
00669 }
00670
00674 protected function getCredits() {
00675 return array(
00676 'API developers:',
00677 ' Roan Kattouw <Firstname>.<Lastname>@home.nl (lead developer Sep 2007-present)',
00678 ' Victor Vasiliev - vasilvv at gee mail dot com',
00679 ' Bryan Tong Minh - bryan . tongminh @ gmail . com',
00680 ' Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)',
00681 '',
00682 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
00683 'or file a bug report at http://bugzilla.wikimedia.org/'
00684 );
00685 }
00686
00690 public function makeHelpMsg() {
00691
00692 $this->mPrinter->setHelp();
00693
00694
00695 $msg = parent :: makeHelpMsg();
00696
00697 $astriks = str_repeat('*** ', 10);
00698 $msg .= "\n\n$astriks Modules $astriks\n\n";
00699 foreach( $this->mModules as $moduleName => $unused ) {
00700 $module = new $this->mModules[$moduleName] ($this, $moduleName);
00701 $msg .= self::makeHelpMsgHeader($module, 'action');
00702 $msg2 = $module->makeHelpMsg();
00703 if ($msg2 !== false)
00704 $msg .= $msg2;
00705 $msg .= "\n";
00706 }
00707
00708 $msg .= "\n$astriks Permissions $astriks\n\n";
00709 foreach ( self :: $mRights as $right => $rightMsg ) {
00710 $groups = User::getGroupsWithPermission( $right );
00711 $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
00712 "\nGranted to:\n " . str_replace( "*", "all", implode( ", ", $groups ) ) . "\n";
00713
00714 }
00715
00716 $msg .= "\n$astriks Formats $astriks\n\n";
00717 foreach( $this->mFormats as $formatName => $unused ) {
00718 $module = $this->createPrinterByName($formatName);
00719 $msg .= self::makeHelpMsgHeader($module, 'format');
00720 $msg2 = $module->makeHelpMsg();
00721 if ($msg2 !== false)
00722 $msg .= $msg2;
00723 $msg .= "\n";
00724 }
00725
00726 $msg .= "\n*** Credits: ***\n " . implode("\n ", $this->getCredits()) . "\n";
00727
00728
00729 return $msg;
00730 }
00731
00732 public static function makeHelpMsgHeader($module, $paramName) {
00733 $modulePrefix = $module->getModulePrefix();
00734 if (strval($modulePrefix) !== '')
00735 $modulePrefix = "($modulePrefix) ";
00736
00737 return "* $paramName={$module->getModuleName()} $modulePrefix*";
00738 }
00739
00740 private $mIsBot = null;
00741 private $mIsSysop = null;
00742 private $mCanApiHighLimits = null;
00743
00748 public function isBot() {
00749 if (!isset ($this->mIsBot)) {
00750 global $wgUser;
00751 $this->mIsBot = $wgUser->isAllowed('bot');
00752 }
00753 return $this->mIsBot;
00754 }
00755
00761 public function isSysop() {
00762 if (!isset ($this->mIsSysop)) {
00763 global $wgUser;
00764 $this->mIsSysop = in_array( 'sysop', $wgUser->getGroups());
00765 }
00766
00767 return $this->mIsSysop;
00768 }
00769
00774 public function canApiHighLimits() {
00775 if (!isset($this->mCanApiHighLimits)) {
00776 global $wgUser;
00777 $this->mCanApiHighLimits = $wgUser->isAllowed('apihighlimits');
00778 }
00779
00780 return $this->mCanApiHighLimits;
00781 }
00782
00787 public function getShowVersions() {
00788 return $this->mShowVersions;
00789 }
00790
00795 public function getVersion() {
00796 $vers = array ();
00797 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
00798 $vers[] = __CLASS__ . ': $Id: ApiMain.php 69990 2010-07-27 08:44:08Z tstarling $';
00799 $vers[] = ApiBase :: getBaseVersion();
00800 $vers[] = ApiFormatBase :: getBaseVersion();
00801 $vers[] = ApiQueryBase :: getBaseVersion();
00802 $vers[] = ApiFormatFeedWrapper :: getVersion();
00803 return $vers;
00804 }
00805
00815 protected function addModule( $mdlName, $mdlClass ) {
00816 $this->mModules[$mdlName] = $mdlClass;
00817 }
00818
00827 protected function addFormat( $fmtName, $fmtClass ) {
00828 $this->mFormats[$fmtName] = $fmtClass;
00829 }
00830
00834 function getModules() {
00835 return $this->mModules;
00836 }
00837 }
00838
00845 class UsageException extends Exception {
00846
00847 private $mCodestr;
00848
00849 public function __construct($message, $codestr, $code = 0) {
00850 parent :: __construct($message, $code);
00851 $this->mCodestr = $codestr;
00852 }
00853 public function getCodeString() {
00854 return $this->mCodestr;
00855 }
00856 public function __toString() {
00857 return "{$this->getCodeString()}: {$this->getMessage()}";
00858 }
00859 }