MediaWiki  REL1_20
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         );
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 }