MediaWiki  master
ApiMain.php
Go to the documentation of this file.
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                 'none' => 'ApiFormatNone',
00109         );
00110 
00117         private static $mRights = array(
00118                 'writeapi' => array(
00119                         'msg' => 'Use of the write API',
00120                         'params' => array()
00121                 ),
00122                 'apihighlimits' => array(
00123                         '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.',
00124                         'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
00125                 )
00126         );
00127 
00131         private $mPrinter;
00132 
00133         private $mModules, $mModuleNames, $mFormats, $mFormatNames;
00134         private $mResult, $mAction, $mShowVersions, $mEnableWrite;
00135         private $mInternalMode, $mSquidMaxage, $mModule;
00136 
00137         private $mCacheMode = 'private';
00138         private $mCacheControl = array();
00139         private $mParamsUsed = array();
00140 
00147         public function __construct( $context = null, $enableWrite = false ) {
00148                 if ( $context === null ) {
00149                         $context = RequestContext::getMain();
00150                 } elseif ( $context instanceof WebRequest ) {
00151                         // BC for pre-1.19
00152                         $request = $context;
00153                         $context = RequestContext::getMain();
00154                 }
00155                 // We set a derivative context so we can change stuff later
00156                 $this->setContext( new DerivativeContext( $context ) );
00157 
00158                 if ( isset( $request ) ) {
00159                         $this->getContext()->setRequest( $request );
00160                 }
00161 
00162                 $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest );
00163 
00164                 // Special handling for the main module: $parent === $this
00165                 parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
00166 
00167                 if ( !$this->mInternalMode ) {
00168                         // Impose module restrictions.
00169                         // If the current user cannot read,
00170                         // Remove all modules other than login
00171                         global $wgUser;
00172 
00173                         if ( $this->getVal( 'callback' ) !== null ) {
00174                                 // JSON callback allows cross-site reads.
00175                                 // For safety, strip user credentials.
00176                                 wfDebug( "API: stripping user credentials for JSON callback\n" );
00177                                 $wgUser = new User();
00178                                 $this->getContext()->setUser( $wgUser );
00179                         }
00180                 }
00181 
00182                 global $wgAPIModules; // extension modules
00183                 $this->mModules = $wgAPIModules + self::$Modules;
00184 
00185                 $this->mModuleNames = array_keys( $this->mModules );
00186                 $this->mFormats = self::$Formats;
00187                 $this->mFormatNames = array_keys( $this->mFormats );
00188 
00189                 $this->mResult = new ApiResult( $this );
00190                 $this->mShowVersions = false;
00191                 $this->mEnableWrite = $enableWrite;
00192 
00193                 $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
00194                 $this->mCommit = false;
00195         }
00196 
00201         public function isInternalMode() {
00202                 return $this->mInternalMode;
00203         }
00204 
00210         public function getResult() {
00211                 return $this->mResult;
00212         }
00213 
00219         public function getModule() {
00220                 return $this->mModule;
00221         }
00222 
00228         public function getPrinter() {
00229                 return $this->mPrinter;
00230         }
00231 
00237         public function setCacheMaxAge( $maxage ) {
00238                 $this->setCacheControl( array(
00239                         'max-age' => $maxage,
00240                         's-maxage' => $maxage
00241                 ) );
00242         }
00243 
00269         public function setCacheMode( $mode ) {
00270                 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
00271                         wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" );
00272                         // Ignore for forwards-compatibility
00273                         return;
00274                 }
00275 
00276                 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
00277                         // Private wiki, only private headers
00278                         if ( $mode !== 'private' ) {
00279                                 wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
00280                                 return;
00281                         }
00282                 }
00283 
00284                 wfDebug( __METHOD__ . ": setting cache mode $mode\n" );
00285                 $this->mCacheMode = $mode;
00286         }
00287 
00293         public function setCachePrivate() {
00294                 wfDeprecated( __METHOD__, '1.17' );
00295                 $this->setCacheMode( 'private' );
00296         }
00297 
00308         public function setCacheControl( $directives ) {
00309                 $this->mCacheControl = $directives + $this->mCacheControl;
00310         }
00311 
00322         public function setVaryCookie() {
00323                 wfDeprecated( __METHOD__, '1.17' );
00324                 $this->setCacheMode( 'anon-public-user-private' );
00325         }
00326 
00334         public function createPrinterByName( $format ) {
00335                 if ( !isset( $this->mFormats[$format] ) ) {
00336                         $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
00337                 }
00338                 return new $this->mFormats[$format] ( $this, $format );
00339         }
00340 
00344         public function execute() {
00345                 $this->profileIn();
00346                 if ( $this->mInternalMode ) {
00347                         $this->executeAction();
00348                 } else {
00349                         $this->executeActionWithErrorHandling();
00350                 }
00351 
00352                 $this->profileOut();
00353         }
00354 
00359         protected function executeActionWithErrorHandling() {
00360                 // Verify the CORS header before executing the action
00361                 if ( !$this->handleCORS() ) {
00362                         // handleCORS() has sent a 403, abort
00363                         return;
00364                 }
00365 
00366                 // In case an error occurs during data output,
00367                 // clear the output buffer and print just the error information
00368                 ob_start();
00369 
00370                 $t = microtime( true );
00371                 try {
00372                         $this->executeAction();
00373                 } catch ( Exception $e ) {
00374                         // Allow extra cleanup and logging
00375                         wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
00376 
00377                         // Log it
00378                         if ( !( $e instanceof UsageException ) ) {
00379                                 global $wgLogExceptionBacktrace;
00380                                 if ( $wgLogExceptionBacktrace ) {
00381                                         wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" );
00382                                 } else {
00383                                         wfDebugLog( 'exception', $e->getLogMessage() );
00384                                 }
00385                         }
00386 
00387                         // Handle any kind of exception by outputing properly formatted error message.
00388                         // If this fails, an unhandled exception should be thrown so that global error
00389                         // handler will process and log it.
00390 
00391                         $errCode = $this->substituteResultWithError( $e );
00392 
00393                         // Error results should not be cached
00394                         $this->setCacheMode( 'private' );
00395 
00396                         $response = $this->getRequest()->response();
00397                         $headerStr = 'MediaWiki-API-Error: ' . $errCode;
00398                         if ( $e->getCode() === 0 ) {
00399                                 $response->header( $headerStr );
00400                         } else {
00401                                 $response->header( $headerStr, true, $e->getCode() );
00402                         }
00403 
00404                         // Reset and print just the error message
00405                         ob_clean();
00406 
00407                         // If the error occurred during printing, do a printer->profileOut()
00408                         $this->mPrinter->safeProfileOut();
00409                         $this->printResult( true );
00410                 }
00411 
00412                 // Log the request whether or not there was an error
00413                 $this->logRequest( microtime( true ) - $t);
00414 
00415                 // Send cache headers after any code which might generate an error, to
00416                 // avoid sending public cache headers for errors.
00417                 $this->sendCacheHeaders();
00418 
00419                 if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) {
00420                         echo wfReportTime();
00421                 }
00422 
00423                 ob_end_flush();
00424         }
00425 
00438         protected function handleCORS() {
00439                 global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions;
00440 
00441                 $originParam = $this->getParameter( 'origin' ); // defaults to null
00442                 if ( $originParam === null ) {
00443                         // No origin parameter, nothing to do
00444                         return true;
00445                 }
00446 
00447                 $request = $this->getRequest();
00448                 $response = $request->response();
00449                 // Origin: header is a space-separated list of origins, check all of them
00450                 $originHeader = $request->getHeader( 'Origin' );
00451                 if ( $originHeader === false ) {
00452                         $origins = array();
00453                 } else {
00454                         $origins = explode( ' ', $originHeader );
00455                 }
00456                 if ( !in_array( $originParam, $origins ) ) {
00457                         // origin parameter set but incorrect
00458                         // Send a 403 response
00459                         $message = HttpStatus::getMessage( 403 );
00460                         $response->header( "HTTP/1.1 403 $message", true, 403 );
00461                         $response->header( 'Cache-Control: no-cache' );
00462                         echo "'origin' parameter does not match Origin header\n";
00463                         return false;
00464                 }
00465                 if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) {
00466                         $response->header( "Access-Control-Allow-Origin: $originParam" );
00467                         $response->header( 'Access-Control-Allow-Credentials: true' );
00468                         $this->getOutput()->addVaryHeader( 'Origin' );
00469                 }
00470                 return true;
00471         }
00472 
00480         protected static function matchOrigin( $value, $rules, $exceptions ) {
00481                 foreach ( $rules as $rule ) {
00482                         if ( preg_match( self::wildcardToRegex( $rule ), $value ) ) {
00483                                 // Rule matches, check exceptions
00484                                 foreach ( $exceptions as $exc ) {
00485                                         if ( preg_match( self::wildcardToRegex( $exc ), $value ) ) {
00486                                                 return false;
00487                                         }
00488                                 }
00489                                 return true;
00490                         }
00491                 }
00492                 return false;
00493         }
00494 
00503         protected static function wildcardToRegex( $wildcard ) {
00504                 $wildcard = preg_quote( $wildcard, '/' );
00505                 $wildcard = str_replace(
00506                         array( '\*', '\?' ),
00507                         array( '.*?', '.' ),
00508                         $wildcard
00509                 );
00510                 return "/https?:\/\/$wildcard/";
00511         }
00512 
00513         protected function sendCacheHeaders() {
00514                 global $wgUseXVO, $wgVaryOnXFP;
00515                 $response = $this->getRequest()->response();
00516                 $out = $this->getOutput();
00517 
00518                 if ( $wgVaryOnXFP ) {
00519                         $out->addVaryHeader( 'X-Forwarded-Proto' );
00520                 }
00521 
00522                 if ( $this->mCacheMode == 'private' ) {
00523                         $response->header( 'Cache-Control: private' );
00524                         return;
00525                 }
00526 
00527                 if ( $this->mCacheMode == 'anon-public-user-private' ) {
00528                         $out->addVaryHeader( 'Cookie' );
00529                         $response->header( $out->getVaryHeader() );
00530                         if ( $wgUseXVO ) {
00531                                 $response->header( $out->getXVO() );
00532                                 if ( $out->haveCacheVaryCookies() ) {
00533                                         // Logged in, mark this request private
00534                                         $response->header( 'Cache-Control: private' );
00535                                         return;
00536                                 }
00537                                 // Logged out, send normal public headers below
00538                         } elseif ( session_id() != '' ) {
00539                                 // Logged in or otherwise has session (e.g. anonymous users who have edited)
00540                                 // Mark request private
00541                                 $response->header( 'Cache-Control: private' );
00542                                 return;
00543                         } // else no XVO and anonymous, send public headers below
00544                 }
00545 
00546                 // Send public headers
00547                 $response->header( $out->getVaryHeader() );
00548                 if ( $wgUseXVO ) {
00549                         $response->header( $out->getXVO() );
00550                 }
00551 
00552                 // If nobody called setCacheMaxAge(), use the (s)maxage parameters
00553                 if ( !isset( $this->mCacheControl['s-maxage'] ) ) {
00554                         $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' );
00555                 }
00556                 if ( !isset( $this->mCacheControl['max-age'] ) ) {
00557                         $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' );
00558                 }
00559 
00560                 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) {
00561                         // Public cache not requested
00562                         // Sending a Vary header in this case is harmless, and protects us
00563                         // against conditional calls of setCacheMaxAge().
00564                         $response->header( 'Cache-Control: private' );
00565                         return;
00566                 }
00567 
00568                 $this->mCacheControl['public'] = true;
00569 
00570                 // Send an Expires header
00571                 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
00572                 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
00573                 $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
00574 
00575                 // Construct the Cache-Control header
00576                 $ccHeader = '';
00577                 $separator = '';
00578                 foreach ( $this->mCacheControl as $name => $value ) {
00579                         if ( is_bool( $value ) ) {
00580                                 if ( $value ) {
00581                                         $ccHeader .= $separator . $name;
00582                                         $separator = ', ';
00583                                 }
00584                         } else {
00585                                 $ccHeader .= $separator . "$name=$value";
00586                                 $separator = ', ';
00587                         }
00588                 }
00589 
00590                 $response->header( "Cache-Control: $ccHeader" );
00591         }
00592 
00599         protected function substituteResultWithError( $e ) {
00600                 global $wgShowHostnames;
00601 
00602                 $result = $this->getResult();
00603                 // Printer may not be initialized if the extractRequestParams() fails for the main module
00604                 if ( !isset ( $this->mPrinter ) ) {
00605                         // The printer has not been created yet. Try to manually get formatter value.
00606                         $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
00607                         if ( !in_array( $value, $this->mFormatNames ) ) {
00608                                 $value = self::API_DEFAULT_FORMAT;
00609                         }
00610 
00611                         $this->mPrinter = $this->createPrinterByName( $value );
00612                         if ( $this->mPrinter->getNeedsRawData() ) {
00613                                 $result->setRawMode();
00614                         }
00615                 }
00616 
00617                 if ( $e instanceof UsageException ) {
00618                         // User entered incorrect parameters - print usage screen
00619                         $errMessage = $e->getMessageArray();
00620 
00621                         // Only print the help message when this is for the developer, not runtime
00622                         if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) {
00623                                 ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
00624                         }
00625 
00626                 } else {
00627                         global $wgShowSQLErrors, $wgShowExceptionDetails;
00628                         // Something is seriously wrong
00629                         if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
00630                                 $info = 'Database query error';
00631                         } else {
00632                                 $info = "Exception Caught: {$e->getMessage()}";
00633                         }
00634 
00635                         $errMessage = array(
00636                                 'code' => 'internal_api_error_' . get_class( $e ),
00637                                 'info' => $info,
00638                         );
00639                         ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
00640                 }
00641 
00642                 $result->reset();
00643                 $result->disableSizeCheck();
00644                 // Re-add the id
00645                 $requestid = $this->getParameter( 'requestid' );
00646                 if ( !is_null( $requestid ) ) {
00647                         $result->addValue( null, 'requestid', $requestid );
00648                 }
00649 
00650                 if ( $wgShowHostnames ) {
00651                         // servedby is especially useful when debugging errors
00652                         $result->addValue( null, 'servedby', wfHostName() );
00653                 }
00654 
00655                 $result->addValue( null, 'error', $errMessage );
00656 
00657                 return $errMessage['code'];
00658         }
00659 
00664         protected function setupExecuteAction() {
00665                 global $wgShowHostnames;
00666 
00667                 // First add the id to the top element
00668                 $result = $this->getResult();
00669                 $requestid = $this->getParameter( 'requestid' );
00670                 if ( !is_null( $requestid ) ) {
00671                         $result->addValue( null, 'requestid', $requestid );
00672                 }
00673 
00674                 if ( $wgShowHostnames ) {
00675                         $servedby = $this->getParameter( 'servedby' );
00676                         if ( $servedby ) {
00677                                 $result->addValue( null, 'servedby', wfHostName() );
00678                         }
00679                 }
00680 
00681                 $params = $this->extractRequestParams();
00682 
00683                 $this->mShowVersions = $params['version'];
00684                 $this->mAction = $params['action'];
00685 
00686                 if ( !is_string( $this->mAction ) ) {
00687                         $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
00688                 }
00689 
00690                 return $params;
00691         }
00692 
00697         protected function setupModule() {
00698                 // Instantiate the module requested by the user
00699                 $module = new $this->mModules[$this->mAction] ( $this, $this->mAction );
00700                 $this->mModule = $module;
00701 
00702                 $moduleParams = $module->extractRequestParams();
00703 
00704                 // Die if token required, but not provided (unless there is a gettoken parameter)
00705                 if ( isset( $moduleParams['gettoken'] ) ) {
00706                         $gettoken = $moduleParams['gettoken'];
00707                 } else {
00708                         $gettoken = false;
00709                 }
00710 
00711                 $salt = $module->getTokenSalt();
00712                 if ( $salt !== false && !$gettoken ) {
00713                         if ( !isset( $moduleParams['token'] ) ) {
00714                                 $this->dieUsageMsg( array( 'missingparam', 'token' ) );
00715                         } else {
00716                                 if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) {
00717                                         $this->dieUsageMsg( 'sessionfailure' );
00718                                 }
00719                         }
00720                 }
00721                 return $module;
00722         }
00723 
00730         protected function checkMaxLag( $module, $params ) {
00731                 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
00732                         // Check for maxlag
00733                         global $wgShowHostnames;
00734                         $maxLag = $params['maxlag'];
00735                         list( $host, $lag ) = wfGetLB()->getMaxLag();
00736                         if ( $lag > $maxLag ) {
00737                                 $response = $this->getRequest()->response();
00738 
00739                                 $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
00740                                 $response->header( 'X-Database-Lag: ' . intval( $lag ) );
00741 
00742                                 if ( $wgShowHostnames ) {
00743                                         $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
00744                                 } else {
00745                                         $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
00746                                 }
00747                                 return false;
00748                         }
00749                 }
00750                 return true;
00751         }
00752 
00757         protected function checkExecutePermissions( $module ) {
00758                 $user = $this->getUser();
00759                 if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
00760                         !$user->isAllowed( 'read' ) )
00761                 {
00762                         $this->dieUsageMsg( 'readrequired' );
00763                 }
00764                 if ( $module->isWriteMode() ) {
00765                         if ( !$this->mEnableWrite ) {
00766                                 $this->dieUsageMsg( 'writedisabled' );
00767                         }
00768                         if ( !$user->isAllowed( 'writeapi' ) ) {
00769                                 $this->dieUsageMsg( 'writerequired' );
00770                         }
00771                         if ( wfReadOnly() ) {
00772                                 $this->dieReadOnly();
00773                         }
00774                 }
00775 
00776                 // Allow extensions to stop execution for arbitrary reasons.
00777                 $message = false;
00778                 if( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) {
00779                         $this->dieUsageMsg( $message );
00780                 }
00781         }
00782 
00788         protected function setupExternalResponse( $module, $params ) {
00789                 // Ignore mustBePosted() for internal calls
00790                 if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) {
00791                         $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) );
00792                 }
00793 
00794                 // See if custom printer is used
00795                 $this->mPrinter = $module->getCustomPrinter();
00796                 if ( is_null( $this->mPrinter ) ) {
00797                         // Create an appropriate printer
00798                         $this->mPrinter = $this->createPrinterByName( $params['format'] );
00799                 }
00800 
00801                 if ( $this->mPrinter->getNeedsRawData() ) {
00802                         $this->getResult()->setRawMode();
00803                 }
00804         }
00805 
00809         protected function executeAction() {
00810                 $params = $this->setupExecuteAction();
00811                 $module = $this->setupModule();
00812 
00813                 $this->checkExecutePermissions( $module );
00814 
00815                 if ( !$this->checkMaxLag( $module, $params ) ) {
00816                         return;
00817                 }
00818 
00819                 if ( !$this->mInternalMode ) {
00820                         $this->setupExternalResponse( $module, $params );
00821                 }
00822 
00823                 // Execute
00824                 $module->profileIn();
00825                 $module->execute();
00826                 wfRunHooks( 'APIAfterExecute', array( &$module ) );
00827                 $module->profileOut();
00828 
00829                 if ( !$this->mInternalMode ) {
00830                         // Report unused params
00831                         $this->reportUnusedParams();
00832 
00833                         //append Debug information
00834                         MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() );
00835 
00836                         // Print result data
00837                         $this->printResult( false );
00838                 }
00839         }
00840 
00845         protected function logRequest( $time ) {
00846                 $request = $this->getRequest();
00847                 $milliseconds = $time === null ? '?' : round( $time * 1000 );
00848                 $s = 'API' .
00849                         ' ' . $request->getMethod() .
00850                         ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
00851                         ' ' . $request->getIP() .
00852                         ' T=' . $milliseconds .'ms';
00853                 foreach ( $this->getParamsUsed() as $name ) {
00854                         $value = $request->getVal( $name );
00855                         if ( $value === null ) {
00856                                 continue;
00857                         }
00858                         $s .= ' ' . $name . '=';
00859                         if ( strlen( $value ) > 256 ) {
00860                                 $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) );
00861                                 $s .= $encValue . '[...]';
00862                         } else {
00863                                 $s .= $this->encodeRequestLogValue( $value );
00864                         }
00865                 }
00866                 $s .= "\n";
00867                 wfDebugLog( 'api', $s, false );
00868         }
00869 
00873         protected function encodeRequestLogValue( $s ) {
00874                 static $table;
00875                 if ( !$table ) {
00876                         $chars = ';@$!*(),/:';
00877                         for ( $i = 0; $i < strlen( $chars ); $i++ ) {
00878                                 $table[ rawurlencode( $chars[$i] ) ] = $chars[$i];
00879                         }
00880                 }
00881                 return strtr( rawurlencode( $s ), $table );
00882         }
00883 
00887         protected function getParamsUsed() {
00888                 return array_keys( $this->mParamsUsed );
00889         }
00890 
00894         public function getVal( $name, $default = null ) {
00895                 $this->mParamsUsed[$name] = true;
00896                 return $this->getRequest()->getVal( $name, $default );
00897         }
00898 
00903         public function getCheck( $name ) {
00904                 $this->mParamsUsed[$name] = true;
00905                 return $this->getRequest()->getCheck( $name );
00906         }
00907 
00912         protected function reportUnusedParams() {
00913                 $paramsUsed = $this->getParamsUsed();
00914                 $allParams = $this->getRequest()->getValueNames();
00915 
00916                 $unusedParams = array_diff( $allParams, $paramsUsed );
00917                 if( count( $unusedParams ) ) {
00918                         $s = count( $unusedParams ) > 1 ? 's' : '';
00919                         $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" );
00920                 }
00921         }
00922 
00928         protected function printResult( $isError ) {
00929                 $this->getResult()->cleanUpUTF8();
00930                 $printer = $this->mPrinter;
00931                 $printer->profileIn();
00932 
00938                 $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError )
00939                                 && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
00940 
00941                 $printer->initPrinter( $isError );
00942 
00943                 $printer->execute();
00944                 $printer->closePrinter();
00945                 $printer->profileOut();
00946         }
00947 
00951         public function isReadMode() {
00952                 return false;
00953         }
00954 
00960         public function getAllowedParams() {
00961                 return array(
00962                         'format' => array(
00963                                 ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
00964                                 ApiBase::PARAM_TYPE => $this->mFormatNames
00965                         ),
00966                         'action' => array(
00967                                 ApiBase::PARAM_DFLT => 'help',
00968                                 ApiBase::PARAM_TYPE => $this->mModuleNames
00969                         ),
00970                         'version' => false,
00971                         'maxlag'  => array(
00972                                 ApiBase::PARAM_TYPE => 'integer'
00973                         ),
00974                         'smaxage' => array(
00975                                 ApiBase::PARAM_TYPE => 'integer',
00976                                 ApiBase::PARAM_DFLT => 0
00977                         ),
00978                         'maxage' => array(
00979                                 ApiBase::PARAM_TYPE => 'integer',
00980                                 ApiBase::PARAM_DFLT => 0
00981                         ),
00982                         'requestid' => null,
00983                         'servedby'  => false,
00984                         'origin' => null,
00985                 );
00986         }
00987 
00993         public function getParamDescription() {
00994                 return array(
00995                         'format' => 'The format of the output',
00996                         'action' => 'What action you would like to perform. See below for module help',
00997                         'version' => 'When showing help, include version for each module',
00998                         'maxlag' => array(
00999                                 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.',
01000                                 'To save actions causing any more site replication lag, this parameter can make the client',
01001                                 'wait until the replication lag is less than the specified value.',
01002                                 'In case of a replag error, a HTTP 503 error is returned, with the message like',
01003                                 '"Waiting for $host: $lag seconds lagged\n".',
01004                                 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information',
01005                         ),
01006                         'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
01007                         'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
01008                         'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
01009                         'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error',
01010                         'origin' => array(
01011                                 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.',
01012                                 '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 .',
01013                                 'If this parameter does not match the Origin: header, a 403 response will be returned.',
01014                                 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.',
01015                         ),
01016                 );
01017         }
01018 
01024         public function getDescription() {
01025                 return array(
01026                         '',
01027                         '',
01028                         '**********************************************************************************************************',
01029                         '**                                                                                                      **',
01030                         '**                      This is an auto-generated MediaWiki API documentation page                      **',
01031                         '**                                                                                                      **',
01032                         '**                                     Documentation and Examples:                                      **',
01033                         '**                                  https://www.mediawiki.org/wiki/API                                  **',
01034                         '**                                                                                                      **',
01035                         '**********************************************************************************************************',
01036                         '',
01037                         'Status:                All features shown on this page should be working, but the API',
01038                         '                       is still in active development, and may change at any time.',
01039                         '                       Make sure to monitor our mailing list for any updates',
01040                         '',
01041                         'Erroneous requests:    When erroneous requests are sent to the API, a HTTP header will be sent',
01042                         '                       with the key "MediaWiki-API-Error" and then both the value of the',
01043                         '                       header and the error code sent back will be set to the same value',
01044                         '',
01045                         '                       In the case of an invalid action being passed, these will have a value',
01046                         '                       of "unknown_action"',
01047                         '',
01048                         '                       For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings',
01049                         '',
01050                         'Documentation:         https://www.mediawiki.org/wiki/API:Main_page',
01051                         'FAQ                    https://www.mediawiki.org/wiki/API:FAQ',
01052                         'Mailing list:          https://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
01053                         'Api Announcements:     https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce',
01054                         'Bugs & Requests:       https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
01055                         '',
01056                         '',
01057                         '',
01058                         '',
01059                         '',
01060                 );
01061         }
01062 
01066         public function getPossibleErrors() {
01067                 return array_merge( parent::getPossibleErrors(), array(
01068                         array( 'readonlytext' ),
01069                         array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ),
01070                         array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ),
01071                         array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ),
01072                         array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ),
01073                 ) );
01074         }
01075 
01080         protected function getCredits() {
01081                 return array(
01082                         'API developers:',
01083                         '    Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-present)',
01084                         '    Victor Vasiliev - vasilvv at gee mail dot com',
01085                         '    Bryan Tong Minh - bryan . tongminh @ gmail . com',
01086                         '    Sam Reed - sam @ reedyboy . net',
01087                         '    Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007)',
01088                         '',
01089                         'Please send your comments, suggestions and questions to [email protected]',
01090                         'or file a bug report at https://bugzilla.wikimedia.org/'
01091                 );
01092         }
01093 
01099         public function setHelp( $help = true ) {
01100                 $this->mPrinter->setHelp( $help );
01101         }
01102 
01108         public function makeHelpMsg() {
01109                 global $wgMemc, $wgAPICacheHelpTimeout;
01110                 $this->setHelp();
01111                 // Get help text from cache if present
01112                 $key = wfMemcKey( 'apihelp', $this->getModuleName(),
01113                         SpecialVersion::getVersion( 'nodb' ) .
01114                         $this->getShowVersions() );
01115                 if ( $wgAPICacheHelpTimeout > 0 ) {
01116                         $cached = $wgMemc->get( $key );
01117                         if ( $cached ) {
01118                                 return $cached;
01119                         }
01120                 }
01121                 $retval = $this->reallyMakeHelpMsg();
01122                 if ( $wgAPICacheHelpTimeout > 0 ) {
01123                         $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
01124                 }
01125                 return $retval;
01126         }
01127 
01131         public function reallyMakeHelpMsg() {
01132                 $this->setHelp();
01133 
01134                 // Use parent to make default message for the main module
01135                 $msg = parent::makeHelpMsg();
01136 
01137                 $astriks = str_repeat( '*** ', 14 );
01138                 $msg .= "\n\n$astriks Modules  $astriks\n\n";
01139                 foreach ( array_keys( $this->mModules ) as $moduleName ) {
01140                         $module = new $this->mModules[$moduleName] ( $this, $moduleName );
01141                         $msg .= self::makeHelpMsgHeader( $module, 'action' );
01142                         $msg2 = $module->makeHelpMsg();
01143                         if ( $msg2 !== false ) {
01144                                 $msg .= $msg2;
01145                         }
01146                         $msg .= "\n";
01147                 }
01148 
01149                 $msg .= "\n$astriks Permissions $astriks\n\n";
01150                 foreach ( self::$mRights as $right => $rightMsg ) {
01151                         $groups = User::getGroupsWithPermission( $right );
01152                         $msg .= "* " . $right . " *\n  " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
01153                                                 "\nGranted to:\n  " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
01154 
01155                 }
01156 
01157                 $msg .= "\n$astriks Formats  $astriks\n\n";
01158                 foreach ( array_keys( $this->mFormats ) as $formatName ) {
01159                         $module = $this->createPrinterByName( $formatName );
01160                         $msg .= self::makeHelpMsgHeader( $module, 'format' );
01161                         $msg2 = $module->makeHelpMsg();
01162                         if ( $msg2 !== false ) {
01163                                 $msg .= $msg2;
01164                         }
01165                         $msg .= "\n";
01166                 }
01167 
01168                 $msg .= "\n*** Credits: ***\n   " . implode( "\n   ", $this->getCredits() ) . "\n";
01169 
01170                 return $msg;
01171         }
01172 
01178         public static function makeHelpMsgHeader( $module, $paramName ) {
01179                 $modulePrefix = $module->getModulePrefix();
01180                 if ( strval( $modulePrefix ) !== '' ) {
01181                         $modulePrefix = "($modulePrefix) ";
01182                 }
01183 
01184                 return "* $paramName={$module->getModuleName()} $modulePrefix*";
01185         }
01186 
01187         private $mCanApiHighLimits = null;
01188 
01193         public function canApiHighLimits() {
01194                 if ( !isset( $this->mCanApiHighLimits ) ) {
01195                         $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' );
01196                 }
01197 
01198                 return $this->mCanApiHighLimits;
01199         }
01200 
01205         public function getShowVersions() {
01206                 return $this->mShowVersions;
01207         }
01208 
01215         public function getVersion() {
01216                 $vers = array();
01217                 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n    https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
01218                 $vers[] = __CLASS__ . ': $Id$';
01219                 $vers[] = ApiBase::getBaseVersion();
01220                 $vers[] = ApiFormatBase::getBaseVersion();
01221                 $vers[] = ApiQueryBase::getBaseVersion();
01222                 return $vers;
01223         }
01224 
01233         protected function addModule( $mdlName, $mdlClass ) {
01234                 $this->mModules[$mdlName] = $mdlClass;
01235         }
01236 
01244         protected function addFormat( $fmtName, $fmtClass ) {
01245                 $this->mFormats[$fmtName] = $fmtClass;
01246         }
01247 
01252         function getModules() {
01253                 return $this->mModules;
01254         }
01255 
01262         public function getFormats() {
01263                 return $this->mFormats;
01264         }
01265 }
01266 
01273 class UsageException extends MWException {
01274 
01275         private $mCodestr;
01276 
01280         private $mExtraData;
01281 
01288         public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
01289                 parent::__construct( $message, $code );
01290                 $this->mCodestr = $codestr;
01291                 $this->mExtraData = $extradata;
01292         }
01293 
01297         public function getCodeString() {
01298                 return $this->mCodestr;
01299         }
01300 
01304         public function getMessageArray() {
01305                 $result = array(
01306                         'code' => $this->mCodestr,
01307                         'info' => $this->getMessage()
01308                 );
01309                 if ( is_array( $this->mExtraData ) ) {
01310                         $result = array_merge( $result, $this->mExtraData );
01311                 }
01312                 return $result;
01313         }
01314 
01318         public function __toString() {
01319                 return "{$this->getCodeString()}: {$this->getMessage()}";
01320         }
01321 }