]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiMain.php
MediaWiki 1.16.1
[autoinstalls/mediawiki.git] / includes / api / ApiMain.php
1 <?php
2
3 /*
4  * Created on Sep 4, 2006
5  *
6  * API for MediaWiki 1.8+
7  *
8  * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23  * http://www.gnu.org/copyleft/gpl.html
24  */
25
26 if ( !defined( 'MEDIAWIKI' ) ) {
27         // Eclipse helper - will be ignored in production
28         require_once ( 'ApiBase.php' );
29 }
30
31 /**
32  * @defgroup API API
33  */
34
35 /**
36  * This is the main API class, used for both external and internal processing.
37  * When executed, it will create the requested formatter object,
38  * instantiate and execute an object associated with the needed action,
39  * and use formatter to print results.
40  * In case of an exception, an error message will be printed using the same formatter.
41  *
42  * To use API from another application, run it using FauxRequest object, in which
43  * case any internal exceptions will not be handled but passed up to the caller.
44  * After successful execution, use getResult() for the resulting data.
45  *
46  * @ingroup API
47  */
48 class ApiMain extends ApiBase {
49
50         /**
51          * When no format parameter is given, this format will be used
52          */
53         const API_DEFAULT_FORMAT = 'xmlfm';
54
55         /**
56          * List of available modules: action name => module class
57          */
58         private static $Modules = array (
59                 'login' => 'ApiLogin',
60                 'logout' => 'ApiLogout',
61                 'query' => 'ApiQuery',
62                 'expandtemplates' => 'ApiExpandTemplates',
63                 'parse' => 'ApiParse',
64                 'opensearch' => 'ApiOpenSearch',
65                 'feedwatchlist' => 'ApiFeedWatchlist',
66                 'help' => 'ApiHelp',
67                 'paraminfo' => 'ApiParamInfo',
68
69                 // Write modules
70                 'purge' => 'ApiPurge',
71                 'rollback' => 'ApiRollback',
72                 'delete' => 'ApiDelete',
73                 'undelete' => 'ApiUndelete',
74                 'protect' => 'ApiProtect',
75                 'block' => 'ApiBlock',
76                 'unblock' => 'ApiUnblock',
77                 'move' => 'ApiMove',
78                 'edit' => 'ApiEditPage',
79                 'upload' => 'ApiUpload',
80                 'emailuser' => 'ApiEmailUser',
81                 'watch' => 'ApiWatch',
82                 'patrol' => 'ApiPatrol',
83                 'import' => 'ApiImport',
84                 'userrights' => 'ApiUserrights',
85         );
86
87         /**
88          * List of available formats: format name => format class
89          */
90         private static $Formats = array (
91                 'json' => 'ApiFormatJson',
92                 'jsonfm' => 'ApiFormatJson',
93                 'php' => 'ApiFormatPhp',
94                 'phpfm' => 'ApiFormatPhp',
95                 'wddx' => 'ApiFormatWddx',
96                 'wddxfm' => 'ApiFormatWddx',
97                 'xml' => 'ApiFormatXml',
98                 'xmlfm' => 'ApiFormatXml',
99                 'yaml' => 'ApiFormatYaml',
100                 'yamlfm' => 'ApiFormatYaml',
101                 'rawfm' => 'ApiFormatJson',
102                 'txt' => 'ApiFormatTxt',
103                 'txtfm' => 'ApiFormatTxt',
104                 'dbg' => 'ApiFormatDbg',
105                 'dbgfm' => 'ApiFormatDbg'
106         );
107
108         /**
109          * List of user roles that are specifically relevant to the API.
110          * array( 'right' => array ( 'msg'    => 'Some message with a $1',
111          *                           'params' => array ( $someVarToSubst ) ),
112          *                          );
113          */
114         private static $mRights = array( 'writeapi' => array(
115                                                 'msg' => 'Use of the write API',
116                                                 'params' => array()
117                                         ),
118                                         'apihighlimits' => array(
119                                                 '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.',
120                                                 'params' => array ( ApiMain::LIMIT_SML2, ApiMain::LIMIT_BIG2 )
121                                         )
122         );
123
124
125         private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
126         private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest;
127         private $mInternalMode, $mSquidMaxage, $mModule;
128
129         private $mCacheMode = 'private';
130         private $mCacheControl = array();
131
132         /**
133         * Constructs an instance of ApiMain that utilizes the module and format specified by $request.
134         *
135         * @param $request object - if this is an instance of FauxRequest, errors are thrown and no printing occurs
136         * @param $enableWrite bool should be set to true if the api may modify data
137         */
138         public function __construct( $request, $enableWrite = false ) {
139
140                 $this->mInternalMode = ( $request instanceof FauxRequest );
141
142                 // Special handling for the main module: $parent === $this
143                 parent :: __construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
144
145                 if ( !$this->mInternalMode ) {
146
147                         // Impose module restrictions.
148                         // If the current user cannot read,
149                         // Remove all modules other than login
150                         global $wgUser;
151
152                         if ( $request->getVal( 'callback' ) !== null ) {
153                                 // JSON callback allows cross-site reads.
154                                 // For safety, strip user credentials.
155                                 wfDebug( "API: stripping user credentials for JSON callback\n" );
156                                 $wgUser = new User();
157                         }
158                 }
159
160                 global $wgAPIModules; // extension modules
161                 $this->mModules = $wgAPIModules + self :: $Modules;
162
163                 $this->mModuleNames = array_keys( $this->mModules );
164                 $this->mFormats = self :: $Formats;
165                 $this->mFormatNames = array_keys( $this->mFormats );
166
167                 $this->mResult = new ApiResult( $this );
168                 $this->mShowVersions = false;
169                 $this->mEnableWrite = $enableWrite;
170
171                 $this->mRequest = & $request;
172
173                 $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
174                 $this->mCommit = false;
175         }
176
177         /**
178          * Return true if the API was started by other PHP code using FauxRequest
179          */
180         public function isInternalMode() {
181                 return $this->mInternalMode;
182         }
183
184         /**
185          * Return the request object that contains client's request
186          */
187         public function getRequest() {
188                 return $this->mRequest;
189         }
190
191         /**
192          * Get the ApiResult object associated with current request
193          */
194         public function getResult() {
195                 return $this->mResult;
196         }
197
198         /**
199          * Get the API module object. Only works after executeAction()
200          */
201         public function getModule() {
202                 return $this->mModule;
203         }
204
205         /**
206          * Only kept for backwards compatibility
207          * @deprecated Use isWriteMode() instead
208          */
209         public function requestWriteMode() {
210                 if ( !$this->mEnableWrite )
211                         $this->dieUsageMsg( array( 'writedisabled' ) );
212                 if ( wfReadOnly() )
213                         $this->dieUsageMsg( array( 'readonlytext' ) );
214         }
215
216         /**
217          * Set how long the response should be cached.
218          */
219         public function setCacheMaxAge( $maxage ) {
220                 $this->setCacheControl( array(
221                         'max-age' => $maxage,
222                         's-maxage' => $maxage
223                 ) );
224         }
225
226         /**
227          * Set the type of caching headers which will be sent.
228          *
229          * @param $mode One of:
230          *    - 'public':     Cache this object in public caches, if the maxage or smaxage 
231          *         parameter is set, or if setCacheMaxAge() was called. If a maximum age is
232          *         not provided by any of these means, the object will be private.
233          *    - 'private':    Cache this object only in private client-side caches.
234          *    - 'anon-public-user-private': Make this object cacheable for logged-out
235          *         users, but private for logged-in users. IMPORTANT: If this is set, it must be 
236          *         set consistently for a given URL, it cannot be set differently depending on 
237          *         things like the contents of the database, or whether the user is logged in.
238          *
239          *  If the wiki does not allow anonymous users to read it, the mode set here
240          *  will be ignored, and private caching headers will always be sent. In other words, 
241          *  the "public" mode is equivalent to saying that the data sent is as public as a page
242          *  view.
243          *
244          *  For user-dependent data, the private mode should generally be used. The 
245          *  anon-public-user-private mode should only be used where there is a particularly 
246          *  good performance reason for caching the anonymous response, but where the
247          *  response to logged-in users may differ, or may contain private data. 
248          *
249          *  If this function is never called, then the default will be the private mode.
250          */
251         public function setCacheMode( $mode ) {
252                 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
253                         wfDebug( __METHOD__.": unrecognised cache mode \"$mode\"\n" );
254                         // Ignore for forwards-compatibility
255                         return;
256                 }
257
258                 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
259                         // Private wiki, only private headers
260                         if ( $mode !== 'private' ) {
261                                 wfDebug( __METHOD__.": ignoring request for $mode cache mode, private wiki\n" );
262                                 return;
263                         }
264                 }
265
266                 wfDebug( __METHOD__.": setting cache mode $mode\n" );
267                 $this->mCacheMode = $mode;
268         }
269         
270         /**
271          * @deprecated Private caching is now the default, so there is usually no 
272          * need to call this function. If there is a need, you can use 
273          * $this->setCacheMode('private')
274          */
275         public function setCachePrivate() {
276                 $this->setCacheMode( 'private' );
277         }
278
279         /**
280          * Set directives (key/value pairs) for the Cache-Control header.
281          * Boolean values will be formatted as such, by including or omitting
282          * without an equals sign.
283          *
284          * Cache control values set here will only be used if the cache mode is not 
285          * private, see setCacheMode().
286          */
287         public function setCacheControl( $directives ) {
288                 $this->mCacheControl = $directives + $this->mCacheControl;
289         }
290         
291         /**
292          * Make sure Vary: Cookie and friends are set. Use this when the output of a request
293          * may be cached for anons but may not be cached for logged-in users.
294          *
295          * WARNING: This function must be called CONSISTENTLY for a given URL. This means that a
296          * given URL must either always or never call this function; if it sometimes does and
297          * sometimes doesn't, stuff will break.
298          *
299          * @deprecated Use setCacheMode( 'anon-public-user-private' )
300          */
301         public function setVaryCookie() {
302                 $this->setCacheMode( 'anon-public-user-private' );
303         }
304
305         /**
306          * Create an instance of an output formatter by its name
307          */
308         public function createPrinterByName( $format ) {
309                 if ( !isset( $this->mFormats[$format] ) )
310                         $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
311                 return new $this->mFormats[$format] ( $this, $format );
312         }
313
314         /**
315          * Execute api request. Any errors will be handled if the API was called by the remote client.
316          */
317         public function execute() {
318                 $this->profileIn();
319                 if ( $this->mInternalMode )
320                         $this->executeAction();
321                 else
322                         $this->executeActionWithErrorHandling();
323
324                 $this->profileOut();
325         }
326
327         /**
328          * Execute an action, and in case of an error, erase whatever partial results
329          * have been accumulated, and replace it with an error message and a help screen.
330          */
331         protected function executeActionWithErrorHandling() {
332
333                 // In case an error occurs during data output,
334                 // clear the output buffer and print just the error information
335                 ob_start();
336
337                 try {
338                         $this->executeAction();
339                 } catch ( Exception $e ) {
340                         // Log it
341                         if ( $e instanceof MWException ) {
342                                 wfDebugLog( 'exception', $e->getLogMessage() );
343                         }
344
345                         //
346                         // Handle any kind of exception by outputing properly formatted error message.
347                         // If this fails, an unhandled exception should be thrown so that global error
348                         // handler will process and log it.
349                         //
350
351                         $errCode = $this->substituteResultWithError( $e );
352
353                         // Error results should not be cached
354                         $this->setCacheMode( 'private' );
355
356                         $headerStr = 'MediaWiki-API-Error: ' . $errCode;
357                         if ( $e->getCode() === 0 )
358                                 header( $headerStr );
359                         else
360                                 header( $headerStr, true, $e->getCode() );
361
362                         // Reset and print just the error message
363                         ob_clean();
364
365                         // If the error occured during printing, do a printer->profileOut()
366                         $this->mPrinter->safeProfileOut();
367                         $this->printResult( true );
368                 }
369
370                 // Send cache headers after any code which might generate an error, to 
371                 // avoid sending public cache headers for errors.
372                 $this->sendCacheHeaders();
373
374                 if ( $this->mPrinter->getIsHtml() ) {
375                         echo wfReportTime();
376                 }
377
378                 ob_end_flush();
379         }
380
381         protected function sendCacheHeaders() {
382                 if ( $this->mCacheMode == 'private' ) {
383                         header( 'Cache-Control: private' );
384                         return;
385                 }
386
387                 if ( $this->mCacheMode == 'anon-public-user-private' ) {
388                         global $wgUseXVO, $wgOut;
389                         header( 'Vary: Accept-Encoding, Cookie' );
390                         if ( $wgUseXVO ) {
391                                 header( $wgOut->getXVO() );
392                                 if ( $wgOut->haveCacheVaryCookies() ) {
393                                         // Logged in, mark this request private
394                                         header( 'Cache-Control: private' );
395                                         return;
396                                 }
397                                 // Logged out, send normal public headers below
398                         } elseif ( session_id() != '' ) {
399                                 // Logged in or otherwise has session (e.g. anonymous users who have edited)
400                                 // Mark request private
401                                 header( 'Cache-Control: private' );
402                                 return;
403                         } // else no XVO and anonymous, send public headers below
404                 } else /* if public */ {
405                         // Give a debugging message if the user object is unstubbed on a public request
406                         global $wgUser;
407                         if ( !( $wgUser instanceof StubUser ) ) {
408                                 wfDebug( __METHOD__." \$wgUser is unstubbed on a public request!\n" );
409                         }
410                 }
411
412                 // If nobody called setCacheMaxAge(), use the (s)maxage parameters
413                 if ( !isset( $this->mCacheControl['s-maxage'] ) ) {
414                         $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' );
415                 }
416                 if ( !isset( $this->mCacheControl['max-age'] ) ) {
417                         $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' );
418                 }
419
420                 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) {
421                         // Public cache not requested
422                         // Sending a Vary header in this case is harmless, and protects us
423                         // against conditional calls of setCacheMaxAge().
424                         header( 'Cache-Control: private' );
425                         return;
426                 }
427
428                 $this->mCacheControl['public'] = true;
429
430                 // Send an Expires header
431                 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
432                 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
433                 header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
434
435                 // Construct the Cache-Control header
436                 $ccHeader = '';
437                 $separator = '';
438                 foreach ( $this->mCacheControl as $name => $value ) {
439                         if ( is_bool( $value ) ) {
440                                 if ( $value ) {
441                                         $ccHeader .= $separator . $name;
442                                         $separator = ', ';
443                                 }
444                         } else {
445                                 $ccHeader .= $separator . "$name=$value";
446                                 $separator = ', ';
447                         }
448                 }
449                         
450                 header( "Cache-Control: $ccHeader" );
451         }
452
453         /**
454          * Replace the result data with the information about an exception.
455          * Returns the error code
456          */
457         protected function substituteResultWithError( $e ) {
458
459                 // Printer may not be initialized if the extractRequestParams() fails for the main module
460                 if ( !isset ( $this->mPrinter ) ) {
461                         // The printer has not been created yet. Try to manually get formatter value.
462                         $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
463                         if ( !in_array( $value, $this->mFormatNames ) )
464                                 $value = self::API_DEFAULT_FORMAT;
465
466                         $this->mPrinter = $this->createPrinterByName( $value );
467                         if ( $this->mPrinter->getNeedsRawData() )
468                                 $this->getResult()->setRawMode();
469                 }
470
471                 if ( $e instanceof UsageException ) {
472                         //
473                         // User entered incorrect parameters - print usage screen
474                         //
475                         $errMessage = $e->getMessageArray();
476
477                         // Only print the help message when this is for the developer, not runtime
478                         if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' )
479                                 ApiResult :: setContent( $errMessage, $this->makeHelpMsg() );
480
481                 } else {
482                         global $wgShowSQLErrors, $wgShowExceptionDetails;
483                         //
484                         // Something is seriously wrong
485                         //
486                         if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
487                                 $info = "Database query error";
488                         } else {
489                                 $info = "Exception Caught: {$e->getMessage()}";
490                         }
491
492                         $errMessage = array (
493                                 'code' => 'internal_api_error_' . get_class( $e ),
494                                 'info' => $info,
495                         );
496                         ApiResult :: setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : "" );
497                 }
498
499                 $this->getResult()->reset();
500                 $this->getResult()->disableSizeCheck();
501                 // Re-add the id
502                 $requestid = $this->getParameter( 'requestid' );
503                 if ( !is_null( $requestid ) )
504                         $this->getResult()->addValue( null, 'requestid', $requestid );
505                 $this->getResult()->addValue( null, 'error', $errMessage );
506
507                 return $errMessage['code'];
508         }
509
510         /**
511          * Execute the actual module, without any error handling
512          */
513         protected function executeAction() {
514                 // First add the id to the top element
515                 $requestid = $this->getParameter( 'requestid' );
516                 if ( !is_null( $requestid ) )
517                         $this->getResult()->addValue( null, 'requestid', $requestid );
518
519                 $params = $this->extractRequestParams();
520
521                 $this->mShowVersions = $params['version'];
522                 $this->mAction = $params['action'];
523
524                 if ( !is_string( $this->mAction ) ) {
525                         $this->dieUsage( "The API requires a valid action parameter", 'unknown_action' );
526                 }
527                 
528                 // Instantiate the module requested by the user
529                 $module = new $this->mModules[$this->mAction] ( $this, $this->mAction );
530                 $this->mModule = $module;
531
532                 $moduleParams = $module->extractRequestParams();
533                 
534                 // Die if token required, but not provided (unless there is a gettoken parameter)
535                 $salt = $module->getTokenSalt();
536                 if ( $salt !== false && !isset( $moduleParams['gettoken'] ) )
537                 {
538                         if ( !isset( $moduleParams['token'] ) ) {
539                                 $this->dieUsageMsg( array( 'missingparam', 'token' ) );
540                         } else {
541                                 global $wgUser;
542                                 if ( !$wgUser->matchEditToken( $moduleParams['token'], $salt ) ) {
543                                         $this->dieUsageMsg( array( 'sessionfailure' ) );
544                                 }
545                         }
546                 }
547
548                 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
549                         // Check for maxlag
550                         global $wgShowHostnames;
551                         $maxLag = $params['maxlag'];
552                         list( $host, $lag ) = wfGetLB()->getMaxLag();
553                         if ( $lag > $maxLag ) {
554                                 header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
555                                 header( 'X-Database-Lag: ' . intval( $lag ) );
556                                 if ( $wgShowHostnames ) {
557                                         $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
558                                 } else {
559                                         $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
560                                 }
561                                 return;
562                         }
563                 }
564
565                 global $wgUser, $wgGroupPermissions;
566                 if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) && !$wgUser->isAllowed( 'read' ) )
567                         $this->dieUsageMsg( array( 'readrequired' ) );
568                 if ( $module->isWriteMode() ) {
569                         if ( !$this->mEnableWrite )
570                                 $this->dieUsageMsg( array( 'writedisabled' ) );
571                         if ( !$wgUser->isAllowed( 'writeapi' ) )
572                                 $this->dieUsageMsg( array( 'writerequired' ) );
573                         if ( wfReadOnly() )
574                                 $this->dieReadOnly();
575                 }
576
577                 if ( !$this->mInternalMode ) {
578                         // Ignore mustBePosted() for internal calls
579                         if ( $module->mustBePosted() && !$this->mRequest->wasPosted() )
580                                 $this->dieUsageMsg( array ( 'mustbeposted', $this->mAction ) );
581
582                         // See if custom printer is used
583                         $this->mPrinter = $module->getCustomPrinter();
584                         if ( is_null( $this->mPrinter ) ) {
585                                 // Create an appropriate printer
586                                 $this->mPrinter = $this->createPrinterByName( $params['format'] );
587                         }
588
589                         if ( $this->mPrinter->getNeedsRawData() )
590                                 $this->getResult()->setRawMode();
591                 }
592
593                 // Execute
594                 $module->profileIn();
595                 $module->execute();
596                 wfRunHooks( 'APIAfterExecute', array( &$module ) );
597                 $module->profileOut();
598
599                 if ( !$this->mInternalMode ) {
600                         // Print result data
601                         $this->printResult( false );
602                 }
603         }
604
605         /**
606          * Print results using the current printer
607          */
608         protected function printResult( $isError ) {
609                 $this->getResult()->cleanUpUTF8();
610                 $printer = $this->mPrinter;
611                 $printer->profileIn();
612
613                 /* If the help message is requested in the default (xmlfm) format,
614                  * tell the printer not to escape ampersands so that our links do
615                  * not break. */
616                 $printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError )
617                                 && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
618
619                 $printer->initPrinter( $isError );
620
621                 $printer->execute();
622                 $printer->closePrinter();
623                 $printer->profileOut();
624         }
625
626         public function isReadMode() {
627                 return false;
628         }
629
630         /**
631          * See ApiBase for description.
632          */
633         public function getAllowedParams() {
634                 return array (
635                         'format' => array (
636                                 ApiBase :: PARAM_DFLT => ApiMain :: API_DEFAULT_FORMAT,
637                                 ApiBase :: PARAM_TYPE => $this->mFormatNames
638                         ),
639                         'action' => array (
640                                 ApiBase :: PARAM_DFLT => 'help',
641                                 ApiBase :: PARAM_TYPE => $this->mModuleNames
642                         ),
643                         'version' => false,
644                         'maxlag'  => array (
645                                 ApiBase :: PARAM_TYPE => 'integer'
646                         ),
647                         'smaxage' => array (
648                                 ApiBase :: PARAM_TYPE => 'integer',
649                                 ApiBase :: PARAM_DFLT => 0
650                         ),
651                         'maxage' => array (
652                                 ApiBase :: PARAM_TYPE => 'integer',
653                                 ApiBase :: PARAM_DFLT => 0
654                         ),
655                         'requestid' => null,
656                 );
657         }
658
659         /**
660          * See ApiBase for description.
661          */
662         public function getParamDescription() {
663                 return array (
664                         'format' => 'The format of the output',
665                         'action' => 'What action you would like to perform',
666                         'version' => 'When showing help, include version for each module',
667                         'maxlag' => 'Maximum lag',
668                         'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
669                         'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
670                         'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
671                 );
672         }
673
674         /**
675          * See ApiBase for description.
676          */
677         public function getDescription() {
678                 return array (
679                         '',
680                         '',
681                         '******************************************************************',
682                         '**                                                              **',
683                         '**  This is an auto-generated MediaWiki API documentation page  **',
684                         '**                                                              **',
685                         '**                  Documentation and Examples:                 **',
686                         '**               http://www.mediawiki.org/wiki/API              **',
687                         '**                                                              **',
688                         '******************************************************************',
689                         '',
690                         'Status:          All features shown on this page should be working, but the API',
691                         '                 is still in active development, and  may change at any time.',
692                         '                 Make sure to monitor our mailing list for any updates.',
693                         '',
694                         'Documentation:   http://www.mediawiki.org/wiki/API',
695                         'Mailing list:    http://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
696                         'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
697                         '',
698                         '',
699                         '',
700                         '',
701                         '',
702                 );
703         }
704
705     public function getPossibleErrors() {
706                 return array_merge( parent::getPossibleErrors(), array(
707                         array( 'readonlytext' ),
708                         array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ),
709                         array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ),
710                         array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ),
711                         array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ),
712         ) );
713         }
714
715         /**
716          * Returns an array of strings with credits for the API
717          */
718         protected function getCredits() {
719                 return array(
720                         'API developers:',
721                         '    Roan Kattouw <Firstname>.<Lastname>@home.nl (lead developer Sep 2007-present)',
722                         '    Victor Vasiliev - vasilvv at gee mail dot com',
723                         '    Bryan Tong Minh - bryan . tongminh @ gmail . com',
724                         '    Sam Reed - sam @ reedyboy . net',
725                         '    Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)',
726                         '',
727                         'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
728                         'or file a bug report at http://bugzilla.wikimedia.org/'
729                 );
730         }
731
732         /**
733          * Override the parent to generate help messages for all available modules.
734          */
735         public function makeHelpMsg() {
736                 global $wgMemc, $wgAPICacheHelp, $wgAPICacheHelpTimeout;
737                 $this->mPrinter->setHelp();
738                 // Get help text from cache if present
739                 $key = wfMemcKey( 'apihelp', $this->getModuleName(),
740                         SpecialVersion::getVersion( 'nodb' ) .
741                         $this->getMain()->getShowVersions() );
742                 if ( $wgAPICacheHelp ) {
743                         $cached = $wgMemc->get( $key );
744                         if ( $cached )
745                                 return $cached;
746                 }
747                 $retval = $this->reallyMakeHelpMsg();
748                 if ( $wgAPICacheHelp )
749                         $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
750                 return $retval;
751         }
752
753         public function reallyMakeHelpMsg() {
754
755                 $this->mPrinter->setHelp();
756
757                 // Use parent to make default message for the main module
758                 $msg = parent :: makeHelpMsg();
759
760                 $astriks = str_repeat( '*** ', 10 );
761                 $msg .= "\n\n$astriks Modules  $astriks\n\n";
762                 foreach ( $this->mModules as $moduleName => $unused ) {
763                         $module = new $this->mModules[$moduleName] ( $this, $moduleName );
764                         $msg .= self::makeHelpMsgHeader( $module, 'action' );
765                         $msg2 = $module->makeHelpMsg();
766                         if ( $msg2 !== false )
767                                 $msg .= $msg2;
768                         $msg .= "\n";
769                 }
770
771                 $msg .= "\n$astriks Permissions $astriks\n\n";
772                 foreach ( self :: $mRights as $right => $rightMsg ) {
773                         $groups = User::getGroupsWithPermission( $right );
774                         $msg .= "* " . $right . " *\n  " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
775                                                 "\nGranted to:\n  " . str_replace( "*", "all", implode( ", ", $groups ) ) . "\n";
776
777                 }
778
779                 $msg .= "\n$astriks Formats  $astriks\n\n";
780                 foreach ( $this->mFormats as $formatName => $unused ) {
781                         $module = $this->createPrinterByName( $formatName );
782                         $msg .= self::makeHelpMsgHeader( $module, 'format' );
783                         $msg2 = $module->makeHelpMsg();
784                         if ( $msg2 !== false )
785                                 $msg .= $msg2;
786                         $msg .= "\n";
787                 }
788
789                 $msg .= "\n*** Credits: ***\n   " . implode( "\n   ", $this->getCredits() ) . "\n";
790
791
792                 return $msg;
793         }
794
795         public static function makeHelpMsgHeader( $module, $paramName ) {
796                 $modulePrefix = $module->getModulePrefix();
797                 if ( strval( $modulePrefix ) !== '' )
798                         $modulePrefix = "($modulePrefix) ";
799
800                 return "* $paramName={$module->getModuleName()} $modulePrefix*";
801         }
802
803         private $mIsBot = null;
804         private $mIsSysop = null;
805         private $mCanApiHighLimits = null;
806
807         /**
808          * Returns true if the currently logged in user is a bot, false otherwise
809          * OBSOLETE, use canApiHighLimits() instead
810          */
811         public function isBot() {
812                 if ( !isset ( $this->mIsBot ) ) {
813                         global $wgUser;
814                         $this->mIsBot = $wgUser->isAllowed( 'bot' );
815                 }
816                 return $this->mIsBot;
817         }
818
819         /**
820          * Similar to isBot(), this method returns true if the logged in user is
821          * a sysop, and false if not.
822          * OBSOLETE, use canApiHighLimits() instead
823          */
824         public function isSysop() {
825                 if ( !isset ( $this->mIsSysop ) ) {
826                         global $wgUser;
827                         $this->mIsSysop = in_array( 'sysop', $wgUser->getGroups() );
828                 }
829
830                 return $this->mIsSysop;
831         }
832
833         /**
834          * Check whether the current user is allowed to use high limits
835          * @return bool
836          */
837         public function canApiHighLimits() {
838                 if ( !isset( $this->mCanApiHighLimits ) ) {
839                         global $wgUser;
840                         $this->mCanApiHighLimits = $wgUser->isAllowed( 'apihighlimits' );
841                 }
842
843                 return $this->mCanApiHighLimits;
844         }
845
846         /**
847          * Check whether the user wants us to show version information in the API help
848          * @return bool
849          */
850         public function getShowVersions() {
851                 return $this->mShowVersions;
852         }
853
854         /**
855          * Returns the version information of this file, plus it includes
856          * the versions for all files that are not callable proper API modules
857          */
858         public function getVersion() {
859                 $vers = array ();
860                 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n    http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
861                 $vers[] = __CLASS__ . ': $Id: ApiMain.php 70066 2010-07-28 05:52:32Z tstarling $';
862                 $vers[] = ApiBase :: getBaseVersion();
863                 $vers[] = ApiFormatBase :: getBaseVersion();
864                 $vers[] = ApiQueryBase :: getBaseVersion();
865                 return $vers;
866         }
867
868         /**
869          * Add or overwrite a module in this ApiMain instance. Intended for use by extending
870          * classes who wish to add their own modules to their lexicon or override the
871          * behavior of inherent ones.
872          *
873          * @access protected
874          * @param $mdlName String The identifier for this module.
875          * @param $mdlClass String The class where this module is implemented.
876          */
877         protected function addModule( $mdlName, $mdlClass ) {
878                 $this->mModules[$mdlName] = $mdlClass;
879         }
880
881         /**
882          * Add or overwrite an output format for this ApiMain. Intended for use by extending
883          * classes who wish to add to or modify current formatters.
884          *
885          * @access protected
886          * @param $fmtName The identifier for this format.
887          * @param $fmtClass The class implementing this format.
888          */
889         protected function addFormat( $fmtName, $fmtClass ) {
890                 $this->mFormats[$fmtName] = $fmtClass;
891         }
892
893         /**
894          * Get the array mapping module names to class names
895          */
896         function getModules() {
897                 return $this->mModules;
898         }
899 }
900
901 /**
902  * This exception will be thrown when dieUsage is called to stop module execution.
903  * The exception handling code will print a help screen explaining how this API may be used.
904  *
905  * @ingroup API
906  */
907 class UsageException extends Exception {
908
909         private $mCodestr;
910         private $mExtraData;
911
912         public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
913                 parent :: __construct( $message, $code );
914                 $this->mCodestr = $codestr;
915                 $this->mExtraData = $extradata;
916         }
917         public function getCodeString() {
918                 return $this->mCodestr;
919         }
920         public function getMessageArray() {
921                 $result = array (
922                                 'code' => $this->mCodestr,
923                                 'info' => $this->getMessage()
924                 );
925                 if ( is_array( $this->mExtraData ) )
926                         $result = array_merge( $result, $this->mExtraData );
927                 return $result;
928         }
929         public function __toString() {
930                 return "{$this->getCodeString()}: {$this->getMessage()}";
931         }
932 }