MediaWiki  REL1_19
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 
00065                 // Write modules
00066                 'purge' => 'ApiPurge',
00067                 'rollback' => 'ApiRollback',
00068                 'delete' => 'ApiDelete',
00069                 'undelete' => 'ApiUndelete',
00070                 'protect' => 'ApiProtect',
00071                 'block' => 'ApiBlock',
00072                 'unblock' => 'ApiUnblock',
00073                 'move' => 'ApiMove',
00074                 'edit' => 'ApiEditPage',
00075                 'upload' => 'ApiUpload',
00076                 'filerevert' => 'ApiFileRevert',
00077                 'emailuser' => 'ApiEmailUser',
00078                 'watch' => 'ApiWatch',
00079                 'patrol' => 'ApiPatrol',
00080                 'import' => 'ApiImport',
00081                 'userrights' => 'ApiUserrights',
00082         );
00083 
00087         private static $Formats = array(
00088                 'json' => 'ApiFormatJson',
00089                 'jsonfm' => 'ApiFormatJson',
00090                 'php' => 'ApiFormatPhp',
00091                 'phpfm' => 'ApiFormatPhp',
00092                 'wddx' => 'ApiFormatWddx',
00093                 'wddxfm' => 'ApiFormatWddx',
00094                 'xml' => 'ApiFormatXml',
00095                 'xmlfm' => 'ApiFormatXml',
00096                 'yaml' => 'ApiFormatYaml',
00097                 'yamlfm' => 'ApiFormatYaml',
00098                 'rawfm' => 'ApiFormatJson',
00099                 'txt' => 'ApiFormatTxt',
00100                 'txtfm' => 'ApiFormatTxt',
00101                 'dbg' => 'ApiFormatDbg',
00102                 'dbgfm' => 'ApiFormatDbg',
00103                 'dump' => 'ApiFormatDump',
00104                 'dumpfm' => 'ApiFormatDump',
00105         );
00106 
00113         private static $mRights = array(
00114                 'writeapi' => array(
00115                         'msg' => 'Use of the write API',
00116                         'params' => array()
00117                 ),
00118                 'apihighlimits' => array(
00119                         '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.',
00120                         'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
00121                 )
00122         );
00123 
00127         private $mPrinter;
00128 
00129         private $mModules, $mModuleNames, $mFormats, $mFormatNames;
00130         private $mResult, $mAction, $mShowVersions, $mEnableWrite;
00131         private $mInternalMode, $mSquidMaxage, $mModule;
00132 
00133         private $mCacheMode = 'private';
00134         private $mCacheControl = array();
00135 
00142         public function __construct( $context = null, $enableWrite = false ) {
00143                 if ( $context === null ) {
00144                         $context = RequestContext::getMain();
00145                 } elseif ( $context instanceof WebRequest ) {
00146                         // BC for pre-1.19
00147                         $request = $context;
00148                         $context = RequestContext::getMain();
00149                 }
00150                 // We set a derivative context so we can change stuff later
00151                 $this->setContext( new DerivativeContext( $context ) );
00152 
00153                 if ( isset( $request ) ) {
00154                         $this->getContext()->setRequest( $request );
00155                 }
00156 
00157                 $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest );
00158 
00159                 // Special handling for the main module: $parent === $this
00160                 parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
00161 
00162                 if ( !$this->mInternalMode ) {
00163                         // Impose module restrictions.
00164                         // If the current user cannot read,
00165                         // Remove all modules other than login
00166                         global $wgUser;
00167 
00168                         if ( $this->getRequest()->getVal( 'callback' ) !== null ) {
00169                                 // JSON callback allows cross-site reads.
00170                                 // For safety, strip user credentials.
00171                                 wfDebug( "API: stripping user credentials for JSON callback\n" );
00172                                 $wgUser = new User();
00173                                 $this->getContext()->setUser( $wgUser );
00174                         }
00175                 }
00176 
00177                 global $wgAPIModules; // extension modules
00178                 $this->mModules = $wgAPIModules + self::$Modules;
00179 
00180                 $this->mModuleNames = array_keys( $this->mModules );
00181                 $this->mFormats = self::$Formats;
00182                 $this->mFormatNames = array_keys( $this->mFormats );
00183 
00184                 $this->mResult = new ApiResult( $this );
00185                 $this->mShowVersions = false;
00186                 $this->mEnableWrite = $enableWrite;
00187 
00188                 $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
00189                 $this->mCommit = false;
00190         }
00191 
00196         public function isInternalMode() {
00197                 return $this->mInternalMode;
00198         }
00199 
00205         public function getResult() {
00206                 return $this->mResult;
00207         }
00208 
00214         public function getModule() {
00215                 return $this->mModule;
00216         }
00217 
00223         public function getPrinter() {
00224                 return $this->mPrinter;
00225         }
00226 
00232         public function setCacheMaxAge( $maxage ) {
00233                 $this->setCacheControl( array(
00234                         'max-age' => $maxage,
00235                         's-maxage' => $maxage
00236                 ) );
00237         }
00238 
00264         public function setCacheMode( $mode ) {
00265                 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
00266                         wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" );
00267                         // Ignore for forwards-compatibility
00268                         return;
00269                 }
00270 
00271                 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
00272                         // Private wiki, only private headers
00273                         if ( $mode !== 'private' ) {
00274                                 wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
00275                                 return;
00276                         }
00277                 }
00278 
00279                 wfDebug( __METHOD__ . ": setting cache mode $mode\n" );
00280                 $this->mCacheMode = $mode;
00281         }
00282 
00288         public function setCachePrivate() {
00289                 wfDeprecated( __METHOD__, '1.17' );
00290                 $this->setCacheMode( 'private' );
00291         }
00292 
00303         public function setCacheControl( $directives ) {
00304                 $this->mCacheControl = $directives + $this->mCacheControl;
00305         }
00306 
00317         public function setVaryCookie() {
00318                 wfDeprecated( __METHOD__, '1.17' );
00319                 $this->setCacheMode( 'anon-public-user-private' );
00320         }
00321 
00329         public function createPrinterByName( $format ) {
00330                 if ( !isset( $this->mFormats[$format] ) ) {
00331                         $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
00332                 }
00333                 return new $this->mFormats[$format] ( $this, $format );
00334         }
00335 
00339         public function execute() {
00340                 $this->profileIn();
00341                 if ( $this->mInternalMode ) {
00342                         $this->executeAction();
00343                 } else {
00344                         $this->executeActionWithErrorHandling();
00345                 }
00346 
00347                 $this->profileOut();
00348         }
00349 
00354         protected function executeActionWithErrorHandling() {
00355                 // In case an error occurs during data output,
00356                 // clear the output buffer and print just the error information
00357                 ob_start();
00358 
00359                 try {
00360                         $this->executeAction();
00361                 } catch ( Exception $e ) {
00362                         // Log it
00363                         if ( $e instanceof MWException ) {
00364                                 wfDebugLog( 'exception', $e->getLogMessage() );
00365                         }
00366 
00367                         // Handle any kind of exception by outputing properly formatted error message.
00368                         // If this fails, an unhandled exception should be thrown so that global error
00369                         // handler will process and log it.
00370 
00371                         $errCode = $this->substituteResultWithError( $e );
00372 
00373                         // Error results should not be cached
00374                         $this->setCacheMode( 'private' );
00375 
00376                         $response = $this->getRequest()->response();
00377                         $headerStr = 'MediaWiki-API-Error: ' . $errCode;
00378                         if ( $e->getCode() === 0 ) {
00379                                 $response->header( $headerStr );
00380                         } else {
00381                                 $response->header( $headerStr, true, $e->getCode() );
00382                         }
00383 
00384                         // Reset and print just the error message
00385                         ob_clean();
00386 
00387                         // If the error occured during printing, do a printer->profileOut()
00388                         $this->mPrinter->safeProfileOut();
00389                         $this->printResult( true );
00390                 }
00391 
00392                 // Send cache headers after any code which might generate an error, to
00393                 // avoid sending public cache headers for errors.
00394                 $this->sendCacheHeaders();
00395 
00396                 if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) {
00397                         echo wfReportTime();
00398                 }
00399 
00400                 ob_end_flush();
00401         }
00402 
00403         protected function sendCacheHeaders() {
00404                 global $wgUseXVO, $wgVaryOnXFP;
00405                 $response = $this->getRequest()->response();
00406 
00407                 if ( $this->mCacheMode == 'private' ) {
00408                         $response->header( 'Cache-Control: private' );
00409                         return;
00410                 }
00411 
00412                 if ( $this->mCacheMode == 'anon-public-user-private' ) {
00413                         $xfp = $wgVaryOnXFP ? ', X-Forwarded-Proto' : '';
00414                         $response->header( 'Vary: Accept-Encoding, Cookie' . $xfp );
00415                         if ( $wgUseXVO ) {
00416                                 $out = $this->getOutput();
00417                                 if ( $wgVaryOnXFP ) {
00418                                         $out->addVaryHeader( 'X-Forwarded-Proto' );
00419                                 }
00420                                 $response->header( $out->getXVO() );
00421                                 if ( $out->haveCacheVaryCookies() ) {
00422                                         // Logged in, mark this request private
00423                                         $response->header( 'Cache-Control: private' );
00424                                         return;
00425                                 }
00426                                 // Logged out, send normal public headers below
00427                         } elseif ( session_id() != '' ) {
00428                                 // Logged in or otherwise has session (e.g. anonymous users who have edited)
00429                                 // Mark request private
00430                                 $response->header( 'Cache-Control: private' );
00431                                 return;
00432                         } // else no XVO and anonymous, send public headers below
00433                 }
00434 
00435                 // Send public headers
00436                 if ( $wgVaryOnXFP ) {
00437                         $response->header( 'Vary: Accept-Encoding, X-Forwarded-Proto' );
00438                         if ( $wgUseXVO ) {
00439                                 // Bleeeeegh. Our header setting system sucks
00440                                 $response->header( 'X-Vary-Options: Accept-Encoding;list-contains=gzip, X-Forwarded-Proto' );
00441                         }
00442                 }
00443 
00444                 // If nobody called setCacheMaxAge(), use the (s)maxage parameters
00445                 if ( !isset( $this->mCacheControl['s-maxage'] ) ) {
00446                         $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' );
00447                 }
00448                 if ( !isset( $this->mCacheControl['max-age'] ) ) {
00449                         $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' );
00450                 }
00451 
00452                 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) {
00453                         // Public cache not requested
00454                         // Sending a Vary header in this case is harmless, and protects us
00455                         // against conditional calls of setCacheMaxAge().
00456                         $response->header( 'Cache-Control: private' );
00457                         return;
00458                 }
00459 
00460                 $this->mCacheControl['public'] = true;
00461 
00462                 // Send an Expires header
00463                 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
00464                 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
00465                 $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
00466 
00467                 // Construct the Cache-Control header
00468                 $ccHeader = '';
00469                 $separator = '';
00470                 foreach ( $this->mCacheControl as $name => $value ) {
00471                         if ( is_bool( $value ) ) {
00472                                 if ( $value ) {
00473                                         $ccHeader .= $separator . $name;
00474                                         $separator = ', ';
00475                                 }
00476                         } else {
00477                                 $ccHeader .= $separator . "$name=$value";
00478                                 $separator = ', ';
00479                         }
00480                 }
00481 
00482                 $response->header( "Cache-Control: $ccHeader" );
00483         }
00484 
00491         protected function substituteResultWithError( $e ) {
00492                 global $wgShowHostnames;
00493 
00494                 $result = $this->getResult();
00495                 // Printer may not be initialized if the extractRequestParams() fails for the main module
00496                 if ( !isset ( $this->mPrinter ) ) {
00497                         // The printer has not been created yet. Try to manually get formatter value.
00498                         $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
00499                         if ( !in_array( $value, $this->mFormatNames ) ) {
00500                                 $value = self::API_DEFAULT_FORMAT;
00501                         }
00502 
00503                         $this->mPrinter = $this->createPrinterByName( $value );
00504                         if ( $this->mPrinter->getNeedsRawData() ) {
00505                                 $result->setRawMode();
00506                         }
00507                 }
00508 
00509                 if ( $e instanceof UsageException ) {
00510                         // User entered incorrect parameters - print usage screen
00511                         $errMessage = $e->getMessageArray();
00512 
00513                         // Only print the help message when this is for the developer, not runtime
00514                         if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) {
00515                                 ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
00516                         }
00517 
00518                 } else {
00519                         global $wgShowSQLErrors, $wgShowExceptionDetails;
00520                         // Something is seriously wrong
00521                         if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
00522                                 $info = 'Database query error';
00523                         } else {
00524                                 $info = "Exception Caught: {$e->getMessage()}";
00525                         }
00526 
00527                         $errMessage = array(
00528                                 'code' => 'internal_api_error_' . get_class( $e ),
00529                                 'info' => $info,
00530                         );
00531                         ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
00532                 }
00533 
00534                 $result->reset();
00535                 $result->disableSizeCheck();
00536                 // Re-add the id
00537                 $requestid = $this->getParameter( 'requestid' );
00538                 if ( !is_null( $requestid ) ) {
00539                         $result->addValue( null, 'requestid', $requestid );
00540                 }
00541 
00542                 if ( $wgShowHostnames ) {
00543                         // servedby is especially useful when debugging errors
00544                         $result->addValue( null, 'servedby', wfHostName() );
00545                 }
00546 
00547                 $result->addValue( null, 'error', $errMessage );
00548 
00549                 return $errMessage['code'];
00550         }
00551 
00556         protected function setupExecuteAction() {
00557                 global $wgShowHostnames;
00558 
00559                 // First add the id to the top element
00560                 $result = $this->getResult();
00561                 $requestid = $this->getParameter( 'requestid' );
00562                 if ( !is_null( $requestid ) ) {
00563                         $result->addValue( null, 'requestid', $requestid );
00564                 }
00565 
00566                 if ( $wgShowHostnames ) {
00567                         $servedby = $this->getParameter( 'servedby' );
00568                         if ( $servedby ) {
00569                                 $result->addValue( null, 'servedby', wfHostName() );
00570                         }
00571                 }
00572 
00573                 $params = $this->extractRequestParams();
00574 
00575                 $this->mShowVersions = $params['version'];
00576                 $this->mAction = $params['action'];
00577 
00578                 if ( !is_string( $this->mAction ) ) {
00579                         $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
00580                 }
00581 
00582                 return $params;
00583         }
00584 
00589         protected function setupModule() {
00590                 // Instantiate the module requested by the user
00591                 $module = new $this->mModules[$this->mAction] ( $this, $this->mAction );
00592                 $this->mModule = $module;
00593 
00594                 $moduleParams = $module->extractRequestParams();
00595 
00596                 // Die if token required, but not provided (unless there is a gettoken parameter)
00597                 if ( isset( $moduleParams['gettoken'] ) ) {
00598                         $gettoken = $moduleParams['gettoken'];
00599                 } else {
00600                         $gettoken = false;
00601                 }
00602 
00603                 $salt = $module->getTokenSalt();
00604                 if ( $salt !== false && !$gettoken ) {
00605                         if ( !isset( $moduleParams['token'] ) ) {
00606                                 $this->dieUsageMsg( array( 'missingparam', 'token' ) );
00607                         } else {
00608                                 if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getRequest() ) ) {
00609                                         $this->dieUsageMsg( 'sessionfailure' );
00610                                 }
00611                         }
00612                 }
00613                 return $module;
00614         }
00615 
00622         protected function checkMaxLag( $module, $params ) {
00623                 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
00624                         // Check for maxlag
00625                         global $wgShowHostnames;
00626                         $maxLag = $params['maxlag'];
00627                         list( $host, $lag ) = wfGetLB()->getMaxLag();
00628                         if ( $lag > $maxLag ) {
00629                                 $response = $this->getRequest()->response();
00630 
00631                                 $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
00632                                 $response->header( 'X-Database-Lag: ' . intval( $lag ) );
00633 
00634                                 if ( $wgShowHostnames ) {
00635                                         $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
00636                                 } else {
00637                                         $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
00638                                 }
00639                                 return false;
00640                         }
00641                 }
00642                 return true;
00643         }
00644 
00649         protected function checkExecutePermissions( $module ) {
00650                 $user = $this->getUser();
00651                 if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
00652                         !$user->isAllowed( 'read' ) )
00653                 {
00654                         $this->dieUsageMsg( 'readrequired' );
00655                 }
00656                 if ( $module->isWriteMode() ) {
00657                         if ( !$this->mEnableWrite ) {
00658                                 $this->dieUsageMsg( 'writedisabled' );
00659                         }
00660                         if ( !$user->isAllowed( 'writeapi' ) ) {
00661                                 $this->dieUsageMsg( 'writerequired' );
00662                         }
00663                         if ( wfReadOnly() ) {
00664                                 $this->dieReadOnly();
00665                         }
00666                 }
00667         }
00668 
00674         protected function setupExternalResponse( $module, $params ) {
00675                 // Ignore mustBePosted() for internal calls
00676                 if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) {
00677                         $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) );
00678                 }
00679 
00680                 // See if custom printer is used
00681                 $this->mPrinter = $module->getCustomPrinter();
00682                 if ( is_null( $this->mPrinter ) ) {
00683                         // Create an appropriate printer
00684                         $this->mPrinter = $this->createPrinterByName( $params['format'] );
00685                 }
00686 
00687                 if ( $this->mPrinter->getNeedsRawData() ) {
00688                         $this->getResult()->setRawMode();
00689                 }
00690         }
00691 
00695         protected function executeAction() {
00696                 $params = $this->setupExecuteAction();
00697                 $module = $this->setupModule();
00698 
00699                 $this->checkExecutePermissions( $module );
00700 
00701                 if ( !$this->checkMaxLag( $module, $params ) ) {
00702                         return;
00703                 }
00704 
00705                 if ( !$this->mInternalMode ) {
00706                         $this->setupExternalResponse( $module, $params );
00707                 }
00708 
00709                 // Execute
00710                 $module->profileIn();
00711                 $module->execute();
00712                 wfRunHooks( 'APIAfterExecute', array( &$module ) );
00713                 $module->profileOut();
00714 
00715                 if ( !$this->mInternalMode ) {
00716                         // Print result data
00717                         $this->printResult( false );
00718                 }
00719         }
00720 
00726         protected function printResult( $isError ) {
00727                 $this->getResult()->cleanUpUTF8();
00728                 $printer = $this->mPrinter;
00729                 $printer->profileIn();
00730 
00736                 $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError )
00737                                 && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
00738 
00739                 $printer->initPrinter( $isError );
00740 
00741                 $printer->execute();
00742                 $printer->closePrinter();
00743                 $printer->profileOut();
00744         }
00745 
00749         public function isReadMode() {
00750                 return false;
00751         }
00752 
00758         public function getAllowedParams() {
00759                 return array(
00760                         'format' => array(
00761                                 ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
00762                                 ApiBase::PARAM_TYPE => $this->mFormatNames
00763                         ),
00764                         'action' => array(
00765                                 ApiBase::PARAM_DFLT => 'help',
00766                                 ApiBase::PARAM_TYPE => $this->mModuleNames
00767                         ),
00768                         'version' => false,
00769                         'maxlag'  => array(
00770                                 ApiBase::PARAM_TYPE => 'integer'
00771                         ),
00772                         'smaxage' => array(
00773                                 ApiBase::PARAM_TYPE => 'integer',
00774                                 ApiBase::PARAM_DFLT => 0
00775                         ),
00776                         'maxage' => array(
00777                                 ApiBase::PARAM_TYPE => 'integer',
00778                                 ApiBase::PARAM_DFLT => 0
00779                         ),
00780                         'requestid' => null,
00781                         'servedby'  => false,
00782                 );
00783         }
00784 
00790         public function getParamDescription() {
00791                 return array(
00792                         'format' => 'The format of the output',
00793                         'action' => 'What action you would like to perform. See below for module help',
00794                         'version' => 'When showing help, include version for each module',
00795                         'maxlag' => array(
00796                                 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.',
00797                                 'To save actions causing any more site replication lag, this parameter can make the client',
00798                                 'wait until the replication lag is less than the specified value.',
00799                                 'In case of a replag error, a HTTP 503 error is returned, with the message like',
00800                                 '"Waiting for $host: $lag seconds lagged\n".',
00801                                 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information',
00802                         ),
00803                         'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
00804                         'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
00805                         'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
00806                         'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error',
00807                 );
00808         }
00809 
00815         public function getDescription() {
00816                 return array(
00817                         '',
00818                         '',
00819                         '**********************************************************************************************************',
00820                         '**                                                                                                      **',
00821                         '**                      This is an auto-generated MediaWiki API documentation page                      **',
00822                         '**                                                                                                      **',
00823                         '**                                     Documentation and Examples:                                      **',
00824                         '**                                  https://www.mediawiki.org/wiki/API                                  **',
00825                         '**                                                                                                      **',
00826                         '**********************************************************************************************************',
00827                         '',
00828                         'Status:                All features shown on this page should be working, but the API',
00829                         '                       is still in active development, and may change at any time.',
00830                         '                       Make sure to monitor our mailing list for any updates',
00831                         '',
00832                         'Erroneous requests:    When erroneous requests are sent to the API, a HTTP header will be sent',
00833                         '                       with the key "MediaWiki-API-Error" and then both the value of the',
00834                         '                       header and the error code sent back will be set to the same value',
00835                         '',
00836                         '                       In the case of an invalid action being passed, these will have a value',
00837                         '                       of "unknown_action"',
00838                         '',
00839                         '                       For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings',
00840                         '',
00841                         'Documentation:         https://www.mediawiki.org/wiki/API:Main_page',
00842                         'FAQ                    https://www.mediawiki.org/wiki/API:FAQ',
00843                         'Mailing list:          https://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
00844                         'Api Announcements:     https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce',
00845                         'Bugs & Requests:       https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
00846                         '',
00847                         '',
00848                         '',
00849                         '',
00850                         '',
00851                 );
00852         }
00853 
00857         public function getPossibleErrors() {
00858                 return array_merge( parent::getPossibleErrors(), array(
00859                         array( 'readonlytext' ),
00860                         array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ),
00861                         array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ),
00862                         array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ),
00863                         array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ),
00864                 ) );
00865         }
00866 
00871         protected function getCredits() {
00872                 return array(
00873                         'API developers:',
00874                         '    Roan Kattouw <Firstname>.<Lastname>@gmail.com (lead developer Sep 2007-present)',
00875                         '    Victor Vasiliev - vasilvv at gee mail dot com',
00876                         '    Bryan Tong Minh - bryan . tongminh @ gmail . com',
00877                         '    Sam Reed - sam @ reedyboy . net',
00878                         '    Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)',
00879                         '',
00880                         'Please send your comments, suggestions and questions to [email protected]',
00881                         'or file a bug report at https://bugzilla.wikimedia.org/'
00882                 );
00883         }
00884 
00890         public function setHelp( $help = true ) {
00891                 $this->mPrinter->setHelp( $help );
00892         }
00893 
00899         public function makeHelpMsg() {
00900                 global $wgMemc, $wgAPICacheHelpTimeout;
00901                 $this->setHelp();
00902                 // Get help text from cache if present
00903                 $key = wfMemcKey( 'apihelp', $this->getModuleName(),
00904                         SpecialVersion::getVersion( 'nodb' ) .
00905                         $this->getShowVersions() );
00906                 if ( $wgAPICacheHelpTimeout > 0 ) {
00907                         $cached = $wgMemc->get( $key );
00908                         if ( $cached ) {
00909                                 return $cached;
00910                         }
00911                 }
00912                 $retval = $this->reallyMakeHelpMsg();
00913                 if ( $wgAPICacheHelpTimeout > 0 ) {
00914                         $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
00915                 }
00916                 return $retval;
00917         }
00918 
00922         public function reallyMakeHelpMsg() {
00923                 $this->setHelp();
00924 
00925                 // Use parent to make default message for the main module
00926                 $msg = parent::makeHelpMsg();
00927 
00928                 $astriks = str_repeat( '*** ', 14 );
00929                 $msg .= "\n\n$astriks Modules  $astriks\n\n";
00930                 foreach ( array_keys( $this->mModules ) as $moduleName ) {
00931                         $module = new $this->mModules[$moduleName] ( $this, $moduleName );
00932                         $msg .= self::makeHelpMsgHeader( $module, 'action' );
00933                         $msg2 = $module->makeHelpMsg();
00934                         if ( $msg2 !== false ) {
00935                                 $msg .= $msg2;
00936                         }
00937                         $msg .= "\n";
00938                 }
00939 
00940                 $msg .= "\n$astriks Permissions $astriks\n\n";
00941                 foreach ( self::$mRights as $right => $rightMsg ) {
00942                         $groups = User::getGroupsWithPermission( $right );
00943                         $msg .= "* " . $right . " *\n  " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
00944                                                 "\nGranted to:\n  " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
00945 
00946                 }
00947 
00948                 $msg .= "\n$astriks Formats  $astriks\n\n";
00949                 foreach ( array_keys( $this->mFormats ) as $formatName ) {
00950                         $module = $this->createPrinterByName( $formatName );
00951                         $msg .= self::makeHelpMsgHeader( $module, 'format' );
00952                         $msg2 = $module->makeHelpMsg();
00953                         if ( $msg2 !== false ) {
00954                                 $msg .= $msg2;
00955                         }
00956                         $msg .= "\n";
00957                 }
00958 
00959                 $msg .= "\n*** Credits: ***\n   " . implode( "\n   ", $this->getCredits() ) . "\n";
00960 
00961                 return $msg;
00962         }
00963 
00969         public static function makeHelpMsgHeader( $module, $paramName ) {
00970                 $modulePrefix = $module->getModulePrefix();
00971                 if ( strval( $modulePrefix ) !== '' ) {
00972                         $modulePrefix = "($modulePrefix) ";
00973                 }
00974 
00975                 return "* $paramName={$module->getModuleName()} $modulePrefix*";
00976         }
00977 
00978         private $mCanApiHighLimits = null;
00979 
00984         public function canApiHighLimits() {
00985                 if ( !isset( $this->mCanApiHighLimits ) ) {
00986                         $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' );
00987                 }
00988 
00989                 return $this->mCanApiHighLimits;
00990         }
00991 
00996         public function getShowVersions() {
00997                 return $this->mShowVersions;
00998         }
00999 
01006         public function getVersion() {
01007                 $vers = array();
01008                 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n    https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
01009                 $vers[] = __CLASS__ . ': $Id$';
01010                 $vers[] = ApiBase::getBaseVersion();
01011                 $vers[] = ApiFormatBase::getBaseVersion();
01012                 $vers[] = ApiQueryBase::getBaseVersion();
01013                 return $vers;
01014         }
01015 
01024         protected function addModule( $mdlName, $mdlClass ) {
01025                 $this->mModules[$mdlName] = $mdlClass;
01026         }
01027 
01035         protected function addFormat( $fmtName, $fmtClass ) {
01036                 $this->mFormats[$fmtName] = $fmtClass;
01037         }
01038 
01043         function getModules() {
01044                 return $this->mModules;
01045         }
01046 
01053         public function getFormats() {
01054                 return $this->mFormats;
01055         }
01056 }
01057 
01064 class UsageException extends Exception {
01065 
01066         private $mCodestr;
01067         private $mExtraData;
01068 
01069         public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
01070                 parent::__construct( $message, $code );
01071                 $this->mCodestr = $codestr;
01072                 $this->mExtraData = $extradata;
01073         }
01074 
01078         public function getCodeString() {
01079                 return $this->mCodestr;
01080         }
01081 
01085         public function getMessageArray() {
01086                 $result = array(
01087                         'code' => $this->mCodestr,
01088                         'info' => $this->getMessage()
01089                 );
01090                 if ( is_array( $this->mExtraData ) ) {
01091                         $result = array_merge( $result, $this->mExtraData );
01092                 }
01093                 return $result;
01094         }
01095 
01099         public function __toString() {
01100                 return "{$this->getCodeString()}: {$this->getMessage()}";
01101         }
01102 }