Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
75.42% |
178 / 236 |
|
57.14% |
16 / 28 |
CRAP | |
0.00% |
0 / 1 |
IndexContentHandler | |
75.42% |
178 / 236 |
|
57.14% |
16 / 28 |
165.01 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getContentClass | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getParser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
buildParser | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
canBeUsedOn | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
serializeContent | |
58.82% |
10 / 17 |
|
0.00% |
0 / 1 |
10.42 | |||
unserializeContent | |
63.64% |
7 / 11 |
|
0.00% |
0 / 1 |
6.20 | |||
serializeContentInWikitext | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
unserializeContentInWikitext | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
8 | |||
serializeContentInJson | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
unserializeContentInJson | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
8 | |||
getActionOverrides | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getSlotDiffRendererWithOptions | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
makeEmptyContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
merge3 | |
96.15% |
25 / 26 |
|
0.00% |
0 / 1 |
8 | |||
arrayMerge3 | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
makeRedirectContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
supportsRedirects | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isParserCacheSupported | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validateSave | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
5.01 | |||
getSecondaryDataUpdates | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getDeletionUpdates | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
preSaveTransform | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
3.01 | |||
supportsPreloadContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
preloadTransform | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
3.01 | |||
fillParserOutput | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
30 | |||
buildIndexQualityStatsUpdate | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
buildIndexQualityStatsDelete | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace ProofreadPage\Index; |
4 | |
5 | use Content; |
6 | use IContextSource; |
7 | use MediaWiki\Content\Renderer\ContentParseParams; |
8 | use MediaWiki\Content\Transform\PreloadTransformParams; |
9 | use MediaWiki\Content\Transform\PreSaveTransformParams; |
10 | use MediaWiki\Content\ValidationParams; |
11 | use MediaWiki\MediaWikiServices; |
12 | use MediaWiki\Parser\ParserOutput; |
13 | use MediaWiki\Revision\SlotRenderingProvider; |
14 | use MediaWiki\Title\Title; |
15 | use MWContentSerializationException; |
16 | use Parser; |
17 | use ParserOptions; |
18 | use PPFrame; |
19 | use ProofreadPage\Context; |
20 | use ProofreadPage\Link; |
21 | use ProofreadPage\MultiFormatSerializerUtils; |
22 | use StatusValue; |
23 | use TextContentHandler; |
24 | use UnexpectedValueException; |
25 | use WikitextContent; |
26 | use WikitextContentHandler; |
27 | |
28 | /** |
29 | * @license GPL-2.0-or-later |
30 | * |
31 | * Content handler for a Index: pages |
32 | */ |
33 | class IndexContentHandler extends TextContentHandler { |
34 | |
35 | use MultiFormatSerializerUtils; |
36 | |
37 | /** |
38 | * @var WikitextContentHandler |
39 | */ |
40 | private $wikitextContentHandler; |
41 | |
42 | /** |
43 | * @var Parser |
44 | */ |
45 | private $parser; |
46 | |
47 | /** |
48 | * @var WikitextLinksExtractor |
49 | */ |
50 | private $wikitextLinksExtractor; |
51 | |
52 | /** |
53 | * @inheritDoc |
54 | */ |
55 | public function __construct( $modelId = CONTENT_MODEL_PROOFREAD_INDEX ) { |
56 | $this->wikitextContentHandler = MediaWikiServices::getInstance() |
57 | ->getContentHandlerFactory() |
58 | ->getContentHandler( CONTENT_MODEL_WIKITEXT ); |
59 | $this->parser = $this->buildParser(); |
60 | $this->wikitextLinksExtractor = new WikitextLinksExtractor(); |
61 | |
62 | parent::__construct( $modelId, [ CONTENT_FORMAT_WIKITEXT, CONTENT_FORMAT_JSON ] ); |
63 | } |
64 | |
65 | /** |
66 | * @return string |
67 | */ |
68 | protected function getContentClass() { |
69 | return IndexContent::class; |
70 | } |
71 | |
72 | /** |
73 | * Warning: should not be used outside of IndexContent |
74 | * @return Parser |
75 | */ |
76 | public function getParser() { |
77 | return $this->parser; |
78 | } |
79 | |
80 | private function buildParser() { |
81 | $parser = MediaWikiServices::getInstance()->getParserFactory()->create(); |
82 | $parser->startExternalParse( |
83 | null, ParserOptions::newFromAnon(), Parser::OT_PLAIN |
84 | ); |
85 | return $parser; |
86 | } |
87 | |
88 | /** |
89 | * @inheritDoc |
90 | */ |
91 | public function canBeUsedOn( Title $title ) { |
92 | return parent::canBeUsedOn( $title ) && |
93 | $title->getNamespace() === Context::getDefaultContext()->getIndexNamespaceId(); |
94 | } |
95 | |
96 | /** |
97 | * @inheritDoc |
98 | */ |
99 | public function serializeContent( Content $content, $format = null ) { |
100 | // if not given, default is Wikitext |
101 | $format = $format ?: CONTENT_FORMAT_WIKITEXT; |
102 | |
103 | $this->checkFormat( $format ); |
104 | |
105 | // redirects can only be wikitext |
106 | if ( $content instanceof IndexRedirectContent ) { |
107 | self::assertFormatSuitableForRedirect( $format ); |
108 | return '#REDIRECT [[' . $content->getRedirectTarget()->getFullText() . ']]'; |
109 | } |
110 | |
111 | if ( !( $content instanceof IndexContent ) ) { |
112 | throw new MWContentSerializationException( |
113 | 'IndexContentHandler could only serialize IndexContent' |
114 | ); |
115 | } |
116 | |
117 | switch ( $format ) { |
118 | case CONTENT_FORMAT_JSON: |
119 | return $this->serializeContentInJson( $content ); |
120 | case CONTENT_FORMAT_WIKITEXT: |
121 | return $this->serializeContentInWikitext( $content ); |
122 | default: |
123 | throw new MWContentSerializationException( |
124 | "Format '$format' is not supported for serialization of content model " . |
125 | $this->getModelID() |
126 | ); |
127 | } |
128 | } |
129 | |
130 | /** |
131 | * @inheritDoc |
132 | */ |
133 | public function unserializeContent( $text, $format = null ) { |
134 | $this->checkFormat( $format ); |
135 | |
136 | if ( $format === null ) { |
137 | $format = self::guessDataFormat( $text, false ); |
138 | } |
139 | |
140 | switch ( $format ) { |
141 | case CONTENT_FORMAT_JSON: |
142 | return $this->unserializeContentInJson( $text ); |
143 | case CONTENT_FORMAT_WIKITEXT: |
144 | return $this->unserializeContentInWikitext( $text ); |
145 | default: |
146 | throw new UnexpectedValueException( |
147 | "Format '$format' is not supported for unserialization of content model " . |
148 | $this->getModelID() |
149 | ); |
150 | } |
151 | } |
152 | |
153 | /** |
154 | * @param IndexContent $content |
155 | * @throws MWContentSerializationException |
156 | * @return string |
157 | */ |
158 | private function serializeContentInWikitext( IndexContent $content ): string { |
159 | $text = '{{:MediaWiki:Proofreadpage_index_template'; |
160 | /** @var WikitextContent $value */ |
161 | foreach ( $content->getFields() as $key => $value ) { |
162 | $text .= "\n|" . $key . '=' . $value->serialize(); |
163 | } |
164 | $text .= "\n}}"; |
165 | |
166 | foreach ( $content->getCategories() as $category ) { |
167 | $text .= "\n[[" . $category->getFullText() . ']]'; |
168 | } |
169 | |
170 | return $text; |
171 | } |
172 | |
173 | /** |
174 | * @param string $text |
175 | * @return IndexRedirectContent|IndexContent |
176 | */ |
177 | private function unserializeContentInWikitext( $text ) { |
178 | $fullWikitext = new WikitextContent( $text ); |
179 | if ( $fullWikitext->isRedirect() ) { |
180 | return new IndexRedirectContent( $fullWikitext->getRedirectTarget() ); |
181 | } |
182 | |
183 | $dom = $this->parser->preprocessToDom( $text ); |
184 | $customFieldsValues = []; |
185 | $categories = []; |
186 | // We iterate on the main components of the Wikitext serialization |
187 | for ( $child = $dom->getFirstChild(); $child; $child = $child->getNextSibling() ) { |
188 | if ( $child->getName() === 'template' ) { |
189 | // It's a template call, we extract the fields |
190 | $frame = $this->parser->getPreprocessor()->newFrame(); |
191 | $childFrame = $frame->newChild( $child->getChildrenOfType( 'part' ) ); |
192 | // @phan-suppress-next-line PhanUndeclaredProperty |
193 | foreach ( $childFrame->namedArgs as $varName => $value ) { |
194 | $value = $this->parser->getStripState()->unstripBoth( |
195 | $frame->expand( $value, PPFrame::RECOVER_ORIG ) |
196 | ); |
197 | |
198 | if ( substr( $value, -1 ) === "\n" ) { |
199 | // We strip one "\n" |
200 | $value = substr( $value, 0, -1 ); |
201 | } |
202 | $customFieldsValues[$varName] = new WikitextContent( $value ); |
203 | } |
204 | } elseif ( $child->getName() === '#text' ) { |
205 | // It's some text, we look for category links |
206 | $text = $this->parser->getStripState()->unstripBoth( strval( $child ) ); |
207 | $categoryLinks = $this->wikitextLinksExtractor->getLinksToNamespace( $text, NS_CATEGORY ); |
208 | /** @var Link $categoryLink */ |
209 | foreach ( $categoryLinks as $categoryLink ) { |
210 | $categories[] = $categoryLink->getTarget(); |
211 | } |
212 | } |
213 | } |
214 | return new IndexContent( $customFieldsValues, $categories ); |
215 | } |
216 | |
217 | /** |
218 | * @param IndexContent $content |
219 | * @throws MWContentSerializationException |
220 | * @return string |
221 | */ |
222 | public function serializeContentInJson( IndexContent $content ): string { |
223 | $data = [ |
224 | 'fields' => [], |
225 | 'categories' => [], |
226 | ]; |
227 | |
228 | /** @var WikitextContent $value */ |
229 | foreach ( $content->getFields() as $key => $value ) { |
230 | $data['fields'][ $key ] = $value->serialize(); |
231 | } |
232 | |
233 | foreach ( $content->getCategories() as $category ) { |
234 | $data['categories'][] = $category->getText(); |
235 | } |
236 | |
237 | return json_encode( $data, JSON_UNESCAPED_UNICODE ); |
238 | } |
239 | |
240 | /** |
241 | * @param string $text |
242 | * @return IndexContent |
243 | * @throws MWContentSerializationException |
244 | */ |
245 | private function unserializeContentInJson( $text ): IndexContent { |
246 | $array = json_decode( $text, true ); |
247 | |
248 | if ( !is_array( $array ) ) { |
249 | throw new MWContentSerializationException( |
250 | 'The serialization is an invalid JSON array.' |
251 | ); |
252 | } |
253 | |
254 | $customFieldsValues = []; |
255 | $categories = []; |
256 | |
257 | if ( isset( $array['categories'] ) ) { |
258 | |
259 | self::assertArrayValueIsArray( $array, 'categories' ); |
260 | self::assertArrayIsSequential( $array['categories'], 'categories' ); |
261 | self::assertContainsOnlyStrings( $array['categories'], false, 'categories' ); |
262 | |
263 | /** @var string $category */ |
264 | foreach ( $array['categories'] as $category ) { |
265 | $title = Title::makeTitleSafe( NS_CATEGORY, $category ); |
266 | |
267 | if ( $title ) { |
268 | $categories[] = $title; |
269 | } else { |
270 | throw new MWContentSerializationException( |
271 | "The category title '$category' is invalid." |
272 | ); |
273 | } |
274 | } |
275 | } |
276 | |
277 | if ( isset( $array['fields'] ) ) { |
278 | self::assertArrayValueIsArray( $array, 'fields' ); |
279 | // for now, all supported 'type' values are encoded as strings |
280 | // we may relax this in future if we accept object-type date in fields |
281 | self::assertContainsOnlyStrings( $array['fields'], true, 'fields' ); |
282 | |
283 | /** @var string $fieldValue */ |
284 | foreach ( $array['fields'] as $fieldKey => $fieldValue ) { |
285 | if ( $fieldValue !== null ) { |
286 | $customFieldsValues[$fieldKey] = new WikitextContent( $fieldValue ); |
287 | } |
288 | } |
289 | } |
290 | |
291 | return new IndexContent( $customFieldsValues, $categories ); |
292 | } |
293 | |
294 | /** |
295 | * @inheritDoc |
296 | */ |
297 | public function getActionOverrides() { |
298 | return [ |
299 | 'edit' => IndexEditAction::class, |
300 | 'submit' => IndexSubmitAction::class |
301 | ]; |
302 | } |
303 | |
304 | /** |
305 | * @inheritDoc |
306 | */ |
307 | protected function getSlotDiffRendererWithOptions( IContextSource $context, $options = [] ) { |
308 | return new IndexSlotDiffRenderer( |
309 | $context, |
310 | Context::getDefaultContext()->getCustomIndexFieldsParser(), |
311 | $this->wikitextContentHandler->getSlotDiffRenderer( $context ) |
312 | ); |
313 | } |
314 | |
315 | /** |
316 | * @inheritDoc |
317 | */ |
318 | public function makeEmptyContent() { |
319 | return new IndexContent( [] ); |
320 | } |
321 | |
322 | /** |
323 | * @inheritDoc |
324 | */ |
325 | public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { |
326 | $this->checkModelID( $oldContent->getModel() ); |
327 | $this->checkModelID( $myContent->getModel() ); |
328 | $this->checkModelID( $yourContent->getModel() ); |
329 | |
330 | if ( !( $oldContent instanceof IndexContent && $myContent instanceof IndexContent && |
331 | $yourContent instanceof IndexContent ) |
332 | ) { |
333 | return false; |
334 | } |
335 | |
336 | $oldFields = $oldContent->getFields(); |
337 | $myFields = $myContent->getFields(); |
338 | $yourFields = $yourContent->getFields(); |
339 | |
340 | // We adds yourFields to myFields |
341 | foreach ( $yourFields as $key => $yourValue ) { |
342 | if ( array_key_exists( $key, $myFields ) ) { |
343 | $oldValue = array_key_exists( $key, $oldFields ) |
344 | ? $oldFields[$key] |
345 | : $this->wikitextContentHandler->makeEmptyContent(); |
346 | $myFields[$key] = $this->wikitextContentHandler->merge3( |
347 | $oldValue, $myFields[$key], $yourValue |
348 | ); |
349 | |
350 | if ( $myFields[$key] === false ) { |
351 | return false; |
352 | } |
353 | } else { |
354 | $myFields[$key] = $yourValue; |
355 | } |
356 | } |
357 | |
358 | // Categories |
359 | $categories = $this->arrayMerge3( |
360 | $oldContent->getCategories(), |
361 | $myContent->getCategories(), |
362 | $yourContent->getCategories() |
363 | ); |
364 | |
365 | return new IndexContent( $myFields, $categories ); |
366 | } |
367 | |
368 | /** |
369 | * @param array $old |
370 | * @param array $my |
371 | * @param array $your |
372 | * @return array |
373 | */ |
374 | private function arrayMerge3( array $old, array $my, array $your ) { |
375 | // TODO: detection of deletions |
376 | return array_unique( array_merge( $my, $your ) ); |
377 | } |
378 | |
379 | /** |
380 | * @inheritDoc |
381 | */ |
382 | public function makeRedirectContent( Title $destination, $text = '' ) { |
383 | return new IndexRedirectContent( $destination ); |
384 | } |
385 | |
386 | /** |
387 | * @inheritDoc |
388 | */ |
389 | public function supportsRedirects() { |
390 | return true; |
391 | } |
392 | |
393 | /** |
394 | * @inheritDoc |
395 | */ |
396 | public function isParserCacheSupported() { |
397 | return true; |
398 | } |
399 | |
400 | /** |
401 | * @inheritDoc |
402 | */ |
403 | public function validateSave( |
404 | Content $content, |
405 | ValidationParams $validationParams |
406 | ) { |
407 | if ( $content instanceof IndexRedirectContent ) { |
408 | return StatusValue::newGood(); |
409 | } else { |
410 | '@phan-var IndexContent $content'; |
411 | if ( !$content->isValid() ) { |
412 | return StatusValue::newFatal( 'invalid-content-data' ); |
413 | } |
414 | |
415 | // Get list of pages titles |
416 | $links = $content->getLinksToNamespace( |
417 | Context::getDefaultContext()->getPageNamespaceId() |
418 | ); |
419 | $linksTitle = []; |
420 | foreach ( $links as $link ) { |
421 | $linksTitle[] = $link->getTarget(); |
422 | } |
423 | |
424 | if ( count( $linksTitle ) !== count( array_unique( $linksTitle ) ) ) { |
425 | return StatusValue::newFatal( 'proofreadpage_indexdupetext' ); |
426 | } |
427 | |
428 | return StatusValue::newGood(); |
429 | } |
430 | } |
431 | |
432 | /** |
433 | * @inheritDoc |
434 | */ |
435 | public function getSecondaryDataUpdates( |
436 | Title $title, |
437 | Content $content, |
438 | $role, |
439 | SlotRenderingProvider $slotOutput |
440 | ) { |
441 | $updates = parent::getSecondaryDataUpdates( $title, $content, $role, $slotOutput ); |
442 | $updates[] = ( $content instanceof IndexContent ) |
443 | ? $this->buildIndexQualityStatsUpdate( $title, $content ) |
444 | : $this->buildIndexQualityStatsDelete( $title ); |
445 | return $updates; |
446 | } |
447 | |
448 | /** |
449 | * @inheritDoc |
450 | */ |
451 | public function getDeletionUpdates( Title $title, $role ) { |
452 | $updates = parent::getDeletionUpdates( $title, $role ); |
453 | $updates[] = $this->buildIndexQualityStatsDelete( $title ); |
454 | return $updates; |
455 | } |
456 | |
457 | /** |
458 | * @inheritDoc |
459 | */ |
460 | public function preSaveTransform( |
461 | Content $content, |
462 | PreSaveTransformParams $pstParams |
463 | ): Content { |
464 | $contentHandlerFactory = MediaWikiServices::getInstance()->getContentHandlerFactory(); |
465 | |
466 | if ( $content instanceof IndexRedirectContent ) { |
467 | return $content; |
468 | } |
469 | |
470 | '@phan-var IndexContent $content'; |
471 | $fields = []; |
472 | foreach ( $content->getFields() as $key => $value ) { |
473 | $contentHandler = $contentHandlerFactory->getContentHandler( $value->getModel() ); |
474 | $fields[$key] = $contentHandler->preSaveTransform( |
475 | $value, |
476 | $pstParams |
477 | ); |
478 | } |
479 | |
480 | $contentClass = $this->getContentClass(); |
481 | return new $contentClass( $fields, $content->getCategories() ); |
482 | } |
483 | |
484 | /** |
485 | * @inheritDoc |
486 | */ |
487 | public function supportsPreloadContent(): bool { |
488 | return true; |
489 | } |
490 | |
491 | /** |
492 | * @inheritDoc |
493 | */ |
494 | public function preloadTransform( |
495 | Content $content, |
496 | PreloadTransformParams $pltParams |
497 | ): Content { |
498 | $contentHandlerFactory = MediaWikiServices::getInstance()->getContentHandlerFactory(); |
499 | |
500 | if ( $content instanceof IndexRedirectContent ) { |
501 | return $content; |
502 | } |
503 | |
504 | '@phan-var IndexContent $content'; |
505 | $fields = []; |
506 | |
507 | foreach ( $content->getFields() as $key => $value ) { |
508 | $contentHandler = $contentHandlerFactory->getContentHandler( $value->getModel() ); |
509 | $fields[$key] = $contentHandler->preloadTransform( |
510 | $value, |
511 | $pltParams |
512 | ); |
513 | } |
514 | |
515 | $contentClass = $this->getContentClass(); |
516 | return new $contentClass( $fields, $content->getCategories() ); |
517 | } |
518 | |
519 | /** |
520 | * @inheritDoc |
521 | */ |
522 | protected function fillParserOutput( |
523 | Content $content, |
524 | ContentParseParams $cpoParams, |
525 | ParserOutput &$parserOutput |
526 | ) { |
527 | $title = Title::castFromPageReference( $cpoParams->getPage() ); |
528 | $parserOptions = $cpoParams->getParserOptions(); |
529 | |
530 | if ( $content instanceof IndexRedirectContent ) { |
531 | $parserOutput->addLink( $content->getRedirectTarget() ); |
532 | if ( $cpoParams->getGenerateHtml() ) { |
533 | $parserOutput->setText( '' ); |
534 | $parserOutput->setRedirectHeader( |
535 | MediaWikiServices::getInstance()->getLinkRenderer()->makeRedirectHeader( |
536 | $title->getPageLanguage(), $content->getRedirectTarget() |
537 | ) ); |
538 | $parserOutput->addModuleStyles( [ 'mediawiki.action.view.redirectPage' ] ); |
539 | } |
540 | } else { |
541 | '@phan-var IndexContent $content'; |
542 | $parserHelper = new ParserHelper( $title, $parserOptions ); |
543 | |
544 | // Start with the default index styles |
545 | // @phan-suppress-next-line PhanTypeMismatchArgument |
546 | $indexTs = new IndexTemplateStyles( $title ); |
547 | $text = $indexTs->getIndexTemplateStyles( null ); |
548 | |
549 | // make sure the template starts on a new line in case it starts |
550 | // with something like '{|' |
551 | if ( $text ) { |
552 | $text .= "\n"; |
553 | } |
554 | |
555 | // We retrieve the view template |
556 | [ $templateText, $templateTitle ] = $parserHelper->fetchTemplateTextAndTitle( |
557 | Title::makeTitle( NS_MEDIAWIKI, 'Proofreadpage index template' ) |
558 | ); |
559 | |
560 | // We replace the arguments calls by their values |
561 | $text .= $parserHelper->expandTemplateArgs( |
562 | $templateText, |
563 | array_map( static function ( Content $content ) { |
564 | return $content->serialize( CONTENT_FORMAT_WIKITEXT ); |
565 | }, $content->getFields() ) |
566 | ); |
567 | |
568 | // Force no section edit links |
569 | $text = '__NOEDITSECTION__' . $text; |
570 | |
571 | // We do the final rendering |
572 | $parserOutput = MediaWikiServices::getInstance()->getParser() |
573 | // @phan-suppress-next-line PhanTypeMismatchArgument |
574 | ->parse( $text, $title, $parserOptions, true, true, $cpoParams->getRevId() ); |
575 | $parserOutput->addTemplate( $templateTitle, |
576 | $templateTitle->getArticleID(), |
577 | $templateTitle->getLatestRevID() |
578 | ); |
579 | |
580 | foreach ( $content->getCategories() as $category ) { |
581 | $parserOutput->addCategory( $category->getDBkey(), $category->getText() ); |
582 | } |
583 | } |
584 | } |
585 | |
586 | /** |
587 | * @param Title $title |
588 | * @param IndexContent $content |
589 | * @return UpdateIndexQualityStats |
590 | */ |
591 | private function buildIndexQualityStatsUpdate( Title $title, IndexContent $content ): UpdateIndexQualityStats { |
592 | $context = Context::getDefaultContext(); |
593 | return new UpdateIndexQualityStats( |
594 | MediaWikiServices::getInstance()->getDBLoadBalancer(), |
595 | $context->getPageQualityLevelLookup(), |
596 | $context->getPaginationFactory()->buildPaginationForIndexContent( $title, $content ), |
597 | $title |
598 | ); |
599 | } |
600 | |
601 | /** |
602 | * @param Title $title |
603 | * @return DeleteIndexQualityStats |
604 | */ |
605 | private function buildIndexQualityStatsDelete( Title $title ): DeleteIndexQualityStats { |
606 | return new DeleteIndexQualityStats( MediaWikiServices::getInstance()->getDBLoadBalancer(), $title ); |
607 | } |
608 | } |