Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.62% |
612 / 640 |
|
66.67% |
14 / 21 |
CRAP | |
0.00% |
0 / 1 |
ApiParse | |
95.62% |
612 / 640 |
|
66.67% |
14 / 21 |
176 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
1 | |||
getPoolKey | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
getContentParserOutput | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
getUserForPreview | |
50.00% |
3 / 6 |
|
0.00% |
0 / 1 |
2.50 | |||
getPageParserOutput | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
97.89% |
324 / 331 |
|
0.00% |
0 / 1 |
112 | |||
makeParserOptions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
tweakParserOptions | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
getParsedContent | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
15 | |||
getSectionContent | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
3.33 | |||
formatSummary | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
6 | |||
formatLangLinks | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
formatCategoryLinks | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
7 | |||
formatLinks | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
formatIWLinks | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
formatHeadItems | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
2.75 | |||
formatLimitReportData | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
setIndexedTagNames | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getAllowedParams | |
100.00% |
97 / 97 |
|
100.00% |
1 / 1 |
1 | |||
getExamplesMessages | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | use MediaWiki\Cache\LinkBatchFactory; |
24 | use MediaWiki\Cache\LinkCache; |
25 | use MediaWiki\CommentFormatter\CommentFormatter; |
26 | use MediaWiki\Content\IContentHandlerFactory; |
27 | use MediaWiki\Content\Renderer\ContentRenderer; |
28 | use MediaWiki\Content\Transform\ContentTransformer; |
29 | use MediaWiki\EditPage\EditPage; |
30 | use MediaWiki\Language\RawMessage; |
31 | use MediaWiki\Languages\LanguageNameUtils; |
32 | use MediaWiki\Output\OutputPage; |
33 | use MediaWiki\Page\PageReference; |
34 | use MediaWiki\Page\WikiPageFactory; |
35 | use MediaWiki\Parser\Parser; |
36 | use MediaWiki\Parser\ParserOutput; |
37 | use MediaWiki\Parser\ParserOutputFlags; |
38 | use MediaWiki\PoolCounter\PoolCounterWorkViaCallback; |
39 | use MediaWiki\Request\FauxRequest; |
40 | use MediaWiki\Revision\RevisionLookup; |
41 | use MediaWiki\Revision\RevisionRecord; |
42 | use MediaWiki\Revision\SlotRecord; |
43 | use MediaWiki\Title\Title; |
44 | use MediaWiki\Title\TitleFormatter; |
45 | use MediaWiki\User\TempUser\TempUserCreator; |
46 | use MediaWiki\User\UserFactory; |
47 | use MediaWiki\Utils\UrlUtils; |
48 | use MediaWiki\WikiMap\WikiMap; |
49 | use Wikimedia\ParamValidator\ParamValidator; |
50 | use Wikimedia\ParamValidator\TypeDef\EnumDef; |
51 | |
52 | /** |
53 | * @ingroup API |
54 | */ |
55 | class ApiParse extends ApiBase { |
56 | |
57 | /** @var string|false|null */ |
58 | private $section = null; |
59 | |
60 | /** @var Content|null */ |
61 | private $content = null; |
62 | |
63 | /** @var Content|null */ |
64 | private $pstContent = null; |
65 | |
66 | /** @var bool */ |
67 | private $contentIsDeleted = false, $contentIsSuppressed = false; |
68 | |
69 | private RevisionLookup $revisionLookup; |
70 | private SkinFactory $skinFactory; |
71 | private LanguageNameUtils $languageNameUtils; |
72 | private LinkBatchFactory $linkBatchFactory; |
73 | private LinkCache $linkCache; |
74 | private IContentHandlerFactory $contentHandlerFactory; |
75 | private ParserFactory $parserFactory; |
76 | private WikiPageFactory $wikiPageFactory; |
77 | private ContentTransformer $contentTransformer; |
78 | private CommentFormatter $commentFormatter; |
79 | private ContentRenderer $contentRenderer; |
80 | private TempUserCreator $tempUserCreator; |
81 | private UserFactory $userFactory; |
82 | private UrlUtils $urlUtils; |
83 | private TitleFormatter $titleFormatter; |
84 | |
85 | /** |
86 | * @param ApiMain $main |
87 | * @param string $action |
88 | * @param RevisionLookup $revisionLookup |
89 | * @param SkinFactory $skinFactory |
90 | * @param LanguageNameUtils $languageNameUtils |
91 | * @param LinkBatchFactory $linkBatchFactory |
92 | * @param LinkCache $linkCache |
93 | * @param IContentHandlerFactory $contentHandlerFactory |
94 | * @param ParserFactory $parserFactory |
95 | * @param WikiPageFactory $wikiPageFactory |
96 | * @param ContentRenderer $contentRenderer |
97 | * @param ContentTransformer $contentTransformer |
98 | * @param CommentFormatter $commentFormatter |
99 | * @param TempUserCreator $tempUserCreator |
100 | * @param UserFactory $userFactory |
101 | * @param UrlUtils $urlUtils |
102 | * @param TitleFormatter $titleFormatter |
103 | */ |
104 | public function __construct( |
105 | ApiMain $main, |
106 | $action, |
107 | RevisionLookup $revisionLookup, |
108 | SkinFactory $skinFactory, |
109 | LanguageNameUtils $languageNameUtils, |
110 | LinkBatchFactory $linkBatchFactory, |
111 | LinkCache $linkCache, |
112 | IContentHandlerFactory $contentHandlerFactory, |
113 | ParserFactory $parserFactory, |
114 | WikiPageFactory $wikiPageFactory, |
115 | ContentRenderer $contentRenderer, |
116 | ContentTransformer $contentTransformer, |
117 | CommentFormatter $commentFormatter, |
118 | TempUserCreator $tempUserCreator, |
119 | UserFactory $userFactory, |
120 | UrlUtils $urlUtils, |
121 | TitleFormatter $titleFormatter |
122 | ) { |
123 | parent::__construct( $main, $action ); |
124 | $this->revisionLookup = $revisionLookup; |
125 | $this->skinFactory = $skinFactory; |
126 | $this->languageNameUtils = $languageNameUtils; |
127 | $this->linkBatchFactory = $linkBatchFactory; |
128 | $this->linkCache = $linkCache; |
129 | $this->contentHandlerFactory = $contentHandlerFactory; |
130 | $this->parserFactory = $parserFactory; |
131 | $this->wikiPageFactory = $wikiPageFactory; |
132 | $this->contentRenderer = $contentRenderer; |
133 | $this->contentTransformer = $contentTransformer; |
134 | $this->commentFormatter = $commentFormatter; |
135 | $this->tempUserCreator = $tempUserCreator; |
136 | $this->userFactory = $userFactory; |
137 | $this->urlUtils = $urlUtils; |
138 | $this->titleFormatter = $titleFormatter; |
139 | } |
140 | |
141 | private function getPoolKey(): string { |
142 | $poolKey = WikiMap::getCurrentWikiDbDomain() . ':ApiParse:'; |
143 | if ( !$this->getUser()->isRegistered() ) { |
144 | $poolKey .= 'a:' . $this->getUser()->getName(); |
145 | } else { |
146 | $poolKey .= 'u:' . $this->getUser()->getId(); |
147 | } |
148 | return $poolKey; |
149 | } |
150 | |
151 | private function getContentParserOutput( |
152 | Content $content, |
153 | PageReference $page, |
154 | ?RevisionRecord $revision, |
155 | ParserOptions $popts |
156 | ) { |
157 | $worker = new PoolCounterWorkViaCallback( 'ApiParser', $this->getPoolKey(), |
158 | [ |
159 | 'doWork' => function () use ( $content, $page, $revision, $popts ) { |
160 | return $this->contentRenderer->getParserOutput( |
161 | $content, $page, $revision, $popts |
162 | ); |
163 | }, |
164 | 'error' => function () { |
165 | $this->dieWithError( 'apierror-concurrency-limit' ); |
166 | }, |
167 | ] |
168 | ); |
169 | return $worker->execute(); |
170 | } |
171 | |
172 | private function getUserForPreview() { |
173 | $user = $this->getUser(); |
174 | if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) { |
175 | return $this->userFactory->newUnsavedTempUser( |
176 | $this->tempUserCreator->getStashedName( $this->getRequest()->getSession() ) |
177 | ); |
178 | } |
179 | return $user; |
180 | } |
181 | |
182 | private function getPageParserOutput( |
183 | WikiPage $page, |
184 | $revId, |
185 | ParserOptions $popts, |
186 | bool $suppressCache |
187 | ) { |
188 | $worker = new PoolCounterWorkViaCallback( 'ApiParser', $this->getPoolKey(), |
189 | [ |
190 | 'doWork' => static function () use ( $page, $revId, $popts, $suppressCache ) { |
191 | return $page->getParserOutput( $popts, $revId, $suppressCache ); |
192 | }, |
193 | 'error' => function () { |
194 | $this->dieWithError( 'apierror-concurrency-limit' ); |
195 | }, |
196 | ] |
197 | ); |
198 | return $worker->execute(); |
199 | } |
200 | |
201 | public function execute() { |
202 | // The data is hot but user-dependent, like page views, so we set vary cookies |
203 | $this->getMain()->setCacheMode( 'anon-public-user-private' ); |
204 | |
205 | // Get parameters |
206 | $params = $this->extractRequestParams(); |
207 | |
208 | // No easy way to say that text and title or revid are allowed together |
209 | // while the rest aren't, so just do it in three calls. |
210 | $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' ); |
211 | $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' ); |
212 | $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'revid' ); |
213 | |
214 | $text = $params['text']; |
215 | $title = $params['title']; |
216 | if ( $title === null ) { |
217 | $titleProvided = false; |
218 | // A title is needed for parsing, so arbitrarily choose one |
219 | $title = 'API'; |
220 | } else { |
221 | $titleProvided = true; |
222 | } |
223 | |
224 | $page = $params['page']; |
225 | $pageid = $params['pageid']; |
226 | $oldid = $params['oldid']; |
227 | |
228 | $prop = array_fill_keys( $params['prop'], true ); |
229 | |
230 | if ( isset( $params['section'] ) ) { |
231 | $this->section = $params['section']; |
232 | if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) { |
233 | $this->dieWithError( 'apierror-invalidsection' ); |
234 | } |
235 | } else { |
236 | $this->section = false; |
237 | } |
238 | |
239 | // The parser needs $wgTitle to be set, apparently the |
240 | // $title parameter in Parser::parse isn't enough *sigh* |
241 | // TODO: Does this still need $wgTitle? |
242 | global $wgTitle; |
243 | |
244 | $format = null; |
245 | $redirValues = null; |
246 | |
247 | $needContent = isset( $prop['wikitext'] ) || |
248 | isset( $prop['parsetree'] ) || $params['generatexml']; |
249 | |
250 | // Return result |
251 | $result = $this->getResult(); |
252 | |
253 | if ( $oldid !== null || $pageid !== null || $page !== null ) { |
254 | if ( $this->section === 'new' ) { |
255 | $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' ); |
256 | } |
257 | if ( $oldid !== null ) { |
258 | // Don't use the parser cache |
259 | $rev = $this->revisionLookup->getRevisionById( $oldid ); |
260 | if ( !$rev ) { |
261 | $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] ); |
262 | } |
263 | |
264 | $this->checkTitleUserPermissions( $rev->getPage(), 'read' ); |
265 | |
266 | if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) { |
267 | $this->dieWithError( |
268 | [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ] |
269 | ); |
270 | } |
271 | |
272 | $revLinkTarget = $rev->getPageAsLinkTarget(); |
273 | $titleObj = Title::newFromLinkTarget( $revLinkTarget ); |
274 | $wgTitle = $titleObj; |
275 | $pageObj = $this->wikiPageFactory->newFromTitle( $titleObj ); |
276 | [ $popts, $reset, $suppressCache ] = $this->makeParserOptions( $pageObj, $params ); |
277 | $p_result = $this->getParsedContent( |
278 | $pageObj, $popts, $suppressCache, $pageid, $rev, $needContent |
279 | ); |
280 | } else { // Not $oldid, but $pageid or $page |
281 | if ( $params['redirects'] ) { |
282 | $reqParams = [ |
283 | 'redirects' => '', |
284 | ]; |
285 | $pageParams = []; |
286 | if ( $pageid !== null ) { |
287 | $reqParams['pageids'] = $pageid; |
288 | $pageParams['pageid'] = $pageid; |
289 | } else { // $page |
290 | $reqParams['titles'] = $page; |
291 | $pageParams['title'] = $page; |
292 | } |
293 | $req = new FauxRequest( $reqParams ); |
294 | $main = new ApiMain( $req ); |
295 | $pageSet = new ApiPageSet( $main ); |
296 | $pageSet->execute(); |
297 | $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() ); |
298 | |
299 | foreach ( $pageSet->getRedirectTargets() as $redirectTarget ) { |
300 | $pageParams = [ 'title' => $this->titleFormatter->getFullText( $redirectTarget ) ]; |
301 | } |
302 | } elseif ( $pageid !== null ) { |
303 | $pageParams = [ 'pageid' => $pageid ]; |
304 | } else { // $page |
305 | $pageParams = [ 'title' => $page ]; |
306 | } |
307 | |
308 | $pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' ); |
309 | $titleObj = $pageObj->getTitle(); |
310 | if ( !$titleObj->exists() ) { |
311 | $this->dieWithError( 'apierror-missingtitle' ); |
312 | } |
313 | |
314 | $this->checkTitleUserPermissions( $titleObj, 'read' ); |
315 | $wgTitle = $titleObj; |
316 | |
317 | if ( isset( $prop['revid'] ) ) { |
318 | $oldid = $pageObj->getLatest(); |
319 | } |
320 | |
321 | [ $popts, $reset, $suppressCache ] = $this->makeParserOptions( $pageObj, $params ); |
322 | $p_result = $this->getParsedContent( |
323 | $pageObj, $popts, $suppressCache, $pageid, null, $needContent |
324 | ); |
325 | } |
326 | } else { // Not $oldid, $pageid, $page. Hence based on $text |
327 | $model = $params['contentmodel']; |
328 | $format = $params['contentformat']; |
329 | |
330 | $titleObj = Title::newFromText( $title ); |
331 | if ( !$titleObj || $titleObj->isExternal() ) { |
332 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] ); |
333 | } |
334 | $revid = $params['revid']; |
335 | $rev = null; |
336 | if ( $revid !== null ) { |
337 | $rev = $this->revisionLookup->getRevisionById( $revid ); |
338 | if ( !$rev ) { |
339 | $this->dieWithError( [ 'apierror-nosuchrevid', $revid ] ); |
340 | } |
341 | $pTitleObj = $titleObj; |
342 | $titleObj = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() ); |
343 | if ( $titleProvided ) { |
344 | if ( !$titleObj->equals( $pTitleObj ) ) { |
345 | $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(), |
346 | wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] ); |
347 | } |
348 | } else { |
349 | // Consider the title derived from the revid as having |
350 | // been provided. |
351 | $titleProvided = true; |
352 | } |
353 | } |
354 | $wgTitle = $titleObj; |
355 | if ( $titleObj->canExist() ) { |
356 | $pageObj = $this->wikiPageFactory->newFromTitle( $titleObj ); |
357 | [ $popts, $reset ] = $this->makeParserOptions( $pageObj, $params ); |
358 | } else { |
359 | // Allow parsing wikitext in the context of special pages (T51477) |
360 | $pageObj = null; |
361 | $popts = ParserOptions::newFromContext( $this->getContext() ); |
362 | [ $popts, $reset ] = $this->tweakParserOptions( $popts, $titleObj, $params ); |
363 | } |
364 | |
365 | $textProvided = $text !== null; |
366 | |
367 | if ( !$textProvided ) { |
368 | if ( $titleProvided && ( $prop || $params['generatexml'] ) ) { |
369 | if ( $revid !== null ) { |
370 | $this->addWarning( 'apiwarn-parse-revidwithouttext' ); |
371 | } else { |
372 | $this->addWarning( 'apiwarn-parse-titlewithouttext' ); |
373 | } |
374 | } |
375 | // Prevent warning from ContentHandler::makeContent() |
376 | $text = ''; |
377 | } |
378 | |
379 | // If we are parsing text, do not use the content model of the default |
380 | // API title, but default to wikitext to keep BC. |
381 | if ( $textProvided && !$titleProvided && $model === null ) { |
382 | $model = CONTENT_MODEL_WIKITEXT; |
383 | $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] ); |
384 | } elseif ( $model === null ) { |
385 | $model = $titleObj->getContentModel(); |
386 | } |
387 | |
388 | $contentHandler = $this->contentHandlerFactory->getContentHandler( $model ); |
389 | // Not in the default format, check supported or not |
390 | if ( $format && !$contentHandler->isSupportedFormat( $format ) ) { |
391 | $this->dieWithError( [ 'apierror-badformat-generic', $format, $model ] ); |
392 | } |
393 | |
394 | try { |
395 | $this->content = $contentHandler->unserializeContent( $text, $format ); |
396 | } catch ( MWContentSerializationException $ex ) { |
397 | $this->dieWithException( $ex, [ |
398 | 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' ) |
399 | ] ); |
400 | } |
401 | |
402 | if ( $this->section !== false ) { |
403 | if ( $this->section === 'new' ) { |
404 | // Insert the section title above the content. |
405 | if ( $params['sectiontitle'] !== null ) { |
406 | $this->content = $this->content->addSectionHeader( $params['sectiontitle'] ); |
407 | } |
408 | } else { |
409 | $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() ); |
410 | } |
411 | } |
412 | |
413 | if ( $params['pst'] || $params['onlypst'] ) { |
414 | $this->pstContent = $this->contentTransformer->preSaveTransform( |
415 | $this->content, |
416 | $titleObj, |
417 | $this->getUserForPreview(), |
418 | $popts |
419 | ); |
420 | } |
421 | if ( $params['onlypst'] ) { |
422 | // Build a result and bail out |
423 | $result_array = []; |
424 | $result_array['text'] = $this->pstContent->serialize( $format ); |
425 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text'; |
426 | if ( isset( $prop['wikitext'] ) ) { |
427 | $result_array['wikitext'] = $this->content->serialize( $format ); |
428 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext'; |
429 | } |
430 | if ( $params['summary'] !== null || |
431 | ( $params['sectiontitle'] !== null && $this->section === 'new' ) |
432 | ) { |
433 | $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params ); |
434 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary'; |
435 | } |
436 | |
437 | $result->addValue( null, $this->getModuleName(), $result_array ); |
438 | |
439 | return; |
440 | } |
441 | |
442 | // Not cached (save or load) |
443 | if ( $params['pst'] ) { |
444 | $p_result = $this->getContentParserOutput( $this->pstContent, $titleObj, $rev, $popts ); |
445 | } else { |
446 | $p_result = $this->getContentParserOutput( $this->content, $titleObj, $rev, $popts ); |
447 | } |
448 | } |
449 | |
450 | $result_array = []; |
451 | |
452 | $result_array['title'] = $titleObj->getPrefixedText(); |
453 | $result_array['pageid'] = $pageid ?: $titleObj->getArticleID(); |
454 | if ( $this->contentIsDeleted ) { |
455 | $result_array['textdeleted'] = true; |
456 | } |
457 | if ( $this->contentIsSuppressed ) { |
458 | $result_array['textsuppressed'] = true; |
459 | } |
460 | |
461 | if ( isset( $params['useskin'] ) ) { |
462 | $skin = $this->skinFactory->makeSkin( Skin::normalizeKey( $params['useskin'] ) ); |
463 | } else { |
464 | $skin = null; |
465 | } |
466 | |
467 | $outputPage = null; |
468 | $context = null; |
469 | if ( |
470 | $skin || isset( $prop['subtitle'] ) || isset( $prop['headhtml'] ) || isset( $prop['categorieshtml'] ) || |
471 | isset( $params['mobileformat'] ) |
472 | ) { |
473 | // Enabling the skin via 'useskin', 'subtitle', 'headhtml', or 'categorieshtml' |
474 | // gets OutputPage and Skin involved, which (among others) applies |
475 | // these hooks: |
476 | // - Hook: LanguageLinks |
477 | // - Hook: SkinSubPageSubtitle |
478 | // - Hook: OutputPageParserOutput |
479 | // - Hook: OutputPageMakeCategoryLinks |
480 | // - Hook: OutputPageBeforeHTML |
481 | // HACK Adding the 'mobileformat' parameter *also* enables the skin, for compatibility with legacy |
482 | // apps. This behavior should be considered deprecated so new users should not rely on this and |
483 | // always use the "useskin" parameter to enable "skin mode". |
484 | // Ideally this would be done with another hook so that MobileFrontend could enable skin mode, but |
485 | // as this is just for a deprecated feature, we are hard-coding this param into core. |
486 | $context = new DerivativeContext( $this->getContext() ); |
487 | $context->setTitle( $titleObj ); |
488 | |
489 | if ( $pageObj ) { |
490 | $context->setWikiPage( $pageObj ); |
491 | } |
492 | // Some hooks only apply to pages when action=view, which this API |
493 | // call is simulating. |
494 | $context->setRequest( new FauxRequest( [ 'action' => 'view' ] ) ); |
495 | |
496 | if ( $skin ) { |
497 | // Use the skin specified by 'useskin' |
498 | $context->setSkin( $skin ); |
499 | // Context clones the skin, refetch to stay in sync. (T166022) |
500 | $skin = $context->getSkin(); |
501 | } else { |
502 | // Make sure the context's skin refers to the context. Without this, |
503 | // $outputPage->getSkin()->getOutput() !== $outputPage which |
504 | // confuses some of the output. |
505 | $context->setSkin( $context->getSkin() ); |
506 | } |
507 | |
508 | $outputPage = new OutputPage( $context ); |
509 | // Required for subtitle to appear |
510 | $outputPage->setArticleFlag( true ); |
511 | |
512 | $outputPage->addParserOutputMetadata( $p_result ); |
513 | if ( $this->content ) { |
514 | $outputPage->addContentOverride( $titleObj, $this->content ); |
515 | } |
516 | $context->setOutput( $outputPage ); |
517 | |
518 | if ( $skin ) { |
519 | // Based on OutputPage::output() |
520 | $outputPage->loadSkinModules( $skin ); |
521 | } |
522 | |
523 | $this->getHookRunner()->onApiParseMakeOutputPage( $this, $outputPage ); |
524 | } |
525 | |
526 | if ( $oldid !== null ) { |
527 | $result_array['revid'] = (int)$oldid; |
528 | } |
529 | |
530 | if ( $params['redirects'] && $redirValues !== null ) { |
531 | $result_array['redirects'] = $redirValues; |
532 | } |
533 | |
534 | if ( isset( $prop['text'] ) ) { |
535 | $skin = $context ? $context->getSkin() : null; |
536 | $skinOptions = $skin ? $skin->getOptions() : [ |
537 | 'toc' => true, |
538 | ]; |
539 | $result_array['text'] = $p_result->getText( [ |
540 | 'allowTOC' => !$params['disabletoc'], |
541 | 'injectTOC' => $skinOptions['toc'], |
542 | 'enableSectionEditLinks' => !$params['disableeditsection'], |
543 | 'wrapperDivClass' => $params['wrapoutputclass'], |
544 | 'deduplicateStyles' => !$params['disablestylededuplication'], |
545 | 'userLang' => $context ? $context->getLanguage() : null, |
546 | 'skin' => $skin, |
547 | 'includeDebugInfo' => !$params['disablepp'] && !$params['disablelimitreport'] |
548 | ] ); |
549 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text'; |
550 | if ( $context ) { |
551 | $this->getHookRunner()->onOutputPageBeforeHTML( $context->getOutput(), $result_array['text'] ); |
552 | } |
553 | } |
554 | |
555 | if ( $params['summary'] !== null || |
556 | ( $params['sectiontitle'] !== null && $this->section === 'new' ) |
557 | ) { |
558 | $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params ); |
559 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary'; |
560 | } |
561 | |
562 | if ( isset( $prop['langlinks'] ) ) { |
563 | if ( $skin ) { |
564 | $langlinks = $outputPage->getLanguageLinks(); |
565 | } else { |
566 | $langlinks = $p_result->getLanguageLinks(); |
567 | // The deprecated 'effectivelanglinks' option depredates OutputPage |
568 | // support via 'useskin'. If not already applied, then run just this |
569 | // one hook of OutputPage::addParserOutputMetadata here. |
570 | if ( $params['effectivelanglinks'] ) { |
571 | $linkFlags = []; |
572 | $this->getHookRunner()->onLanguageLinks( $titleObj, $langlinks, $linkFlags ); |
573 | } |
574 | } |
575 | |
576 | $result_array['langlinks'] = $this->formatLangLinks( $langlinks ); |
577 | } |
578 | if ( isset( $prop['categories'] ) ) { |
579 | $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategoryMap() ); |
580 | } |
581 | if ( isset( $prop['categorieshtml'] ) ) { |
582 | $result_array['categorieshtml'] = $outputPage->getSkin()->getCategories(); |
583 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml'; |
584 | } |
585 | if ( isset( $prop['links'] ) ) { |
586 | $result_array['links'] = $this->formatLinks( $p_result->getLinks() ); |
587 | } |
588 | if ( isset( $prop['templates'] ) ) { |
589 | $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() ); |
590 | } |
591 | if ( isset( $prop['images'] ) ) { |
592 | // Cast image links to string since PHP coerces numeric string array keys to numbers |
593 | // (T346265). |
594 | $result_array['images'] = array_map( |
595 | fn ( $link ) => (string)$link, |
596 | array_keys( $p_result->getImages() ) |
597 | ); |
598 | } |
599 | if ( isset( $prop['externallinks'] ) ) { |
600 | $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() ); |
601 | } |
602 | if ( isset( $prop['sections'] ) ) { |
603 | $result_array['sections'] = $p_result->getSections(); |
604 | $result_array['showtoc'] = $p_result->getOutputFlag( ParserOutputFlags::SHOW_TOC ); |
605 | } |
606 | if ( isset( $prop['parsewarnings'] ) ) { |
607 | $result_array['parsewarnings'] = $p_result->getWarnings(); |
608 | } |
609 | if ( isset( $prop['parsewarningshtml'] ) ) { |
610 | $warnings = $p_result->getWarnings(); |
611 | $warningsHtml = array_map( static function ( $warning ) { |
612 | return ( new RawMessage( '$1', [ $warning ] ) )->parse(); |
613 | }, $warnings ); |
614 | $result_array['parsewarningshtml'] = $warningsHtml; |
615 | } |
616 | |
617 | if ( isset( $prop['displaytitle'] ) ) { |
618 | $result_array['displaytitle'] = $p_result->getDisplayTitle() !== false |
619 | ? $p_result->getDisplayTitle() |
620 | : htmlspecialchars( $titleObj->getPrefixedText(), ENT_NOQUOTES ); |
621 | } |
622 | |
623 | if ( isset( $prop['subtitle'] ) ) { |
624 | // Get the subtitle without its container element to support UI refreshing |
625 | $result_array['subtitle'] = $context->getSkin()->prepareSubtitle( false ); |
626 | } |
627 | |
628 | if ( isset( $prop['headitems'] ) ) { |
629 | if ( $skin ) { |
630 | $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() ); |
631 | } else { |
632 | $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() ); |
633 | } |
634 | } |
635 | |
636 | if ( isset( $prop['headhtml'] ) ) { |
637 | $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() ); |
638 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml'; |
639 | } |
640 | |
641 | if ( isset( $prop['modules'] ) ) { |
642 | if ( $skin ) { |
643 | $result_array['modules'] = $outputPage->getModules(); |
644 | // Deprecated since 1.32 (T188689) |
645 | $result_array['modulescripts'] = []; |
646 | $result_array['modulestyles'] = $outputPage->getModuleStyles(); |
647 | } else { |
648 | $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) ); |
649 | // Deprecated since 1.32 (T188689) |
650 | $result_array['modulescripts'] = []; |
651 | $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) ); |
652 | } |
653 | } |
654 | |
655 | if ( isset( $prop['jsconfigvars'] ) ) { |
656 | $showStrategyKeys = (bool)( $params['showstrategykeys'] ); |
657 | $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars( $showStrategyKeys ); |
658 | $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars ); |
659 | } |
660 | |
661 | if ( isset( $prop['encodedjsconfigvars'] ) ) { |
662 | $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars(); |
663 | $result_array['encodedjsconfigvars'] = FormatJson::encode( |
664 | $jsconfigvars, |
665 | false, |
666 | FormatJson::ALL_OK |
667 | ); |
668 | $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars'; |
669 | } |
670 | |
671 | if ( isset( $prop['modules'] ) && |
672 | !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) { |
673 | $this->addWarning( 'apiwarn-moduleswithoutvars' ); |
674 | } |
675 | |
676 | if ( isset( $prop['indicators'] ) ) { |
677 | if ( $skin ) { |
678 | $result_array['indicators'] = $outputPage->getIndicators(); |
679 | } else { |
680 | $result_array['indicators'] = $p_result->getIndicators(); |
681 | } |
682 | ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' ); |
683 | } |
684 | |
685 | if ( isset( $prop['iwlinks'] ) ) { |
686 | $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() ); |
687 | } |
688 | |
689 | if ( isset( $prop['wikitext'] ) ) { |
690 | $result_array['wikitext'] = $this->content->serialize( $format ); |
691 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext'; |
692 | // @phan-suppress-next-line PhanImpossibleTypeComparison |
693 | if ( $this->pstContent !== null ) { |
694 | $result_array['psttext'] = $this->pstContent->serialize( $format ); |
695 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext'; |
696 | } |
697 | } |
698 | if ( isset( $prop['properties'] ) ) { |
699 | $result_array['properties'] = $p_result->getPageProperties(); |
700 | ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' ); |
701 | } |
702 | |
703 | if ( isset( $prop['limitreportdata'] ) ) { |
704 | $result_array['limitreportdata'] = |
705 | $this->formatLimitReportData( $p_result->getLimitReportData() ); |
706 | } |
707 | if ( isset( $prop['limitreporthtml'] ) ) { |
708 | $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result ); |
709 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml'; |
710 | } |
711 | |
712 | if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) { |
713 | if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) { |
714 | $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' ); |
715 | } |
716 | |
717 | $parser = $this->parserFactory->getInstance(); |
718 | $parser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS ); |
719 | // @phan-suppress-next-line PhanUndeclaredMethod |
720 | $xml = $parser->preprocessToDom( $this->content->getText() )->__toString(); |
721 | $result_array['parsetree'] = $xml; |
722 | $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree'; |
723 | } |
724 | |
725 | $result_mapping = [ |
726 | 'redirects' => 'r', |
727 | 'langlinks' => 'll', |
728 | 'categories' => 'cl', |
729 | 'links' => 'pl', |
730 | 'templates' => 'tl', |
731 | 'images' => 'img', |
732 | 'externallinks' => 'el', |
733 | 'iwlinks' => 'iw', |
734 | 'sections' => 's', |
735 | 'headitems' => 'hi', |
736 | 'modules' => 'm', |
737 | 'indicators' => 'ind', |
738 | 'modulescripts' => 'm', |
739 | 'modulestyles' => 'm', |
740 | 'properties' => 'pp', |
741 | 'limitreportdata' => 'lr', |
742 | 'parsewarnings' => 'pw', |
743 | 'parsewarningshtml' => 'pw', |
744 | ]; |
745 | $this->setIndexedTagNames( $result_array, $result_mapping ); |
746 | $result->addValue( null, $this->getModuleName(), $result_array ); |
747 | } |
748 | |
749 | /** |
750 | * Constructs a ParserOptions object |
751 | * |
752 | * @param WikiPage $pageObj |
753 | * @param array $params |
754 | * |
755 | * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ] |
756 | */ |
757 | private function makeParserOptions( WikiPage $pageObj, array $params ) { |
758 | $popts = $pageObj->makeParserOptions( $this->getContext() ); |
759 | $popts->setRenderReason( 'api-parse' ); |
760 | return $this->tweakParserOptions( $popts, $pageObj->getTitle(), $params ); |
761 | } |
762 | |
763 | /** |
764 | * Tweaks a ParserOptions object |
765 | * |
766 | * @param ParserOptions $popts |
767 | * @param Title $title |
768 | * @param array $params |
769 | * |
770 | * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ] |
771 | */ |
772 | private function tweakParserOptions( ParserOptions $popts, Title $title, array $params ) { |
773 | $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] ); |
774 | $popts->setIsSectionPreview( $params['sectionpreview'] ); |
775 | |
776 | if ( $params['wrapoutputclass'] !== '' ) { |
777 | $popts->setWrapOutputClass( $params['wrapoutputclass'] ); |
778 | } |
779 | if ( $params['parsoid'] ) { |
780 | $popts->setUseParsoid(); |
781 | } |
782 | |
783 | $reset = null; |
784 | $suppressCache = false; |
785 | $this->getHookRunner()->onApiMakeParserOptions( $popts, $title, |
786 | $params, $this, $reset, $suppressCache ); |
787 | |
788 | return [ $popts, $reset, $suppressCache ]; |
789 | } |
790 | |
791 | /** |
792 | * @param WikiPage $page |
793 | * @param ParserOptions $popts |
794 | * @param bool $suppressCache |
795 | * @param int $pageId |
796 | * @param RevisionRecord|null $rev |
797 | * @param bool $getContent |
798 | * @return ParserOutput |
799 | */ |
800 | private function getParsedContent( |
801 | WikiPage $page, $popts, $suppressCache, $pageId, $rev, $getContent |
802 | ) { |
803 | $revId = $rev ? $rev->getId() : null; |
804 | $isDeleted = $rev && $rev->isDeleted( RevisionRecord::DELETED_TEXT ); |
805 | |
806 | if ( $getContent || $this->section !== false || $isDeleted ) { |
807 | if ( $rev ) { |
808 | $this->content = $rev->getContent( |
809 | SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER, $this->getAuthority() |
810 | ); |
811 | if ( !$this->content ) { |
812 | $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ] ); |
813 | } |
814 | } else { |
815 | $this->content = $page->getContent( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ); |
816 | if ( !$this->content ) { |
817 | $this->dieWithError( [ 'apierror-missingcontent-pageid', $page->getId() ] ); |
818 | } |
819 | } |
820 | $this->contentIsDeleted = $isDeleted; |
821 | $this->contentIsSuppressed = $rev && |
822 | $rev->isDeleted( RevisionRecord::DELETED_TEXT | RevisionRecord::DELETED_RESTRICTED ); |
823 | } |
824 | |
825 | if ( $this->section !== false ) { |
826 | $this->content = $this->getSectionContent( |
827 | $this->content, |
828 | $pageId === null ? $page->getTitle()->getPrefixedText() : $this->msg( 'pageid', $pageId ) |
829 | ); |
830 | return $this->getContentParserOutput( |
831 | $this->content, $page->getTitle(), |
832 | $rev, |
833 | $popts |
834 | ); |
835 | } |
836 | |
837 | if ( $isDeleted ) { |
838 | // getParserOutput can't do revdeled revisions |
839 | |
840 | $pout = $this->getContentParserOutput( |
841 | $this->content, $page->getTitle(), |
842 | $rev, |
843 | $popts |
844 | ); |
845 | } else { |
846 | // getParserOutput will save to Parser cache if able |
847 | $pout = $this->getPageParserOutput( $page, $revId, $popts, $suppressCache ); |
848 | } |
849 | if ( !$pout ) { |
850 | // @codeCoverageIgnoreStart |
851 | $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] ); |
852 | // @codeCoverageIgnoreEnd |
853 | } |
854 | |
855 | return $pout; |
856 | } |
857 | |
858 | /** |
859 | * Extract the requested section from the given Content |
860 | * |
861 | * @param Content $content |
862 | * @param string|Message $what Identifies the content in error messages, e.g. page title. |
863 | * @return Content |
864 | */ |
865 | private function getSectionContent( Content $content, $what ) { |
866 | // Not cached (save or load) |
867 | $section = $content->getSection( $this->section ); |
868 | if ( $section === false ) { |
869 | $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' ); |
870 | } |
871 | if ( $section === null ) { |
872 | $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' ); |
873 | } |
874 | |
875 | // @phan-suppress-next-line PhanTypeMismatchReturnNullable T240141 |
876 | return $section; |
877 | } |
878 | |
879 | /** |
880 | * This mimics the behavior of EditPage in formatting a summary |
881 | * |
882 | * @param Title $title of the page being parsed |
883 | * @param array $params The API parameters of the request |
884 | * @return string HTML |
885 | */ |
886 | private function formatSummary( $title, $params ) { |
887 | $summary = $params['summary'] ?? ''; |
888 | $sectionTitle = $params['sectiontitle'] ?? ''; |
889 | |
890 | if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) { |
891 | if ( $sectionTitle !== '' ) { |
892 | $summary = $params['sectiontitle']; |
893 | } |
894 | if ( $summary !== '' ) { |
895 | $summary = $this->msg( 'newsectionsummary' ) |
896 | ->rawParams( $this->parserFactory->getMainInstance()->stripSectionName( $summary ) ) |
897 | ->inContentLanguage()->text(); |
898 | } |
899 | } |
900 | return $this->commentFormatter->format( $summary, $title, $this->section === 'new' ); |
901 | } |
902 | |
903 | private function formatLangLinks( $links ) { |
904 | $result = []; |
905 | foreach ( $links as $link ) { |
906 | $entry = []; |
907 | $bits = explode( ':', $link, 2 ); |
908 | $title = Title::newFromText( $link ); |
909 | |
910 | $entry['lang'] = $bits[0]; |
911 | if ( $title ) { |
912 | $entry['url'] = (string)$this->urlUtils->expand( $title->getFullURL(), PROTO_CURRENT ); |
913 | // localised language name in 'uselang' language |
914 | $entry['langname'] = $this->languageNameUtils->getLanguageName( |
915 | $title->getInterwiki(), |
916 | $this->getLanguage()->getCode() |
917 | ); |
918 | |
919 | // native language name |
920 | $entry['autonym'] = $this->languageNameUtils->getLanguageName( $title->getInterwiki() ); |
921 | } |
922 | ApiResult::setContentValue( $entry, 'title', $bits[1] ); |
923 | $result[] = $entry; |
924 | } |
925 | |
926 | return $result; |
927 | } |
928 | |
929 | private function formatCategoryLinks( $links ) { |
930 | $result = []; |
931 | |
932 | if ( !$links ) { |
933 | return $result; |
934 | } |
935 | |
936 | // Fetch hiddencat property |
937 | $lb = $this->linkBatchFactory->newLinkBatch(); |
938 | $lb->setArray( [ NS_CATEGORY => $links ] ); |
939 | $db = $this->getDB(); |
940 | $res = $db->newSelectQueryBuilder() |
941 | ->select( [ 'page_title', 'pp_propname' ] ) |
942 | ->from( 'page' ) |
943 | ->where( $lb->constructSet( 'page', $db ) ) |
944 | ->leftJoin( 'page_props', null, [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ] ) |
945 | ->caller( __METHOD__ ) |
946 | ->fetchResultSet(); |
947 | $hiddencats = []; |
948 | foreach ( $res as $row ) { |
949 | $hiddencats[$row->page_title] = isset( $row->pp_propname ); |
950 | } |
951 | |
952 | foreach ( $links as $link => $sortkey ) { |
953 | $entry = []; |
954 | $entry['sortkey'] = $sortkey; |
955 | // array keys will cast numeric category names to ints, so cast back to string |
956 | ApiResult::setContentValue( $entry, 'category', (string)$link ); |
957 | if ( !isset( $hiddencats[$link] ) ) { |
958 | $entry['missing'] = true; |
959 | |
960 | // We already know the link doesn't exist in the database, so |
961 | // tell LinkCache that before calling $title->isKnown(). |
962 | $title = Title::makeTitle( NS_CATEGORY, $link ); |
963 | $this->linkCache->addBadLinkObj( $title ); |
964 | if ( $title->isKnown() ) { |
965 | $entry['known'] = true; |
966 | } |
967 | } elseif ( $hiddencats[$link] ) { |
968 | $entry['hidden'] = true; |
969 | } |
970 | $result[] = $entry; |
971 | } |
972 | |
973 | return $result; |
974 | } |
975 | |
976 | private function formatLinks( $links ) { |
977 | $result = []; |
978 | foreach ( $links as $ns => $nslinks ) { |
979 | foreach ( $nslinks as $title => $id ) { |
980 | $entry = []; |
981 | $entry['ns'] = $ns; |
982 | ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() ); |
983 | $entry['exists'] = $id != 0; |
984 | $result[] = $entry; |
985 | } |
986 | } |
987 | |
988 | return $result; |
989 | } |
990 | |
991 | private function formatIWLinks( $iw ) { |
992 | $result = []; |
993 | foreach ( $iw as $prefix => $titles ) { |
994 | foreach ( $titles as $title => $_ ) { |
995 | $entry = []; |
996 | $entry['prefix'] = $prefix; |
997 | |
998 | $title = Title::newFromText( "{$prefix}:{$title}" ); |
999 | if ( $title ) { |
1000 | $entry['url'] = (string)$this->urlUtils->expand( $title->getFullURL(), PROTO_CURRENT ); |
1001 | } |
1002 | |
1003 | ApiResult::setContentValue( $entry, 'title', $title->getFullText() ); |
1004 | $result[] = $entry; |
1005 | } |
1006 | } |
1007 | |
1008 | return $result; |
1009 | } |
1010 | |
1011 | private function formatHeadItems( $headItems ) { |
1012 | $result = []; |
1013 | foreach ( $headItems as $tag => $content ) { |
1014 | $entry = []; |
1015 | $entry['tag'] = $tag; |
1016 | ApiResult::setContentValue( $entry, 'content', $content ); |
1017 | $result[] = $entry; |
1018 | } |
1019 | |
1020 | return $result; |
1021 | } |
1022 | |
1023 | private function formatLimitReportData( $limitReportData ) { |
1024 | $result = []; |
1025 | |
1026 | foreach ( $limitReportData as $name => $value ) { |
1027 | $entry = []; |
1028 | $entry['name'] = $name; |
1029 | if ( !is_array( $value ) ) { |
1030 | $value = [ $value ]; |
1031 | } |
1032 | ApiResult::setIndexedTagNameRecursive( $value, 'param' ); |
1033 | $entry = array_merge( $entry, $value ); |
1034 | $result[] = $entry; |
1035 | } |
1036 | |
1037 | return $result; |
1038 | } |
1039 | |
1040 | private function setIndexedTagNames( &$array, $mapping ) { |
1041 | foreach ( $mapping as $key => $name ) { |
1042 | if ( isset( $array[$key] ) ) { |
1043 | ApiResult::setIndexedTagName( $array[$key], $name ); |
1044 | } |
1045 | } |
1046 | } |
1047 | |
1048 | public function getAllowedParams() { |
1049 | return [ |
1050 | 'title' => null, |
1051 | 'text' => [ |
1052 | ParamValidator::PARAM_TYPE => 'text', |
1053 | ], |
1054 | 'revid' => [ |
1055 | ParamValidator::PARAM_TYPE => 'integer', |
1056 | ], |
1057 | 'summary' => null, |
1058 | 'page' => null, |
1059 | 'pageid' => [ |
1060 | ParamValidator::PARAM_TYPE => 'integer', |
1061 | ], |
1062 | 'redirects' => false, |
1063 | 'oldid' => [ |
1064 | ParamValidator::PARAM_TYPE => 'integer', |
1065 | ], |
1066 | 'prop' => [ |
1067 | ParamValidator::PARAM_DEFAULT => 'text|langlinks|categories|links|templates|' . |
1068 | 'images|externallinks|sections|revid|displaytitle|iwlinks|' . |
1069 | 'properties|parsewarnings', |
1070 | ParamValidator::PARAM_ISMULTI => true, |
1071 | ParamValidator::PARAM_TYPE => [ |
1072 | 'text', |
1073 | 'langlinks', |
1074 | 'categories', |
1075 | 'categorieshtml', |
1076 | 'links', |
1077 | 'templates', |
1078 | 'images', |
1079 | 'externallinks', |
1080 | 'sections', |
1081 | 'revid', |
1082 | 'displaytitle', |
1083 | 'subtitle', |
1084 | 'headhtml', |
1085 | 'modules', |
1086 | 'jsconfigvars', |
1087 | 'encodedjsconfigvars', |
1088 | 'indicators', |
1089 | 'iwlinks', |
1090 | 'wikitext', |
1091 | 'properties', |
1092 | 'limitreportdata', |
1093 | 'limitreporthtml', |
1094 | 'parsetree', |
1095 | 'parsewarnings', |
1096 | 'parsewarningshtml', |
1097 | 'headitems', |
1098 | ], |
1099 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [ |
1100 | 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ], |
1101 | ], |
1102 | EnumDef::PARAM_DEPRECATED_VALUES => [ |
1103 | 'headitems' => 'apiwarn-deprecation-parse-headitems', |
1104 | ], |
1105 | ], |
1106 | 'wrapoutputclass' => 'mw-parser-output', |
1107 | 'parsoid' => false, // since 1.41 |
1108 | 'pst' => false, |
1109 | 'onlypst' => false, |
1110 | 'effectivelanglinks' => [ |
1111 | ParamValidator::PARAM_DEFAULT => false, |
1112 | ParamValidator::PARAM_DEPRECATED => true, |
1113 | ], |
1114 | 'section' => null, |
1115 | 'sectiontitle' => [ |
1116 | ParamValidator::PARAM_TYPE => 'string', |
1117 | ], |
1118 | 'disablepp' => [ |
1119 | ParamValidator::PARAM_DEFAULT => false, |
1120 | ParamValidator::PARAM_DEPRECATED => true, |
1121 | ], |
1122 | 'disablelimitreport' => false, |
1123 | 'disableeditsection' => false, |
1124 | 'disablestylededuplication' => false, |
1125 | 'showstrategykeys' => false, |
1126 | 'generatexml' => [ |
1127 | ParamValidator::PARAM_DEFAULT => false, |
1128 | ApiBase::PARAM_HELP_MSG => [ |
1129 | 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT |
1130 | ], |
1131 | ParamValidator::PARAM_DEPRECATED => true, |
1132 | ], |
1133 | 'preview' => false, |
1134 | 'sectionpreview' => false, |
1135 | 'disabletoc' => false, |
1136 | 'useskin' => [ |
1137 | // T237856; We use all installed skins here to allow hidden (but usable) skins |
1138 | // to continue working correctly with some features such as Live Preview |
1139 | ParamValidator::PARAM_TYPE => array_keys( $this->skinFactory->getInstalledSkins() ), |
1140 | ], |
1141 | 'contentformat' => [ |
1142 | ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(), |
1143 | ], |
1144 | 'contentmodel' => [ |
1145 | ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(), |
1146 | ], |
1147 | ]; |
1148 | } |
1149 | |
1150 | protected function getExamplesMessages() { |
1151 | return [ |
1152 | 'action=parse&page=Project:Sandbox' |
1153 | => 'apihelp-parse-example-page', |
1154 | 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext' |
1155 | => 'apihelp-parse-example-text', |
1156 | 'action=parse&text={{PAGENAME}}&title=Test' |
1157 | => 'apihelp-parse-example-texttitle', |
1158 | 'action=parse&summary=Some+[[link]]&prop=' |
1159 | => 'apihelp-parse-example-summary', |
1160 | ]; |
1161 | } |
1162 | |
1163 | public function getHelpUrls() { |
1164 | return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext'; |
1165 | } |
1166 | } |