Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
6.64% |
16 / 241 |
|
16.67% |
2 / 12 |
CRAP | |
0.00% |
0 / 1 |
DataAccess | |
6.64% |
16 / 241 |
|
16.67% |
2 / 12 |
3089.00 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
makeTransformOptions | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
getPageInfo | |
0.00% |
0 / 47 |
|
0.00% |
0 / 1 |
90 | |||
getFileInfo | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
182 | |||
prepareParser | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
makeLimitReport | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
parseWikitext | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
preprocessWikitext | |
0.00% |
0 / 59 |
|
0.00% |
0 / 1 |
272 | |||
fetchTemplateSource | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
fetchTemplateData | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
addTrackingCategory | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
logLinterData | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | /** |
3 | * Copyright (C) 2011-2022 Wikimedia Foundation and others. |
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 | */ |
19 | |
20 | namespace MediaWiki\Parser\Parsoid\Config; |
21 | |
22 | use MediaTransformError; |
23 | use MediaWiki\Cache\LinkBatchFactory; |
24 | use MediaWiki\Category\TrackingCategories; |
25 | use MediaWiki\Config\ServiceOptions; |
26 | use MediaWiki\Content\Transform\ContentTransformer; |
27 | use MediaWiki\FileRepo\File\File; |
28 | use MediaWiki\FileRepo\RepoGroup; |
29 | use MediaWiki\HookContainer\HookContainer; |
30 | use MediaWiki\HookContainer\HookRunner; |
31 | use MediaWiki\Html\Html; |
32 | use MediaWiki\Language\LanguageCode; |
33 | use MediaWiki\Linker\Linker; |
34 | use MediaWiki\MainConfigNames; |
35 | use MediaWiki\Page\File\BadFileLookup; |
36 | use MediaWiki\Parser\Parser; |
37 | use MediaWiki\Parser\ParserFactory; |
38 | use MediaWiki\Parser\ParserOptions; |
39 | use MediaWiki\Parser\ParserOutput; |
40 | use MediaWiki\Parser\PPFrame; |
41 | use MediaWiki\Title\Title; |
42 | use Wikimedia\Assert\UnreachableException; |
43 | use Wikimedia\Parsoid\Config\DataAccess as IDataAccess; |
44 | use Wikimedia\Parsoid\Config\PageConfig as IPageConfig; |
45 | use Wikimedia\Parsoid\Config\PageContent as IPageContent; |
46 | use Wikimedia\Parsoid\Core\ContentMetadataCollector; |
47 | use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget; |
48 | use Wikimedia\Parsoid\Fragments\HtmlPFragment; |
49 | use Wikimedia\Parsoid\Fragments\PFragment; |
50 | use Wikimedia\Parsoid\Fragments\WikitextPFragment; |
51 | use Wikimedia\Rdbms\ReadOnlyMode; |
52 | |
53 | /** |
54 | * Implement Parsoid's abstract class for data access. |
55 | * |
56 | * @since 1.39 |
57 | * @internal |
58 | */ |
59 | class DataAccess extends IDataAccess { |
60 | public const CONSTRUCTOR_OPTIONS = [ |
61 | MainConfigNames::SVGMaxSize, |
62 | ]; |
63 | |
64 | private RepoGroup $repoGroup; |
65 | private BadFileLookup $badFileLookup; |
66 | private HookContainer $hookContainer; |
67 | private HookRunner $hookRunner; |
68 | private ContentTransformer $contentTransformer; |
69 | private TrackingCategories $trackingCategories; |
70 | private ParserFactory $parserFactory; |
71 | /** Lazy-created via self::prepareParser() */ |
72 | private ?Parser $parser = null; |
73 | private PPFrame $ppFrame; |
74 | private ?PageConfig $previousPageConfig = null; |
75 | private ServiceOptions $config; |
76 | private ReadOnlyMode $readOnlyMode; |
77 | private LinkBatchFactory $linkBatchFactory; |
78 | private int $markerIndex = 0; |
79 | |
80 | /** |
81 | * @param ServiceOptions $config MediaWiki main configuration object |
82 | * @param RepoGroup $repoGroup |
83 | * @param BadFileLookup $badFileLookup |
84 | * @param HookContainer $hookContainer |
85 | * @param ContentTransformer $contentTransformer |
86 | * @param TrackingCategories $trackingCategories |
87 | * @param ReadOnlyMode $readOnlyMode used to disable linting when the |
88 | * database is read-only. |
89 | * @param ParserFactory $parserFactory A legacy parser factory, |
90 | * for PST/preprocessing/extension handling |
91 | * @param LinkBatchFactory $linkBatchFactory |
92 | */ |
93 | public function __construct( |
94 | ServiceOptions $config, |
95 | RepoGroup $repoGroup, |
96 | BadFileLookup $badFileLookup, |
97 | HookContainer $hookContainer, |
98 | ContentTransformer $contentTransformer, |
99 | TrackingCategories $trackingCategories, |
100 | ReadOnlyMode $readOnlyMode, |
101 | ParserFactory $parserFactory, |
102 | LinkBatchFactory $linkBatchFactory |
103 | ) { |
104 | $config->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
105 | $this->config = $config; |
106 | $this->repoGroup = $repoGroup; |
107 | $this->badFileLookup = $badFileLookup; |
108 | $this->hookContainer = $hookContainer; |
109 | $this->contentTransformer = $contentTransformer; |
110 | $this->trackingCategories = $trackingCategories; |
111 | $this->readOnlyMode = $readOnlyMode; |
112 | $this->linkBatchFactory = $linkBatchFactory; |
113 | |
114 | $this->hookRunner = new HookRunner( $hookContainer ); |
115 | |
116 | $this->parserFactory = $parserFactory; |
117 | $this->previousPageConfig = null; // ensure we initialize parser options |
118 | } |
119 | |
120 | /** |
121 | * @param IPageConfig $pageConfig |
122 | * @param File $file |
123 | * @param array $hp |
124 | * @return array |
125 | */ |
126 | private function makeTransformOptions( IPageConfig $pageConfig, $file, array $hp ): array { |
127 | // Validate the input parameters like Parser::makeImage() |
128 | $handler = $file->getHandler(); |
129 | if ( !$handler ) { |
130 | return []; // will get iconThumb() |
131 | } |
132 | foreach ( $hp as $name => $value ) { |
133 | if ( !$handler->validateParam( $name, $value ) ) { |
134 | unset( $hp[$name] ); |
135 | } |
136 | } |
137 | |
138 | // This part is similar to Linker::makeImageLink(). If there is no width, |
139 | // set one based on the source file size. |
140 | $page = $hp['page'] ?? 0; |
141 | if ( !isset( $hp['width'] ) ) { |
142 | if ( isset( $hp['height'] ) && $file->isVectorized() ) { |
143 | // If it's a vector image, and user only specifies height |
144 | // we don't want it to be limited by its "normal" width. |
145 | $hp['width'] = $this->config->get( MainConfigNames::SVGMaxSize ); |
146 | } else { |
147 | $hp['width'] = $file->getWidth( $page ); |
148 | } |
149 | |
150 | // We don't need to fill in a default thumbnail width here, since |
151 | // that is done by Parsoid. Parsoid always sets the width parameter |
152 | // for thumbnails. |
153 | } |
154 | |
155 | // Parser::makeImage() always sets this |
156 | $hp['targetlang'] = LanguageCode::bcp47ToInternal( |
157 | $pageConfig->getPageLanguageBcp47() |
158 | ); |
159 | |
160 | return $hp; |
161 | } |
162 | |
163 | /** @inheritDoc */ |
164 | public function getPageInfo( $pageConfigOrTitle, array $titles ): array { |
165 | if ( $pageConfigOrTitle instanceof IPageConfig ) { |
166 | $context_title = Title::newFromLinkTarget( |
167 | $pageConfigOrTitle->getLinkTarget() |
168 | ); |
169 | } elseif ( is_string( $pageConfigOrTitle ) ) { |
170 | // Temporary, deprecated. |
171 | $context_title = Title::newFromTextThrow( $pageConfigOrTitle ); |
172 | } elseif ( $pageConfigOrTitle instanceof ParsoidLinkTarget ) { |
173 | $context_title = Title::newFromLinkTarget( $pageConfigOrTitle ); |
174 | } else { |
175 | throw new UnreachableException( "Bad type for argument 1" ); |
176 | } |
177 | $titleObjs = []; |
178 | $pagemap = []; |
179 | $classes = []; |
180 | $ret = []; |
181 | foreach ( $titles as $name ) { |
182 | $t = Title::newFromText( $name ); |
183 | // Filter out invalid titles. Title::newFromText in core (not our bespoke |
184 | // version in src/Utils/Title.php) can return null for invalid titles. |
185 | if ( !$t ) { |
186 | // FIXME: This is a bandaid to patch up the fact that Env::makeTitle treats |
187 | // this as a valid title, but Title::newFromText treats it as invalid. |
188 | // T237535 |
189 | // This matches what ApiQuery::outputGeneralPageInfo() would |
190 | // return for an invalid title. |
191 | $ret[$name] = [ |
192 | 'pageId' => -1, |
193 | 'revId' => -1, |
194 | 'invalid' => true, |
195 | 'invalidreason' => 'The requested page title is invalid', |
196 | ]; |
197 | } else { |
198 | $titleObjs[$name] = $t; |
199 | } |
200 | } |
201 | $this->linkBatchFactory->newLinkBatch( $titleObjs ) |
202 | ->setCaller( __METHOD__ ) |
203 | ->execute(); |
204 | |
205 | foreach ( $titleObjs as $obj ) { |
206 | $pdbk = $obj->getPrefixedDBkey(); |
207 | $pagemap[$obj->getArticleID()] = $pdbk; |
208 | $classes[$pdbk] = $obj->isRedirect() ? 'mw-redirect' : ''; |
209 | } |
210 | $this->hookRunner->onGetLinkColours( |
211 | # $classes is passed by reference and mutated |
212 | $pagemap, $classes, $context_title |
213 | ); |
214 | |
215 | foreach ( $titleObjs as $name => $obj ) { |
216 | /** @var Title $obj */ |
217 | $pdbk = $obj->getPrefixedDBkey(); |
218 | $c = preg_split( |
219 | '/\s+/', $classes[$pdbk] ?? '', -1, PREG_SPLIT_NO_EMPTY |
220 | ); |
221 | $ret[$name] = [ |
222 | 'pageId' => $obj->getArticleID(), |
223 | 'revId' => $obj->getLatestRevID(), |
224 | 'missing' => !$obj->exists(), |
225 | 'known' => $obj->isKnown(), |
226 | 'redirect' => $obj->isRedirect(), |
227 | 'linkclasses' => $c, # See ApiQueryInfo::getLinkClasses() in core |
228 | ]; |
229 | } |
230 | return $ret; |
231 | } |
232 | |
233 | /** @inheritDoc */ |
234 | public function getFileInfo( IPageConfig $pageConfig, array $files ): array { |
235 | $page = Title::newFromLinkTarget( $pageConfig->getLinkTarget() ); |
236 | |
237 | $keys = []; |
238 | foreach ( $files as $f ) { |
239 | $keys[] = $f[0]; |
240 | } |
241 | $fileObjs = $this->repoGroup->findFiles( $keys ); |
242 | |
243 | $ret = []; |
244 | foreach ( $files as $f ) { |
245 | $filename = $f[0]; |
246 | $dims = $f[1]; |
247 | |
248 | /** @var File $file */ |
249 | $file = $fileObjs[$filename] ?? null; |
250 | if ( !$file ) { |
251 | $ret[] = null; |
252 | continue; |
253 | } |
254 | |
255 | // See Linker::makeImageLink; 'page' is a key in $handlerParams |
256 | // core uses 'false' as the default then casts to (int) => 0 |
257 | $pageNum = $dims['page'] ?? 0; |
258 | |
259 | $result = [ |
260 | 'width' => $file->getWidth( $pageNum ), |
261 | 'height' => $file->getHeight( $pageNum ), |
262 | 'size' => $file->getSize(), |
263 | 'mediatype' => $file->getMediaType(), |
264 | 'mime' => $file->getMimeType(), |
265 | 'url' => $file->getFullUrl(), |
266 | 'mustRender' => $file->mustRender(), |
267 | 'badFile' => $this->badFileLookup->isBadFile( $filename, $page ), |
268 | 'timestamp' => $file->getTimestamp(), |
269 | 'sha1' => $file->getSha1(), |
270 | ]; |
271 | |
272 | $length = $file->getLength(); |
273 | if ( $length ) { |
274 | $result['duration'] = (float)$length; |
275 | } |
276 | |
277 | if ( isset( $dims['seek'] ) ) { |
278 | $dims['thumbtime'] = $dims['seek']; |
279 | } |
280 | |
281 | $txopts = $this->makeTransformOptions( $pageConfig, $file, $dims ); |
282 | $mto = $file->transform( $txopts ); |
283 | if ( $mto ) { |
284 | if ( $mto->isError() && $mto instanceof MediaTransformError ) { |
285 | $result['thumberror'] = $mto->toText(); |
286 | } else { |
287 | if ( $txopts ) { |
288 | // Do srcset scaling |
289 | Linker::processResponsiveImages( $file, $mto, $txopts ); |
290 | if ( count( $mto->responsiveUrls ) ) { |
291 | $result['responsiveUrls'] = []; |
292 | foreach ( $mto->responsiveUrls as $density => $url ) { |
293 | $result['responsiveUrls'][$density] = $url; |
294 | } |
295 | } |
296 | } |
297 | |
298 | // Proposed MediaTransformOutput serialization method for T51896 etc. |
299 | // Note that getAPIData(['fullurl']) would return |
300 | // UrlUtils::expand(), which wouldn't respect the wiki's |
301 | // protocol preferences -- instead it would use the |
302 | // protocol used for the API request. |
303 | if ( is_callable( [ $mto, 'getAPIData' ] ) ) { |
304 | $result['thumbdata'] = $mto->getAPIData( [ 'withhash' ] ); |
305 | } |
306 | |
307 | $result['thumburl'] = $mto->getUrl(); |
308 | $result['thumbwidth'] = $mto->getWidth(); |
309 | $result['thumbheight'] = $mto->getHeight(); |
310 | } |
311 | } else { |
312 | $result['thumberror'] = "Presumably, invalid parameters, despite validation."; |
313 | } |
314 | |
315 | $ret[] = $result; |
316 | } |
317 | |
318 | return $ret; |
319 | } |
320 | |
321 | /** |
322 | * Prepare MediaWiki's parser for preprocessing or extension tag parsing, |
323 | * clearing its state if necessary. |
324 | * |
325 | * @note The caller is expected to call Parser::resetOutput() and |
326 | * reset the watcher if needed on $pageConfig->getParserOptions() |
327 | * as needed. |
328 | * |
329 | * @param IPageConfig $pageConfig |
330 | * @param int $outputType |
331 | * @return Parser |
332 | */ |
333 | private function prepareParser( IPageConfig $pageConfig, int $outputType ) { |
334 | '@phan-var PageConfig $pageConfig'; // @var PageConfig $pageConfig |
335 | // Clear the state only when the PageConfig changes, so that Parser's internal caches can |
336 | // be retained. This should also provide better compatibility with extension tags. |
337 | $clearState = $this->previousPageConfig !== $pageConfig; |
338 | $this->previousPageConfig = $pageConfig; |
339 | $parserOptions = $pageConfig->getParserOptions(); |
340 | $oldWatcher = $parserOptions->registerWatcher( null ); |
341 | // Use the same legacy parser object for all calls to extension tag |
342 | // processing, for greater compatibility. |
343 | $this->parser ??= $this->parserFactory->create(); |
344 | $this->parser->setStripExtTags( false ); |
345 | $this->parser->startExternalParse( |
346 | Title::newFromLinkTarget( $pageConfig->getLinkTarget() ), |
347 | $parserOptions, |
348 | $outputType, $clearState, $pageConfig->getRevisionId() ); |
349 | |
350 | // Retain a PPFrame object between preprocess requests since it contains |
351 | // some useful caches. |
352 | if ( $clearState ) { |
353 | $this->ppFrame = $this->parser->getPreprocessor()->newFrame(); |
354 | // If $clearState is true, then we've reset the parser output and |
355 | // clobbered the watcher on the parser options; restore the old |
356 | // one. |
357 | $parserOptions->registerWatcher( $oldWatcher ); |
358 | } |
359 | return $this->parser; |
360 | } |
361 | |
362 | /** @internal */ |
363 | public function makeLimitReport( |
364 | IPageConfig $pageConfig, |
365 | ParserOptions $parserOptions, |
366 | ParserOutput $parserOutput |
367 | ) { |
368 | $parser = $this->prepareParser( $pageConfig, Parser::OT_HTML ); |
369 | // This next call doesn't touch $parser::$mParserOutput so we |
370 | // don't need to call Parser::resetOutput() here. |
371 | $parser->makeLimitReport( $parserOptions, $parserOutput ); |
372 | } |
373 | |
374 | /** @inheritDoc */ |
375 | public function parseWikitext( |
376 | IPageConfig $pageConfig, |
377 | ContentMetadataCollector $metadata, |
378 | string $wikitext |
379 | ): string { |
380 | '@phan-var PageConfig $pageConfig'; // @var PageConfig $pageConfig |
381 | $parser = $this->prepareParser( $pageConfig, Parser::OT_HTML ); |
382 | |
383 | // XXX: Ideally we will eventually have the legacy parser use our |
384 | // ContentMetadataCollector instead of having a new ParserOutput |
385 | // created (in Parser::resetOutput() here) which we then have to |
386 | // manually merge. On the other hand, this will let us precisely |
387 | // identify metadata added by $wikitext. |
388 | $parserOptions = $pageConfig->getParserOptions(); |
389 | $oldWatcher = $parserOptions->registerWatcher( null ); |
390 | $parser->resetOutput(); |
391 | |
392 | $html = $parser->parseExtensionTagAsTopLevelDoc( $wikitext ); |
393 | |
394 | $out = $parser->getOutput(); |
395 | $out->collectMetadata( $metadata ); # merges $out into $metadata |
396 | $parserOptions->registerWatcher( $oldWatcher ); |
397 | |
398 | return Parser::extractBody( $html ); |
399 | } |
400 | |
401 | /** @inheritDoc */ |
402 | public function preprocessWikitext( |
403 | IPageConfig $pageConfig, |
404 | ContentMetadataCollector $metadata, |
405 | $wikitext |
406 | ) { |
407 | '@phan-var PageConfig $pageConfig'; // @var PageConfig $pageConfig |
408 | |
409 | $parser = $this->prepareParser( $pageConfig, Parser::OT_PREPROCESS ); |
410 | |
411 | // XXX: Ideally we will eventually have the legacy parser use our |
412 | // ContentMetadataCollector instead of having a new ParserOutput |
413 | // created (in Parser::resetOutput() here) which we then have to |
414 | // manually merge. On the other hand, this will let us precisely |
415 | // identify metadata added by $wikitext. |
416 | $parserOptions = $pageConfig->getParserOptions(); |
417 | $oldWatcher = $parserOptions->registerWatcher( null ); |
418 | $parser->resetOutput(); |
419 | |
420 | if ( $wikitext instanceof PFragment ) { |
421 | $result = []; |
422 | $index = 1; |
423 | $split = $wikitext instanceof WikitextPFragment ? |
424 | $wikitext->split() : [ $wikitext ]; |
425 | foreach ( $split as $fragment ) { |
426 | if ( is_string( $fragment ) ) { |
427 | $result[] = $fragment; |
428 | } else { |
429 | $marker = Parser::MARKER_PREFIX . '-parsoid-' . |
430 | sprintf( '%08X', $this->markerIndex++ ) . |
431 | Parser::MARKER_SUFFIX; |
432 | $parser->getStripState()->addParsoidOpaque( |
433 | $marker, $fragment |
434 | ); |
435 | $result[] = $marker; |
436 | } |
437 | } |
438 | $wikitext = implode( $result ); |
439 | } |
440 | $this->hookRunner->onParserBeforePreprocess( |
441 | # $wikitext is passed by reference and mutated |
442 | $parser, $wikitext, $parser->getStripState() |
443 | ); |
444 | // New PFragment-based support (T374616) |
445 | $wikitext = $parser->replaceVariables( |
446 | $wikitext, $this->ppFrame, false, [ |
447 | 'parsoidTopLevelCall' => true, |
448 | // This is implied by stripExtTags=false and |
449 | // probably doesn't need to be explicitly passed |
450 | // any more. |
451 | 'processNowiki' => true, |
452 | ] |
453 | ); |
454 | // Where the result has strip state markers, tunnel this content |
455 | // through Parsoid as a PFragment type. |
456 | $pieces = $parser->getStripState()->split( $wikitext ); |
457 | if ( count( $pieces ) > 1 || ( $pieces[0]['type'] ?? null ) !== 'string' ) { |
458 | for ( $i = 0; $i < count( $pieces ); $i++ ) { |
459 | [ 'type' => $type, 'content' => $content ] = $pieces[$i]; |
460 | if ( $type === 'string' ) { |
461 | // wikitext (could include extension tag snippets like <tag..>...</tag>) |
462 | $pieces[$i] = $content; |
463 | } elseif ( $type === 'parsoid' ) { |
464 | $pieces[$i] = $pieces[$i]['extra']; // replace w/ fragment |
465 | } elseif ( $type === 'nowiki' ) { |
466 | $extra = $pieces[$i]['extra'] ?? null; |
467 | // T388819: If this is from an actual <nowiki>, we |
468 | // wrap <span typeof="mw:Nowiki"> around $contents. |
469 | if ( $extra === 'nowiki' ) { |
470 | $content = Html::rawElement( 'span', [ |
471 | 'typeof' => 'mw:Nowiki', |
472 | ], $content ); |
473 | } |
474 | $pieces[$i] = $content ? HtmlPFragment::newFromHtmlString( $content, null ) : ''; |
475 | } else { |
476 | // T381709: technically this fragment should |
477 | // be subject to language conversion and some |
478 | // additional processing |
479 | $pieces[$i] = $content ? HtmlPFragment::newFromHtmlString( $content, null ) : ''; |
480 | } |
481 | } |
482 | // Concatenate wikitext strings generated by extension tags, |
483 | // so that PFragment doesn't try to add <nowiki>s between |
484 | // the pieces to prevent token-gluing. |
485 | $result = []; |
486 | $wt = ''; |
487 | foreach ( $pieces as $p ) { |
488 | if ( is_string( $p ) ) { |
489 | $wt .= $p; |
490 | } else { |
491 | $result[] = $wt; |
492 | $result[] = $p; |
493 | $wt = ''; |
494 | } |
495 | } |
496 | $result[] = $wt; |
497 | // result will be a PFragment, no longer a string. |
498 | $wikitext = PFragment::fromSplitWt( $result ); |
499 | } |
500 | |
501 | $out = $parser->getOutput(); |
502 | $out->collectMetadata( $metadata ); # merges $out into $metadata |
503 | $parserOptions->registerWatcher( $oldWatcher ); |
504 | |
505 | return $wikitext; |
506 | } |
507 | |
508 | /** @inheritDoc */ |
509 | public function fetchTemplateSource( |
510 | IPageConfig $pageConfig, $title |
511 | ): ?IPageContent { |
512 | '@phan-var PageConfig $pageConfig'; // @var PageConfig $pageConfig |
513 | if ( is_string( $title ) ) { |
514 | $titleObj = Title::newFromTextThrow( $title ); |
515 | } else { |
516 | $titleObj = Title::newFromLinkTarget( $title ); |
517 | } |
518 | |
519 | // Use the PageConfig to take advantage of custom template |
520 | // fetch hooks like FlaggedRevisions, etc. |
521 | $revRecord = $pageConfig->fetchRevisionRecordOfTemplate( $titleObj ); |
522 | |
523 | return $revRecord ? new PageContent( $revRecord ) : null; |
524 | } |
525 | |
526 | /** @inheritDoc */ |
527 | public function fetchTemplateData( IPageConfig $pageConfig, $title ): ?array { |
528 | $ret = []; |
529 | if ( !is_string( $title ) ) { |
530 | $titleObj = Title::newFromLinkTarget( $title ); |
531 | $title = $titleObj->getPrefixedText(); |
532 | } |
533 | // @todo: This hook needs some clean up: T304899 |
534 | $this->hookRunner->onParserFetchTemplateData( |
535 | [ $title ], |
536 | $ret # value returned by reference |
537 | ); |
538 | |
539 | // Cast value to array since the hook returns this as a stdclass |
540 | $tplData = $ret[$title] ?? null; |
541 | if ( $tplData ) { |
542 | // Deep convert to associative array |
543 | $tplData = json_decode( json_encode( $tplData ), true ); |
544 | } |
545 | return $tplData; |
546 | } |
547 | |
548 | /** |
549 | * Add a tracking category with the given key to the metadata for the page. |
550 | * @param IPageConfig $pageConfig the page on which the tracking category |
551 | * is to be added |
552 | * @param ContentMetadataCollector $metadata The metadata for the page |
553 | * @param string $key Message key (not localized) |
554 | */ |
555 | public function addTrackingCategory( |
556 | IPageConfig $pageConfig, |
557 | ContentMetadataCollector $metadata, |
558 | string $key |
559 | ): void { |
560 | $page = Title::newFromLinkTarget( $pageConfig->getLinkTarget() ); |
561 | $this->trackingCategories->addTrackingCategory( |
562 | $metadata, $key, $page |
563 | ); |
564 | } |
565 | |
566 | /** @inheritDoc */ |
567 | public function logLinterData( IPageConfig $pageConfig, array $lints ): void { |
568 | if ( $this->readOnlyMode->isReadOnly() ) { |
569 | return; |
570 | } |
571 | |
572 | $revId = $pageConfig->getRevisionId(); |
573 | $title = Title::newFromLinkTarget( |
574 | $pageConfig->getLinkTarget() |
575 | )->getPrefixedText(); |
576 | $pageInfo = $this->getPageInfo( $pageConfig, [ $title ] ); |
577 | $latest = $pageInfo[$title]['revId']; |
578 | |
579 | // Only send the request if it the latest revision |
580 | if ( $revId !== null && $revId === $latest ) { |
581 | $this->hookRunner->onParserLogLinterData( |
582 | $title, $revId, $lints |
583 | ); |
584 | } |
585 | } |
586 | |
587 | } |