MediaWiki
REL1_20
|
00001 <?php 00041 class ApiMain extends ApiBase { 00042 00046 const API_DEFAULT_FORMAT = 'xmlfm'; 00047 00051 private static $Modules = array( 00052 'login' => 'ApiLogin', 00053 'logout' => 'ApiLogout', 00054 'query' => 'ApiQuery', 00055 'expandtemplates' => 'ApiExpandTemplates', 00056 'parse' => 'ApiParse', 00057 'opensearch' => 'ApiOpenSearch', 00058 'feedcontributions' => 'ApiFeedContributions', 00059 'feedwatchlist' => 'ApiFeedWatchlist', 00060 'help' => 'ApiHelp', 00061 'paraminfo' => 'ApiParamInfo', 00062 'rsd' => 'ApiRsd', 00063 'compare' => 'ApiComparePages', 00064 'tokens' => 'ApiTokens', 00065 00066 // Write modules 00067 'purge' => 'ApiPurge', 00068 'setnotificationtimestamp' => 'ApiSetNotificationTimestamp', 00069 'rollback' => 'ApiRollback', 00070 'delete' => 'ApiDelete', 00071 'undelete' => 'ApiUndelete', 00072 'protect' => 'ApiProtect', 00073 'block' => 'ApiBlock', 00074 'unblock' => 'ApiUnblock', 00075 'move' => 'ApiMove', 00076 'edit' => 'ApiEditPage', 00077 'upload' => 'ApiUpload', 00078 'filerevert' => 'ApiFileRevert', 00079 'emailuser' => 'ApiEmailUser', 00080 'watch' => 'ApiWatch', 00081 'patrol' => 'ApiPatrol', 00082 'import' => 'ApiImport', 00083 'userrights' => 'ApiUserrights', 00084 'options' => 'ApiOptions', 00085 ); 00086 00090 private static $Formats = array( 00091 'json' => 'ApiFormatJson', 00092 'jsonfm' => 'ApiFormatJson', 00093 'php' => 'ApiFormatPhp', 00094 'phpfm' => 'ApiFormatPhp', 00095 'wddx' => 'ApiFormatWddx', 00096 'wddxfm' => 'ApiFormatWddx', 00097 'xml' => 'ApiFormatXml', 00098 'xmlfm' => 'ApiFormatXml', 00099 'yaml' => 'ApiFormatYaml', 00100 'yamlfm' => 'ApiFormatYaml', 00101 'rawfm' => 'ApiFormatJson', 00102 'txt' => 'ApiFormatTxt', 00103 'txtfm' => 'ApiFormatTxt', 00104 'dbg' => 'ApiFormatDbg', 00105 'dbgfm' => 'ApiFormatDbg', 00106 'dump' => 'ApiFormatDump', 00107 'dumpfm' => 'ApiFormatDump', 00108 ); 00109 00116 private static $mRights = array( 00117 'writeapi' => array( 00118 'msg' => 'Use of the write API', 00119 'params' => array() 00120 ), 00121 'apihighlimits' => array( 00122 '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.', 00123 'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 ) 00124 ) 00125 ); 00126 00130 private $mPrinter; 00131 00132 private $mModules, $mModuleNames, $mFormats, $mFormatNames; 00133 private $mResult, $mAction, $mShowVersions, $mEnableWrite; 00134 private $mInternalMode, $mSquidMaxage, $mModule; 00135 00136 private $mCacheMode = 'private'; 00137 private $mCacheControl = array(); 00138 00145 public function __construct( $context = null, $enableWrite = false ) { 00146 if ( $context === null ) { 00147 $context = RequestContext::getMain(); 00148 } elseif ( $context instanceof WebRequest ) { 00149 // BC for pre-1.19 00150 $request = $context; 00151 $context = RequestContext::getMain(); 00152 } 00153 // We set a derivative context so we can change stuff later 00154 $this->setContext( new DerivativeContext( $context ) ); 00155 00156 if ( isset( $request ) ) { 00157 $this->getContext()->setRequest( $request ); 00158 } 00159 00160 $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest ); 00161 00162 // Special handling for the main module: $parent === $this 00163 parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' ); 00164 00165 if ( !$this->mInternalMode ) { 00166 // Impose module restrictions. 00167 // If the current user cannot read, 00168 // Remove all modules other than login 00169 global $wgUser; 00170 00171 if ( $this->getRequest()->getVal( 'callback' ) !== null ) { 00172 // JSON callback allows cross-site reads. 00173 // For safety, strip user credentials. 00174 wfDebug( "API: stripping user credentials for JSON callback\n" ); 00175 $wgUser = new User(); 00176 $this->getContext()->setUser( $wgUser ); 00177 } 00178 } 00179 00180 global $wgAPIModules; // extension modules 00181 $this->mModules = $wgAPIModules + self::$Modules; 00182 00183 $this->mModuleNames = array_keys( $this->mModules ); 00184 $this->mFormats = self::$Formats; 00185 $this->mFormatNames = array_keys( $this->mFormats ); 00186 00187 $this->mResult = new ApiResult( $this ); 00188 $this->mShowVersions = false; 00189 $this->mEnableWrite = $enableWrite; 00190 00191 $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling() 00192 $this->mCommit = false; 00193 } 00194 00199 public function isInternalMode() { 00200 return $this->mInternalMode; 00201 } 00202 00208 public function getResult() { 00209 return $this->mResult; 00210 } 00211 00217 public function getModule() { 00218 return $this->mModule; 00219 } 00220 00226 public function getPrinter() { 00227 return $this->mPrinter; 00228 } 00229 00235 public function setCacheMaxAge( $maxage ) { 00236 $this->setCacheControl( array( 00237 'max-age' => $maxage, 00238 's-maxage' => $maxage 00239 ) ); 00240 } 00241 00267 public function setCacheMode( $mode ) { 00268 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) { 00269 wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" ); 00270 // Ignore for forwards-compatibility 00271 return; 00272 } 00273 00274 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) { 00275 // Private wiki, only private headers 00276 if ( $mode !== 'private' ) { 00277 wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" ); 00278 return; 00279 } 00280 } 00281 00282 wfDebug( __METHOD__ . ": setting cache mode $mode\n" ); 00283 $this->mCacheMode = $mode; 00284 } 00285 00291 public function setCachePrivate() { 00292 wfDeprecated( __METHOD__, '1.17' ); 00293 $this->setCacheMode( 'private' ); 00294 } 00295 00306 public function setCacheControl( $directives ) { 00307 $this->mCacheControl = $directives + $this->mCacheControl; 00308 } 00309 00320 public function setVaryCookie() { 00321 wfDeprecated( __METHOD__, '1.17' ); 00322 $this->setCacheMode( 'anon-public-user-private' ); 00323 } 00324 00332 public function createPrinterByName( $format ) { 00333 if ( !isset( $this->mFormats[$format] ) ) { 00334 $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' ); 00335 } 00336 return new $this->mFormats[$format] ( $this, $format ); 00337 } 00338 00342 public function execute() { 00343 $this->profileIn(); 00344 if ( $this->mInternalMode ) { 00345 $this->executeAction(); 00346 } else { 00347 $this->executeActionWithErrorHandling(); 00348 } 00349 00350 $this->profileOut(); 00351 } 00352 00357 protected function executeActionWithErrorHandling() { 00358 // Verify the CORS header before executing the action 00359 if ( !$this->handleCORS() ) { 00360 // handleCORS() has sent a 403, abort 00361 return; 00362 } 00363 00364 // In case an error occurs during data output, 00365 // clear the output buffer and print just the error information 00366 ob_start(); 00367 00368 try { 00369 $this->executeAction(); 00370 } catch ( Exception $e ) { 00371 // Allow extra cleanup and logging 00372 wfRunHooks( 'ApiMain::onException', array( $this, $e ) ); 00373 00374 // Log it 00375 if ( !( $e instanceof UsageException ) ) { 00376 wfDebugLog( 'exception', $e->getLogMessage() ); 00377 } 00378 00379 // Handle any kind of exception by outputing properly formatted error message. 00380 // If this fails, an unhandled exception should be thrown so that global error 00381 // handler will process and log it. 00382 00383 $errCode = $this->substituteResultWithError( $e ); 00384 00385 // Error results should not be cached 00386 $this->setCacheMode( 'private' ); 00387 00388 $response = $this->getRequest()->response(); 00389 $headerStr = 'MediaWiki-API-Error: ' . $errCode; 00390 if ( $e->getCode() === 0 ) { 00391 $response->header( $headerStr ); 00392 } else { 00393 $response->header( $headerStr, true, $e->getCode() ); 00394 } 00395 00396 // Reset and print just the error message 00397 ob_clean(); 00398 00399 // If the error occurred during printing, do a printer->profileOut() 00400 $this->mPrinter->safeProfileOut(); 00401 $this->printResult( true ); 00402 } 00403 00404 // Send cache headers after any code which might generate an error, to 00405 // avoid sending public cache headers for errors. 00406 $this->sendCacheHeaders(); 00407 00408 if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) { 00409 echo wfReportTime(); 00410 } 00411 00412 ob_end_flush(); 00413 } 00414 00427 protected function handleCORS() { 00428 global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions; 00429 00430 $originParam = $this->getParameter( 'origin' ); // defaults to null 00431 if ( $originParam === null ) { 00432 // No origin parameter, nothing to do 00433 return true; 00434 } 00435 00436 $request = $this->getRequest(); 00437 $response = $request->response(); 00438 // Origin: header is a space-separated list of origins, check all of them 00439 $originHeader = $request->getHeader( 'Origin' ); 00440 if ( $originHeader === false ) { 00441 $origins = array(); 00442 } else { 00443 $origins = explode( ' ', $originHeader ); 00444 } 00445 if ( !in_array( $originParam, $origins ) ) { 00446 // origin parameter set but incorrect 00447 // Send a 403 response 00448 $message = HttpStatus::getMessage( 403 ); 00449 $response->header( "HTTP/1.1 403 $message", true, 403 ); 00450 $response->header( 'Cache-Control: no-cache' ); 00451 echo "'origin' parameter does not match Origin header\n"; 00452 return false; 00453 } 00454 if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) { 00455 $response->header( "Access-Control-Allow-Origin: $originParam" ); 00456 $response->header( 'Access-Control-Allow-Credentials: true' ); 00457 $this->getOutput()->addVaryHeader( 'Origin' ); 00458 } 00459 return true; 00460 } 00461 00469 protected static function matchOrigin( $value, $rules, $exceptions ) { 00470 foreach ( $rules as $rule ) { 00471 if ( preg_match( self::wildcardToRegex( $rule ), $value ) ) { 00472 // Rule matches, check exceptions 00473 foreach ( $exceptions as $exc ) { 00474 if ( preg_match( self::wildcardToRegex( $exc ), $value ) ) { 00475 return false; 00476 } 00477 } 00478 return true; 00479 } 00480 } 00481 return false; 00482 } 00483 00492 protected static function wildcardToRegex( $wildcard ) { 00493 $wildcard = preg_quote( $wildcard, '/' ); 00494 $wildcard = str_replace( 00495 array( '\*', '\?' ), 00496 array( '.*?', '.' ), 00497 $wildcard 00498 ); 00499 return "/https?:\/\/$wildcard/"; 00500 } 00501 00502 protected function sendCacheHeaders() { 00503 global $wgUseXVO, $wgVaryOnXFP; 00504 $response = $this->getRequest()->response(); 00505 $out = $this->getOutput(); 00506 00507 if ( $wgVaryOnXFP ) { 00508 $out->addVaryHeader( 'X-Forwarded-Proto' ); 00509 } 00510 00511 if ( $this->mCacheMode == 'private' ) { 00512 $response->header( 'Cache-Control: private' ); 00513 return; 00514 } 00515 00516 if ( $this->mCacheMode == 'anon-public-user-private' ) { 00517 $out->addVaryHeader( 'Cookie' ); 00518 $response->header( $out->getVaryHeader() ); 00519 if ( $wgUseXVO ) { 00520 $response->header( $out->getXVO() ); 00521 if ( $out->haveCacheVaryCookies() ) { 00522 // Logged in, mark this request private 00523 $response->header( 'Cache-Control: private' ); 00524 return; 00525 } 00526 // Logged out, send normal public headers below 00527 } elseif ( session_id() != '' ) { 00528 // Logged in or otherwise has session (e.g. anonymous users who have edited) 00529 // Mark request private 00530 $response->header( 'Cache-Control: private' ); 00531 return; 00532 } // else no XVO and anonymous, send public headers below 00533 } 00534 00535 // Send public headers 00536 $response->header( $out->getVaryHeader() ); 00537 if ( $wgUseXVO ) { 00538 $response->header( $out->getXVO() ); 00539 } 00540 00541 // If nobody called setCacheMaxAge(), use the (s)maxage parameters 00542 if ( !isset( $this->mCacheControl['s-maxage'] ) ) { 00543 $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' ); 00544 } 00545 if ( !isset( $this->mCacheControl['max-age'] ) ) { 00546 $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' ); 00547 } 00548 00549 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) { 00550 // Public cache not requested 00551 // Sending a Vary header in this case is harmless, and protects us 00552 // against conditional calls of setCacheMaxAge(). 00553 $response->header( 'Cache-Control: private' ); 00554 return; 00555 } 00556 00557 $this->mCacheControl['public'] = true; 00558 00559 // Send an Expires header 00560 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] ); 00561 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge ); 00562 $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) ); 00563 00564 // Construct the Cache-Control header 00565 $ccHeader = ''; 00566 $separator = ''; 00567 foreach ( $this->mCacheControl as $name => $value ) { 00568 if ( is_bool( $value ) ) { 00569 if ( $value ) { 00570 $ccHeader .= $separator . $name; 00571 $separator = ', '; 00572 } 00573 } else { 00574 $ccHeader .= $separator . "$name=$value"; 00575 $separator = ', '; 00576 } 00577 } 00578 00579 $response->header( "Cache-Control: $ccHeader" ); 00580 } 00581 00588 protected function substituteResultWithError( $e ) { 00589 global $wgShowHostnames; 00590 00591 $result = $this->getResult(); 00592 // Printer may not be initialized if the extractRequestParams() fails for the main module 00593 if ( !isset ( $this->mPrinter ) ) { 00594 // The printer has not been created yet. Try to manually get formatter value. 00595 $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); 00596 if ( !in_array( $value, $this->mFormatNames ) ) { 00597 $value = self::API_DEFAULT_FORMAT; 00598 } 00599 00600 $this->mPrinter = $this->createPrinterByName( $value ); 00601 if ( $this->mPrinter->getNeedsRawData() ) { 00602 $result->setRawMode(); 00603 } 00604 } 00605 00606 if ( $e instanceof UsageException ) { 00607 // User entered incorrect parameters - print usage screen 00608 $errMessage = $e->getMessageArray(); 00609 00610 // Only print the help message when this is for the developer, not runtime 00611 if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) { 00612 ApiResult::setContent( $errMessage, $this->makeHelpMsg() ); 00613 } 00614 00615 } else { 00616 global $wgShowSQLErrors, $wgShowExceptionDetails; 00617 // Something is seriously wrong 00618 if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) { 00619 $info = 'Database query error'; 00620 } else { 00621 $info = "Exception Caught: {$e->getMessage()}"; 00622 } 00623 00624 $errMessage = array( 00625 'code' => 'internal_api_error_' . get_class( $e ), 00626 'info' => $info, 00627 ); 00628 ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' ); 00629 } 00630 00631 $result->reset(); 00632 $result->disableSizeCheck(); 00633 // Re-add the id 00634 $requestid = $this->getParameter( 'requestid' ); 00635 if ( !is_null( $requestid ) ) { 00636 $result->addValue( null, 'requestid', $requestid ); 00637 } 00638 00639 if ( $wgShowHostnames ) { 00640 // servedby is especially useful when debugging errors 00641 $result->addValue( null, 'servedby', wfHostName() ); 00642 } 00643 00644 $result->addValue( null, 'error', $errMessage ); 00645 00646 return $errMessage['code']; 00647 } 00648 00653 protected function setupExecuteAction() { 00654 global $wgShowHostnames; 00655 00656 // First add the id to the top element 00657 $result = $this->getResult(); 00658 $requestid = $this->getParameter( 'requestid' ); 00659 if ( !is_null( $requestid ) ) { 00660 $result->addValue( null, 'requestid', $requestid ); 00661 } 00662 00663 if ( $wgShowHostnames ) { 00664 $servedby = $this->getParameter( 'servedby' ); 00665 if ( $servedby ) { 00666 $result->addValue( null, 'servedby', wfHostName() ); 00667 } 00668 } 00669 00670 $params = $this->extractRequestParams(); 00671 00672 $this->mShowVersions = $params['version']; 00673 $this->mAction = $params['action']; 00674 00675 if ( !is_string( $this->mAction ) ) { 00676 $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); 00677 } 00678 00679 return $params; 00680 } 00681 00686 protected function setupModule() { 00687 // Instantiate the module requested by the user 00688 $module = new $this->mModules[$this->mAction] ( $this, $this->mAction ); 00689 $this->mModule = $module; 00690 00691 $moduleParams = $module->extractRequestParams(); 00692 00693 // Die if token required, but not provided (unless there is a gettoken parameter) 00694 if ( isset( $moduleParams['gettoken'] ) ) { 00695 $gettoken = $moduleParams['gettoken']; 00696 } else { 00697 $gettoken = false; 00698 } 00699 00700 $salt = $module->getTokenSalt(); 00701 if ( $salt !== false && !$gettoken ) { 00702 if ( !isset( $moduleParams['token'] ) ) { 00703 $this->dieUsageMsg( array( 'missingparam', 'token' ) ); 00704 } else { 00705 if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) { 00706 $this->dieUsageMsg( 'sessionfailure' ); 00707 } 00708 } 00709 } 00710 return $module; 00711 } 00712 00719 protected function checkMaxLag( $module, $params ) { 00720 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) { 00721 // Check for maxlag 00722 global $wgShowHostnames; 00723 $maxLag = $params['maxlag']; 00724 list( $host, $lag ) = wfGetLB()->getMaxLag(); 00725 if ( $lag > $maxLag ) { 00726 $response = $this->getRequest()->response(); 00727 00728 $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); 00729 $response->header( 'X-Database-Lag: ' . intval( $lag ) ); 00730 00731 if ( $wgShowHostnames ) { 00732 $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); 00733 } else { 00734 $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); 00735 } 00736 return false; 00737 } 00738 } 00739 return true; 00740 } 00741 00746 protected function checkExecutePermissions( $module ) { 00747 $user = $this->getUser(); 00748 if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) && 00749 !$user->isAllowed( 'read' ) ) 00750 { 00751 $this->dieUsageMsg( 'readrequired' ); 00752 } 00753 if ( $module->isWriteMode() ) { 00754 if ( !$this->mEnableWrite ) { 00755 $this->dieUsageMsg( 'writedisabled' ); 00756 } 00757 if ( !$user->isAllowed( 'writeapi' ) ) { 00758 $this->dieUsageMsg( 'writerequired' ); 00759 } 00760 if ( wfReadOnly() ) { 00761 $this->dieReadOnly(); 00762 } 00763 } 00764 00765 // Allow extensions to stop execution for arbitrary reasons. 00766 $message = false; 00767 if( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { 00768 $this->dieUsageMsg( $message ); 00769 } 00770 } 00771 00777 protected function setupExternalResponse( $module, $params ) { 00778 // Ignore mustBePosted() for internal calls 00779 if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) { 00780 $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) ); 00781 } 00782 00783 // See if custom printer is used 00784 $this->mPrinter = $module->getCustomPrinter(); 00785 if ( is_null( $this->mPrinter ) ) { 00786 // Create an appropriate printer 00787 $this->mPrinter = $this->createPrinterByName( $params['format'] ); 00788 } 00789 00790 if ( $this->mPrinter->getNeedsRawData() ) { 00791 $this->getResult()->setRawMode(); 00792 } 00793 } 00794 00798 protected function executeAction() { 00799 $params = $this->setupExecuteAction(); 00800 $module = $this->setupModule(); 00801 00802 $this->checkExecutePermissions( $module ); 00803 00804 if ( !$this->checkMaxLag( $module, $params ) ) { 00805 return; 00806 } 00807 00808 if ( !$this->mInternalMode ) { 00809 $this->setupExternalResponse( $module, $params ); 00810 } 00811 00812 // Execute 00813 $module->profileIn(); 00814 $module->execute(); 00815 wfRunHooks( 'APIAfterExecute', array( &$module ) ); 00816 $module->profileOut(); 00817 00818 if ( !$this->mInternalMode ) { 00819 //append Debug information 00820 MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() ); 00821 00822 // Print result data 00823 $this->printResult( false ); 00824 } 00825 } 00826 00832 protected function printResult( $isError ) { 00833 $this->getResult()->cleanUpUTF8(); 00834 $printer = $this->mPrinter; 00835 $printer->profileIn(); 00836 00842 $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError ) 00843 && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); 00844 00845 $printer->initPrinter( $isError ); 00846 00847 $printer->execute(); 00848 $printer->closePrinter(); 00849 $printer->profileOut(); 00850 } 00851 00855 public function isReadMode() { 00856 return false; 00857 } 00858 00864 public function getAllowedParams() { 00865 return array( 00866 'format' => array( 00867 ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT, 00868 ApiBase::PARAM_TYPE => $this->mFormatNames 00869 ), 00870 'action' => array( 00871 ApiBase::PARAM_DFLT => 'help', 00872 ApiBase::PARAM_TYPE => $this->mModuleNames 00873 ), 00874 'version' => false, 00875 'maxlag' => array( 00876 ApiBase::PARAM_TYPE => 'integer' 00877 ), 00878 'smaxage' => array( 00879 ApiBase::PARAM_TYPE => 'integer', 00880 ApiBase::PARAM_DFLT => 0 00881 ), 00882 'maxage' => array( 00883 ApiBase::PARAM_TYPE => 'integer', 00884 ApiBase::PARAM_DFLT => 0 00885 ), 00886 'requestid' => null, 00887 'servedby' => false, 00888 'origin' => null, 00889 ); 00890 } 00891 00897 public function getParamDescription() { 00898 return array( 00899 'format' => 'The format of the output', 00900 'action' => 'What action you would like to perform. See below for module help', 00901 'version' => 'When showing help, include version for each module', 00902 'maxlag' => array( 00903 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.', 00904 'To save actions causing any more site replication lag, this parameter can make the client', 00905 'wait until the replication lag is less than the specified value.', 00906 'In case of a replag error, a HTTP 503 error is returned, with the message like', 00907 '"Waiting for $host: $lag seconds lagged\n".', 00908 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information', 00909 ), 00910 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached', 00911 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached', 00912 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', 00913 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error', 00914 'origin' => array( 00915 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.', 00916 'This must match one of the origins in the Origin: header exactly, so it has to be set to something like http://en.wikipedia.org or https://meta.wikimedia.org .', 00917 'If this parameter does not match the Origin: header, a 403 response will be returned.', 00918 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.', 00919 ), 00920 ); 00921 } 00922 00928 public function getDescription() { 00929 return array( 00930 '', 00931 '', 00932 '**********************************************************************************************************', 00933 '** **', 00934 '** This is an auto-generated MediaWiki API documentation page **', 00935 '** **', 00936 '** Documentation and Examples: **', 00937 '** https://www.mediawiki.org/wiki/API **', 00938 '** **', 00939 '**********************************************************************************************************', 00940 '', 00941 'Status: All features shown on this page should be working, but the API', 00942 ' is still in active development, and may change at any time.', 00943 ' Make sure to monitor our mailing list for any updates', 00944 '', 00945 'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent', 00946 ' with the key "MediaWiki-API-Error" and then both the value of the', 00947 ' header and the error code sent back will be set to the same value', 00948 '', 00949 ' In the case of an invalid action being passed, these will have a value', 00950 ' of "unknown_action"', 00951 '', 00952 ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings', 00953 '', 00954 'Documentation: https://www.mediawiki.org/wiki/API:Main_page', 00955 'FAQ https://www.mediawiki.org/wiki/API:FAQ', 00956 'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api', 00957 'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce', 00958 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', 00959 '', 00960 '', 00961 '', 00962 '', 00963 '', 00964 ); 00965 } 00966 00970 public function getPossibleErrors() { 00971 return array_merge( parent::getPossibleErrors(), array( 00972 array( 'readonlytext' ), 00973 array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ), 00974 array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ), 00975 array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ), 00976 array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ), 00977 ) ); 00978 } 00979 00984 protected function getCredits() { 00985 return array( 00986 'API developers:', 00987 ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-present)', 00988 ' Victor Vasiliev - vasilvv at gee mail dot com', 00989 ' Bryan Tong Minh - bryan . tongminh @ gmail . com', 00990 ' Sam Reed - sam @ reedyboy . net', 00991 ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007)', 00992 '', 00993 'Please send your comments, suggestions and questions to [email protected]', 00994 'or file a bug report at https://bugzilla.wikimedia.org/' 00995 ); 00996 } 00997 01003 public function setHelp( $help = true ) { 01004 $this->mPrinter->setHelp( $help ); 01005 } 01006 01012 public function makeHelpMsg() { 01013 global $wgMemc, $wgAPICacheHelpTimeout; 01014 $this->setHelp(); 01015 // Get help text from cache if present 01016 $key = wfMemcKey( 'apihelp', $this->getModuleName(), 01017 SpecialVersion::getVersion( 'nodb' ) . 01018 $this->getShowVersions() ); 01019 if ( $wgAPICacheHelpTimeout > 0 ) { 01020 $cached = $wgMemc->get( $key ); 01021 if ( $cached ) { 01022 return $cached; 01023 } 01024 } 01025 $retval = $this->reallyMakeHelpMsg(); 01026 if ( $wgAPICacheHelpTimeout > 0 ) { 01027 $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout ); 01028 } 01029 return $retval; 01030 } 01031 01035 public function reallyMakeHelpMsg() { 01036 $this->setHelp(); 01037 01038 // Use parent to make default message for the main module 01039 $msg = parent::makeHelpMsg(); 01040 01041 $astriks = str_repeat( '*** ', 14 ); 01042 $msg .= "\n\n$astriks Modules $astriks\n\n"; 01043 foreach ( array_keys( $this->mModules ) as $moduleName ) { 01044 $module = new $this->mModules[$moduleName] ( $this, $moduleName ); 01045 $msg .= self::makeHelpMsgHeader( $module, 'action' ); 01046 $msg2 = $module->makeHelpMsg(); 01047 if ( $msg2 !== false ) { 01048 $msg .= $msg2; 01049 } 01050 $msg .= "\n"; 01051 } 01052 01053 $msg .= "\n$astriks Permissions $astriks\n\n"; 01054 foreach ( self::$mRights as $right => $rightMsg ) { 01055 $groups = User::getGroupsWithPermission( $right ); 01056 $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) . 01057 "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n"; 01058 01059 } 01060 01061 $msg .= "\n$astriks Formats $astriks\n\n"; 01062 foreach ( array_keys( $this->mFormats ) as $formatName ) { 01063 $module = $this->createPrinterByName( $formatName ); 01064 $msg .= self::makeHelpMsgHeader( $module, 'format' ); 01065 $msg2 = $module->makeHelpMsg(); 01066 if ( $msg2 !== false ) { 01067 $msg .= $msg2; 01068 } 01069 $msg .= "\n"; 01070 } 01071 01072 $msg .= "\n*** Credits: ***\n " . implode( "\n ", $this->getCredits() ) . "\n"; 01073 01074 return $msg; 01075 } 01076 01082 public static function makeHelpMsgHeader( $module, $paramName ) { 01083 $modulePrefix = $module->getModulePrefix(); 01084 if ( strval( $modulePrefix ) !== '' ) { 01085 $modulePrefix = "($modulePrefix) "; 01086 } 01087 01088 return "* $paramName={$module->getModuleName()} $modulePrefix*"; 01089 } 01090 01091 private $mCanApiHighLimits = null; 01092 01097 public function canApiHighLimits() { 01098 if ( !isset( $this->mCanApiHighLimits ) ) { 01099 $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' ); 01100 } 01101 01102 return $this->mCanApiHighLimits; 01103 } 01104 01109 public function getShowVersions() { 01110 return $this->mShowVersions; 01111 } 01112 01119 public function getVersion() { 01120 $vers = array(); 01121 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; 01122 $vers[] = __CLASS__ . ': $Id$'; 01123 $vers[] = ApiBase::getBaseVersion(); 01124 $vers[] = ApiFormatBase::getBaseVersion(); 01125 $vers[] = ApiQueryBase::getBaseVersion(); 01126 return $vers; 01127 } 01128 01137 protected function addModule( $mdlName, $mdlClass ) { 01138 $this->mModules[$mdlName] = $mdlClass; 01139 } 01140 01148 protected function addFormat( $fmtName, $fmtClass ) { 01149 $this->mFormats[$fmtName] = $fmtClass; 01150 } 01151 01156 function getModules() { 01157 return $this->mModules; 01158 } 01159 01166 public function getFormats() { 01167 return $this->mFormats; 01168 } 01169 } 01170 01177 class UsageException extends MWException { 01178 01179 private $mCodestr; 01180 01184 private $mExtraData; 01185 01192 public function __construct( $message, $codestr, $code = 0, $extradata = null ) { 01193 parent::__construct( $message, $code ); 01194 $this->mCodestr = $codestr; 01195 $this->mExtraData = $extradata; 01196 } 01197 01201 public function getCodeString() { 01202 return $this->mCodestr; 01203 } 01204 01208 public function getMessageArray() { 01209 $result = array( 01210 'code' => $this->mCodestr, 01211 'info' => $this->getMessage() 01212 ); 01213 if ( is_array( $this->mExtraData ) ) { 01214 $result = array_merge( $result, $this->mExtraData ); 01215 } 01216 return $result; 01217 } 01218 01222 public function __toString() { 01223 return "{$this->getCodeString()}: {$this->getMessage()}"; 01224 } 01225 }