Completed
Branch master (227f0c)
by
unknown
30:54
created

ApiQuery::doExport()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 23
nc 8
nop 2
dl 0
loc 38
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 *
4
 *
5
 * Created on Sep 7, 2006
6
 *
7
 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
/**
28
 * This is the main query class. It behaves similar to ApiMain: based on the
29
 * parameters given, it will create a list of titles to work on (an ApiPageSet
30
 * object), instantiate and execute various property/list/meta modules, and
31
 * assemble all resulting data into a single ApiResult object.
32
 *
33
 * In generator mode, a generator will be executed first to populate a second
34
 * ApiPageSet object, and that object will be used for all subsequent modules.
35
 *
36
 * @ingroup API
37
 */
38
class ApiQuery extends ApiBase {
39
40
	/**
41
	 * List of Api Query prop modules
42
	 * @var array
43
	 */
44
	private static $QueryPropModules = [
45
		'categories' => 'ApiQueryCategories',
46
		'categoryinfo' => 'ApiQueryCategoryInfo',
47
		'contributors' => 'ApiQueryContributors',
48
		'deletedrevisions' => 'ApiQueryDeletedRevisions',
49
		'duplicatefiles' => 'ApiQueryDuplicateFiles',
50
		'extlinks' => 'ApiQueryExternalLinks',
51
		'fileusage' => 'ApiQueryBacklinksprop',
52
		'images' => 'ApiQueryImages',
53
		'imageinfo' => 'ApiQueryImageInfo',
54
		'info' => 'ApiQueryInfo',
55
		'links' => 'ApiQueryLinks',
56
		'linkshere' => 'ApiQueryBacklinksprop',
57
		'iwlinks' => 'ApiQueryIWLinks',
58
		'langlinks' => 'ApiQueryLangLinks',
59
		'pageprops' => 'ApiQueryPageProps',
60
		'redirects' => 'ApiQueryBacklinksprop',
61
		'revisions' => 'ApiQueryRevisions',
62
		'stashimageinfo' => 'ApiQueryStashImageInfo',
63
		'templates' => 'ApiQueryLinks',
64
		'transcludedin' => 'ApiQueryBacklinksprop',
65
	];
66
67
	/**
68
	 * List of Api Query list modules
69
	 * @var array
70
	 */
71
	private static $QueryListModules = [
72
		'allcategories' => 'ApiQueryAllCategories',
73
		'alldeletedrevisions' => 'ApiQueryAllDeletedRevisions',
74
		'allfileusages' => 'ApiQueryAllLinks',
75
		'allimages' => 'ApiQueryAllImages',
76
		'alllinks' => 'ApiQueryAllLinks',
77
		'allpages' => 'ApiQueryAllPages',
78
		'allredirects' => 'ApiQueryAllLinks',
79
		'allrevisions' => 'ApiQueryAllRevisions',
80
		'mystashedfiles' => 'ApiQueryMyStashedFiles',
81
		'alltransclusions' => 'ApiQueryAllLinks',
82
		'allusers' => 'ApiQueryAllUsers',
83
		'backlinks' => 'ApiQueryBacklinks',
84
		'blocks' => 'ApiQueryBlocks',
85
		'categorymembers' => 'ApiQueryCategoryMembers',
86
		'deletedrevs' => 'ApiQueryDeletedrevs',
87
		'embeddedin' => 'ApiQueryBacklinks',
88
		'exturlusage' => 'ApiQueryExtLinksUsage',
89
		'filearchive' => 'ApiQueryFilearchive',
90
		'imageusage' => 'ApiQueryBacklinks',
91
		'iwbacklinks' => 'ApiQueryIWBacklinks',
92
		'langbacklinks' => 'ApiQueryLangBacklinks',
93
		'logevents' => 'ApiQueryLogEvents',
94
		'pageswithprop' => 'ApiQueryPagesWithProp',
95
		'pagepropnames' => 'ApiQueryPagePropNames',
96
		'prefixsearch' => 'ApiQueryPrefixSearch',
97
		'protectedtitles' => 'ApiQueryProtectedTitles',
98
		'querypage' => 'ApiQueryQueryPage',
99
		'random' => 'ApiQueryRandom',
100
		'recentchanges' => 'ApiQueryRecentChanges',
101
		'search' => 'ApiQuerySearch',
102
		'tags' => 'ApiQueryTags',
103
		'usercontribs' => 'ApiQueryContributions',
104
		'users' => 'ApiQueryUsers',
105
		'watchlist' => 'ApiQueryWatchlist',
106
		'watchlistraw' => 'ApiQueryWatchlistRaw',
107
	];
108
109
	/**
110
	 * List of Api Query meta modules
111
	 * @var array
112
	 */
113
	private static $QueryMetaModules = [
114
		'allmessages' => 'ApiQueryAllMessages',
115
		'authmanagerinfo' => 'ApiQueryAuthManagerInfo',
116
		'siteinfo' => 'ApiQuerySiteinfo',
117
		'userinfo' => 'ApiQueryUserInfo',
118
		'filerepoinfo' => 'ApiQueryFileRepoInfo',
119
		'tokens' => 'ApiQueryTokens',
120
	];
121
122
	/**
123
	 * @var ApiPageSet
124
	 */
125
	private $mPageSet;
126
127
	private $mParams;
128
	private $mNamedDB = [];
129
	private $mModuleMgr;
130
131
	/**
132
	 * @param ApiMain $main
133
	 * @param string $action
134
	 */
135
	public function __construct( ApiMain $main, $action ) {
136
		parent::__construct( $main, $action );
137
138
		$this->mModuleMgr = new ApiModuleManager( $this );
139
140
		// Allow custom modules to be added in LocalSettings.php
141
		$config = $this->getConfig();
142
		$this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
143
		$this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' );
144
		$this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
145
		$this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' );
146
		$this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
147
		$this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
148
149
		Hooks::run( 'ApiQuery::moduleManager', [ $this->mModuleMgr ] );
150
151
		// Create PageSet that will process titles/pageids/revids/generator
152
		$this->mPageSet = new ApiPageSet( $this );
153
	}
154
155
	/**
156
	 * Overrides to return this instance's module manager.
157
	 * @return ApiModuleManager
158
	 */
159
	public function getModuleManager() {
160
		return $this->mModuleMgr;
161
	}
162
163
	/**
164
	 * Get the query database connection with the given name.
165
	 * If no such connection has been requested before, it will be created.
166
	 * Subsequent calls with the same $name will return the same connection
167
	 * as the first, regardless of the values of $db and $groups
168
	 * @param string $name Name to assign to the database connection
169
	 * @param int $db One of the DB_* constants
170
	 * @param array $groups Query groups
171
	 * @return DatabaseBase
172
	 */
173
	public function getNamedDB( $name, $db, $groups ) {
174
		if ( !array_key_exists( $name, $this->mNamedDB ) ) {
175
			$this->mNamedDB[$name] = wfGetDB( $db, $groups );
176
		}
177
178
		return $this->mNamedDB[$name];
179
	}
180
181
	/**
182
	 * Gets the set of pages the user has requested (or generated)
183
	 * @return ApiPageSet
184
	 */
185
	public function getPageSet() {
186
		return $this->mPageSet;
187
	}
188
189
	/**
190
	 * @return ApiFormatRaw|null
191
	 */
192
	public function getCustomPrinter() {
193
		// If &exportnowrap is set, use the raw formatter
194
		if ( $this->getParameter( 'export' ) &&
195
			$this->getParameter( 'exportnowrap' )
196
		) {
197
			return new ApiFormatRaw( $this->getMain(),
198
				$this->getMain()->createPrinterByName( 'xml' ) );
199
		} else {
200
			return null;
201
		}
202
	}
203
204
	/**
205
	 * Query execution happens in the following steps:
206
	 * #1 Create a PageSet object with any pages requested by the user
207
	 * #2 If using a generator, execute it to get a new ApiPageSet object
208
	 * #3 Instantiate all requested modules.
209
	 *    This way the PageSet object will know what shared data is required,
210
	 *    and minimize DB calls.
211
	 * #4 Output all normalization and redirect resolution information
212
	 * #5 Execute all requested modules
213
	 */
214
	public function execute() {
215
		$this->mParams = $this->extractRequestParams();
216
217
		// Instantiate requested modules
218
		$allModules = [];
219
		$this->instantiateModules( $allModules, 'prop' );
220
		$propModules = array_keys( $allModules );
221
		$this->instantiateModules( $allModules, 'list' );
222
		$this->instantiateModules( $allModules, 'meta' );
223
224
		// Filter modules based on continue parameter
225
		$continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
226
		$this->setContinuationManager( $continuationManager );
227
		$modules = $continuationManager->getRunModules();
228
229
		if ( !$continuationManager->isGeneratorDone() ) {
230
			// Query modules may optimize data requests through the $this->getPageSet()
231
			// object by adding extra fields from the page table.
232
			foreach ( $modules as $module ) {
233
				$module->requestExtraData( $this->mPageSet );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ApiBase as the method requestExtraData() does only exist in the following sub-classes of ApiBase: ApiQueryAllCategories, ApiQueryAllDeletedRevisions, ApiQueryAllImages, ApiQueryAllLinks, ApiQueryAllMessages, ApiQueryAllPages, ApiQueryAllRevisions, ApiQueryAllUsers, ApiQueryAuthManagerInfo, ApiQueryBacklinks, ApiQueryBacklinksprop, ApiQueryBase, ApiQueryBlocks, ApiQueryCategories, ApiQueryCategoryInfo, ApiQueryCategoryMembers, ApiQueryContributions, ApiQueryContributors, ApiQueryDeletedRevisions, ApiQueryDeletedrevs, ApiQueryDisabled, ApiQueryDuplicateFiles, ApiQueryExtLinksUsage, ApiQueryExternalLinks, ApiQueryFileRepoInfo, ApiQueryFilearchive, ApiQueryGeneratorBase, ApiQueryIWBacklinks, ApiQueryIWLinks, ApiQueryImageInfo, ApiQueryImages, ApiQueryInfo, ApiQueryLangBacklinks, ApiQueryLangLinks, ApiQueryLinks, ApiQueryLogEvents, ApiQueryMyStashedFiles, ApiQueryPagePropNames, ApiQueryPageProps, ApiQueryPagesWithProp, ApiQueryPrefixSearch, ApiQueryProtectedTitles, ApiQueryQueryPage, ApiQueryRandom, ApiQueryRecentChanges, ApiQueryRevisions, ApiQueryRevisionsBase, ApiQuerySearch, ApiQuerySiteinfo, ApiQueryStashImageInfo, ApiQueryTags, ApiQueryTokens, ApiQueryUserInfo, ApiQueryUsers, ApiQueryWatchlist, ApiQueryWatchlistRaw. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
234
			}
235
			// Populate page/revision information
236
			$this->mPageSet->execute();
237
			// Record page information (title, namespace, if exists, etc)
238
			$this->outputGeneralPageInfo();
239
		} else {
240
			$this->mPageSet->executeDryRun();
241
		}
242
243
		$cacheMode = $this->mPageSet->getCacheMode();
244
245
		// Execute all unfinished modules
246
		/** @var $module ApiQueryBase */
247
		foreach ( $modules as $module ) {
248
			$params = $module->extractRequestParams();
249
			$cacheMode = $this->mergeCacheMode(
250
				$cacheMode, $module->getCacheMode( $params ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ApiBase as the method getCacheMode() does only exist in the following sub-classes of ApiBase: ApiPageSet, ApiQueryAllCategories, ApiQueryAllDeletedRevisions, ApiQueryAllImages, ApiQueryAllLinks, ApiQueryAllMessages, ApiQueryAllPages, ApiQueryAllRevisions, ApiQueryAllUsers, ApiQueryAuthManagerInfo, ApiQueryBacklinks, ApiQueryBacklinksprop, ApiQueryBase, ApiQueryBlocks, ApiQueryCategories, ApiQueryCategoryInfo, ApiQueryCategoryMembers, ApiQueryContributions, ApiQueryContributors, ApiQueryDeletedRevisions, ApiQueryDeletedrevs, ApiQueryDisabled, ApiQueryDuplicateFiles, ApiQueryExtLinksUsage, ApiQueryExternalLinks, ApiQueryFileRepoInfo, ApiQueryFilearchive, ApiQueryGeneratorBase, ApiQueryIWBacklinks, ApiQueryIWLinks, ApiQueryImageInfo, ApiQueryImages, ApiQueryInfo, ApiQueryLangBacklinks, ApiQueryLangLinks, ApiQueryLinks, ApiQueryLogEvents, ApiQueryMyStashedFiles, ApiQueryPagePropNames, ApiQueryPageProps, ApiQueryPagesWithProp, ApiQueryPrefixSearch, ApiQueryProtectedTitles, ApiQueryQueryPage, ApiQueryRandom, ApiQueryRecentChanges, ApiQueryRevisions, ApiQueryRevisionsBase, ApiQuerySearch, ApiQuerySiteinfo, ApiQueryStashImageInfo, ApiQueryTags, ApiQueryTokens, ApiQueryUserInfo, ApiQueryUsers, ApiQueryWatchlist, ApiQueryWatchlistRaw. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
251
			$module->execute();
252
			Hooks::run( 'APIQueryAfterExecute', [ &$module ] );
253
		}
254
255
		// Set the cache mode
256
		$this->getMain()->setCacheMode( $cacheMode );
257
258
		// Write the continuation data into the result
259
		$this->setContinuationManager( null );
260 View Code Duplication
		if ( $this->mParams['rawcontinue'] ) {
261
			$data = $continuationManager->getRawContinuation();
262
			if ( $data ) {
263
				$this->getResult()->addValue( null, 'query-continue', $data,
264
					ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
265
			}
266
		} else {
267
			$continuationManager->setContinuationIntoResult( $this->getResult() );
268
		}
269
	}
270
271
	/**
272
	 * Update a cache mode string, applying the cache mode of a new module to it.
273
	 * The cache mode may increase in the level of privacy, but public modules
274
	 * added to private data do not decrease the level of privacy.
275
	 *
276
	 * @param string $cacheMode
277
	 * @param string $modCacheMode
278
	 * @return string
279
	 */
280
	protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
281
		if ( $modCacheMode === 'anon-public-user-private' ) {
282
			if ( $cacheMode !== 'private' ) {
283
				$cacheMode = 'anon-public-user-private';
284
			}
285
		} elseif ( $modCacheMode === 'public' ) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
286
			// do nothing, if it's public already it will stay public
287
		} else { // private
288
			$cacheMode = 'private';
289
		}
290
291
		return $cacheMode;
292
	}
293
294
	/**
295
	 * Create instances of all modules requested by the client
296
	 * @param array $modules To append instantiated modules to
297
	 * @param string $param Parameter name to read modules from
298
	 */
299
	private function instantiateModules( &$modules, $param ) {
300
		$wasPosted = $this->getRequest()->wasPosted();
301
		if ( isset( $this->mParams[$param] ) ) {
302
			foreach ( $this->mParams[$param] as $moduleName ) {
303
				$instance = $this->mModuleMgr->getModule( $moduleName, $param );
304
				if ( $instance === null ) {
305
					ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
306
				}
307
				if ( !$wasPosted && $instance->mustBePosted() ) {
308
					$this->dieUsageMsgOrDebug( [ 'mustbeposted', $moduleName ] );
309
				}
310
				// Ignore duplicates. TODO 2.0: die()?
311
				if ( !array_key_exists( $moduleName, $modules ) ) {
312
					$modules[$moduleName] = $instance;
313
				}
314
			}
315
		}
316
	}
317
318
	/**
319
	 * Appends an element for each page in the current pageSet with the
320
	 * most general information (id, title), plus any title normalizations
321
	 * and missing or invalid title/pageids/revids.
322
	 */
323
	private function outputGeneralPageInfo() {
324
		$pageSet = $this->getPageSet();
325
		$result = $this->getResult();
326
327
		// We can't really handle max-result-size failure here, but we need to
328
		// check anyway in case someone set the limit stupidly low.
329
		$fit = true;
330
331
		$values = $pageSet->getNormalizedTitlesAsResult( $result );
332
		if ( $values ) {
333
			$fit = $fit && $result->addValue( 'query', 'normalized', $values );
334
		}
335
		$values = $pageSet->getConvertedTitlesAsResult( $result );
336
		if ( $values ) {
337
			$fit = $fit && $result->addValue( 'query', 'converted', $values );
338
		}
339
		$values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
340
		if ( $values ) {
341
			$fit = $fit && $result->addValue( 'query', 'interwiki', $values );
342
		}
343
		$values = $pageSet->getRedirectTitlesAsResult( $result );
344
		if ( $values ) {
345
			$fit = $fit && $result->addValue( 'query', 'redirects', $values );
346
		}
347
		$values = $pageSet->getMissingRevisionIDsAsResult( $result );
348
		if ( $values ) {
349
			$fit = $fit && $result->addValue( 'query', 'badrevids', $values );
350
		}
351
352
		// Page elements
353
		$pages = [];
354
355
		// Report any missing titles
356 View Code Duplication
		foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
357
			$vals = [];
358
			ApiQueryBase::addTitleInfo( $vals, $title );
359
			$vals['missing'] = true;
360
			$pages[$fakeId] = $vals;
361
		}
362
		// Report any invalid titles
363
		foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
364
			$pages[$fakeId] = $data + [ 'invalid' => true ];
365
		}
366
		// Report any missing page ids
367
		foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
368
			$pages[$pageid] = [
369
				'pageid' => $pageid,
370
				'missing' => true
371
			];
372
		}
373
		// Report special pages
374
		/** @var $title Title */
375
		foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
376
			$vals = [];
377
			ApiQueryBase::addTitleInfo( $vals, $title );
378
			$vals['special'] = true;
379
			if ( $title->isSpecialPage() &&
380
				!SpecialPageFactory::exists( $title->getDBkey() )
381
			) {
382
				$vals['missing'] = true;
383
			} elseif ( $title->getNamespace() == NS_MEDIA &&
384
				!wfFindFile( $title )
385
			) {
386
				$vals['missing'] = true;
387
			}
388
			$pages[$fakeId] = $vals;
389
		}
390
391
		// Output general page information for found titles
392 View Code Duplication
		foreach ( $pageSet->getGoodTitles() as $pageid => $title ) {
393
			$vals = [];
394
			$vals['pageid'] = $pageid;
395
			ApiQueryBase::addTitleInfo( $vals, $title );
396
			$pages[$pageid] = $vals;
397
		}
398
399
		if ( count( $pages ) ) {
400
			$pageSet->populateGeneratorData( $pages );
401
			ApiResult::setArrayType( $pages, 'BCarray' );
402
403
			if ( $this->mParams['indexpageids'] ) {
404
				$pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
405
				// json treats all map keys as strings - converting to match
406
				$pageIDs = array_map( 'strval', $pageIDs );
407
				ApiResult::setIndexedTagName( $pageIDs, 'id' );
408
				$fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
409
			}
410
411
			ApiResult::setIndexedTagName( $pages, 'page' );
412
			$fit = $fit && $result->addValue( 'query', 'pages', $pages );
413
		}
414
415
		if ( !$fit ) {
416
			$this->dieUsage(
417
				'The value of $wgAPIMaxResultSize on this wiki is ' .
418
					'too small to hold basic result information',
419
				'badconfig'
420
			);
421
		}
422
423
		if ( $this->mParams['export'] ) {
424
			$this->doExport( $pageSet, $result );
425
		}
426
	}
427
428
	/**
429
	 * @param ApiPageSet $pageSet Pages to be exported
430
	 * @param ApiResult $result Result to output to
431
	 */
432
	private function doExport( $pageSet, $result ) {
433
		$exportTitles = [];
434
		$titles = $pageSet->getGoodTitles();
435
		if ( count( $titles ) ) {
436
			$user = $this->getUser();
437
			/** @var $title Title */
438
			foreach ( $titles as $title ) {
439
				if ( $title->userCan( 'read', $user ) ) {
440
					$exportTitles[] = $title;
441
				}
442
			}
443
		}
444
445
		$exporter = new WikiExporter( $this->getDB() );
446
		// WikiExporter writes to stdout, so catch its
447
		// output with an ob
448
		ob_start();
449
		$exporter->openStream();
450
		foreach ( $exportTitles as $title ) {
451
			$exporter->pageByTitle( $title );
452
		}
453
		$exporter->closeStream();
454
		$exportxml = ob_get_contents();
455
		ob_end_clean();
456
457
		// Don't check the size of exported stuff
458
		// It's not continuable, so it would cause more
459
		// problems than it'd solve
460
		if ( $this->mParams['exportnowrap'] ) {
461
			$result->reset();
462
			// Raw formatter will handle this
463
			$result->addValue( null, 'text', $exportxml, ApiResult::NO_SIZE_CHECK );
464
			$result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
465
		} else {
466
			$result->addValue( 'query', 'export', $exportxml, ApiResult::NO_SIZE_CHECK );
467
			$result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
468
		}
469
	}
470
471
	public function getAllowedParams( $flags = 0 ) {
472
		$result = [
473
			'prop' => [
474
				ApiBase::PARAM_ISMULTI => true,
475
				ApiBase::PARAM_TYPE => 'submodule',
476
			],
477
			'list' => [
478
				ApiBase::PARAM_ISMULTI => true,
479
				ApiBase::PARAM_TYPE => 'submodule',
480
			],
481
			'meta' => [
482
				ApiBase::PARAM_ISMULTI => true,
483
				ApiBase::PARAM_TYPE => 'submodule',
484
			],
485
			'indexpageids' => false,
486
			'export' => false,
487
			'exportnowrap' => false,
488
			'iwurl' => false,
489
			'continue' => [
490
				ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
491
			],
492
			'rawcontinue' => false,
493
		];
494
		if ( $flags ) {
495
			$result += $this->getPageSet()->getFinalParams( $flags );
496
		}
497
498
		return $result;
499
	}
500
501
	/**
502
	 * Override the parent to generate help messages for all available query modules.
503
	 * @deprecated since 1.25
504
	 * @return string
505
	 */
506
	public function makeHelpMsg() {
507
		wfDeprecated( __METHOD__, '1.25' );
508
509
		// Use parent to make default message for the query module
510
		$msg = parent::makeHelpMsg();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::makeHelpMsg() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
511
512
		$querySeparator = str_repeat( '--- ', 12 );
513
		$moduleSeparator = str_repeat( '*** ', 14 );
514
		$msg .= "\n$querySeparator Query: Prop  $querySeparator\n\n";
515
		$msg .= $this->makeHelpMsgHelper( 'prop' );
0 ignored issues
show
Deprecated Code introduced by
The method ApiQuery::makeHelpMsgHelper() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
516
		$msg .= "\n$querySeparator Query: List  $querySeparator\n\n";
517
		$msg .= $this->makeHelpMsgHelper( 'list' );
0 ignored issues
show
Deprecated Code introduced by
The method ApiQuery::makeHelpMsgHelper() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
518
		$msg .= "\n$querySeparator Query: Meta  $querySeparator\n\n";
519
		$msg .= $this->makeHelpMsgHelper( 'meta' );
0 ignored issues
show
Deprecated Code introduced by
The method ApiQuery::makeHelpMsgHelper() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
520
		$msg .= "\n\n$moduleSeparator Modules: continuation  $moduleSeparator\n\n";
521
522
		return $msg;
523
	}
524
525
	/**
526
	 * For all modules of a given group, generate help messages and join them together
527
	 * @deprecated since 1.25
528
	 * @param string $group Module group
529
	 * @return string
530
	 */
531
	private function makeHelpMsgHelper( $group ) {
532
		$moduleDescriptions = [];
533
534
		$moduleNames = $this->mModuleMgr->getNames( $group );
535
		sort( $moduleNames );
536
		foreach ( $moduleNames as $name ) {
537
			/**
538
			 * @var $module ApiQueryBase
539
			 */
540
			$module = $this->mModuleMgr->getModule( $name );
541
542
			$msg = ApiMain::makeHelpMsgHeader( $module, $group );
0 ignored issues
show
Deprecated Code introduced by
The method ApiMain::makeHelpMsgHeader() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
543
			$msg2 = $module->makeHelpMsg();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::makeHelpMsg() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
544
			if ( $msg2 !== false ) {
545
				$msg .= $msg2;
546
			}
547
			if ( $module instanceof ApiQueryGeneratorBase ) {
548
				$msg .= "Generator:\n  This module may be used as a generator\n";
549
			}
550
			$moduleDescriptions[] = $msg;
551
		}
552
553
		return implode( "\n", $moduleDescriptions );
554
	}
555
556
	public function isReadMode() {
557
		// We need to make an exception for certain meta modules that should be
558
		// accessible even without the 'read' right. Restrict the exception as
559
		// much as possible: no other modules allowed, and no pageset
560
		// parameters either. We do allow the 'rawcontinue' and 'indexpageids'
561
		// parameters since frameworks might add these unconditionally and they
562
		// can't expose anything here.
563
		$this->mParams = $this->extractRequestParams();
564
		$params = array_filter(
565
			array_diff_key(
566
				$this->mParams + $this->getPageSet()->extractRequestParams(),
567
				[ 'rawcontinue' => 1, 'indexpageids' => 1 ]
568
			)
569
		);
570
		if ( array_keys( $params ) !== [ 'meta' ] ) {
571
			return true;
572
		}
573
574
		// Ask each module if it requires read mode. Any true => this returns
575
		// true.
576
		$modules = [];
577
		$this->instantiateModules( $modules, 'meta' );
578
		foreach ( $modules as $module ) {
579
			if ( $module->isReadMode() ) {
580
				return true;
581
			}
582
		}
583
584
		return false;
585
	}
586
587
	protected function getExamplesMessages() {
588
		return [
589
			'action=query&prop=revisions&meta=siteinfo&' .
590
				'titles=Main%20Page&rvprop=user|comment&continue='
591
				=> 'apihelp-query-example-revisions',
592
			'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
593
				=> 'apihelp-query-example-allpages',
594
		];
595
	}
596
597
	public function getHelpUrls() {
598
		return [
599
			'https://www.mediawiki.org/wiki/API:Query',
600
			'https://www.mediawiki.org/wiki/API:Meta',
601
			'https://www.mediawiki.org/wiki/API:Properties',
602
			'https://www.mediawiki.org/wiki/API:Lists',
603
		];
604
	}
605
}
606