MediaWiki REL1_32
DerivedPageDataUpdaterTest.php
Go to the documentation of this file.
1<?php
2
4
19use PHPUnit\Framework\MockObject\MockObject;
23use User;
24use Wikimedia\TestingAccessWrapper;
27
34
35 public function tearDown() {
36 MWTimestamp::setFakeTime( false );
37
38 parent::tearDown();
39 }
40
46 private function getTitle( $title ) {
47 return Title::makeTitleSafe( $this->getDefaultWikitextNS(), $title );
48 }
49
55 private function getPage( $title ) {
56 $title = ( $title instanceof Title ) ? $title : $this->getTitle( $title );
57
58 return WikiPage::factory( $title );
59 }
60
66 private function getDerivedPageDataUpdater( $page, RevisionRecord $rec = null ) {
67 if ( is_string( $page ) || $page instanceof Title ) {
68 $page = $this->getPage( $page );
69 }
70
71 $page = TestingAccessWrapper::newFromObject( $page );
72 return $page->getDerivedDataUpdater( null, $rec );
73 }
74
84 private function createRevision( WikiPage $page, $summary, $content = null ) {
85 $user = $this->getTestUser()->getUser();
86 $comment = CommentStoreComment::newUnsavedComment( $summary );
87
88 if ( $content === null || is_string( $content ) ) {
89 $content = new WikitextContent( $content ?? $summary );
90 }
91
92 if ( !is_array( $content ) ) {
93 $content = [ 'main' => $content ];
94 }
95
96 $this->getDerivedPageDataUpdater( $page ); // flush cached instance before.
97
98 $updater = $page->newPageUpdater( $user );
99
100 foreach ( $content as $role => $c ) {
101 $updater->setContent( $role, $c );
102 }
103
104 $rev = $updater->saveRevision( $comment );
105
106 $this->getDerivedPageDataUpdater( $page ); // flush cached instance after.
107 return $rev;
108 }
109
110 // TODO: test setArticleCountMethod() and isCountable();
111 // TODO: test isRedirect() and wasRedirect()
112
117 $user = $this->getTestUser()->getUser();
118 $page = $this->getPage( __METHOD__ );
119
120 $parentRev = $this->createRevision( $page, 'first' );
121
122 $mainContent = new WikitextContent( 'Lorem ipsum' );
123
124 $update = new RevisionSlotsUpdate();
125 $update->modifyContent( SlotRecord::MAIN, $mainContent );
126 $updater = $this->getDerivedPageDataUpdater( $page );
127 $updater->prepareContent( $user, $update, false );
128
129 $options1 = $updater->getCanonicalParserOptions();
130 $this->assertSame( MediaWikiServices::getInstance()->getContentLanguage(),
131 $options1->getUserLangObj() );
132
133 $speculativeId = $options1->getSpeculativeRevId();
134 $this->assertSame( $parentRev->getId() + 1, $speculativeId );
135
136 $rev = $this->makeRevision(
137 $page->getTitle(),
138 $update,
139 $user,
140 $parentRev->getId() + 7,
141 $parentRev->getId()
142 );
143 $updater->prepareUpdate( $rev );
144
145 $options2 = $updater->getCanonicalParserOptions();
146
147 $currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
148 $this->assertSame( $rev->getId(), $currentRev->getId() );
149 }
150
155 public function testGrabCurrentRevision() {
156 $page = $this->getPage( __METHOD__ );
157
158 $updater0 = $this->getDerivedPageDataUpdater( $page );
159 $this->assertNull( $updater0->grabCurrentRevision() );
160 $this->assertFalse( $updater0->pageExisted() );
161
162 $rev1 = $this->createRevision( $page, 'first' );
163 $updater1 = $this->getDerivedPageDataUpdater( $page );
164 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
165 $this->assertFalse( $updater0->pageExisted() );
166 $this->assertTrue( $updater1->pageExisted() );
167
168 $rev2 = $this->createRevision( $page, 'second' );
169 $updater2 = $this->getDerivedPageDataUpdater( $page );
170 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
171 $this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
172 }
173
188 public function testPrepareContent() {
189 $sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
190 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
191
192 $this->assertFalse( $updater->isContentPrepared() );
193
194 // TODO: test stash
195 // TODO: MCR: Test multiple slots. Test slot removal.
196 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
197 $auxContent = new WikitextContent( 'inherited ~~~ content' );
198 $auxSlot = SlotRecord::newSaved(
199 10, 7, 'tt:7',
200 SlotRecord::newUnsaved( 'aux', $auxContent )
201 );
202
203 $update = new RevisionSlotsUpdate();
204 $update->modifyContent( SlotRecord::MAIN, $mainContent );
205 $update->modifySlot( SlotRecord::newInherited( $auxSlot ) );
206 // TODO: MCR: test removing slots!
207
208 $updater->prepareContent( $sysop, $update, false );
209
210 // second be ok to call again with the same params
211 $updater->prepareContent( $sysop, $update, false );
212
213 $this->assertNull( $updater->grabCurrentRevision() );
214 $this->assertTrue( $updater->isContentPrepared() );
215 $this->assertFalse( $updater->isUpdatePrepared() );
216 $this->assertFalse( $updater->pageExisted() );
217 $this->assertTrue( $updater->isCreation() );
218 $this->assertTrue( $updater->isChange() );
219 $this->assertFalse( $updater->isContentDeleted() );
220
221 $this->assertNotNull( $updater->getRevision() );
222 $this->assertNotNull( $updater->getRenderedRevision() );
223
224 $this->assertEquals( [ 'main', 'aux' ], $updater->getSlots()->getSlotRoles() );
225 $this->assertEquals( [ 'main' ], array_keys( $updater->getSlots()->getOriginalSlots() ) );
226 $this->assertEquals( [ 'aux' ], array_keys( $updater->getSlots()->getInheritedSlots() ) );
227 $this->assertEquals( [ 'main', 'aux' ], $updater->getModifiedSlotRoles() );
228 $this->assertEquals( [ 'main', 'aux' ], $updater->getTouchedSlotRoles() );
229
230 $mainSlot = $updater->getRawSlot( SlotRecord::MAIN );
231 $this->assertInstanceOf( SlotRecord::class, $mainSlot );
232 $this->assertNotContains( '~~~', $mainSlot->getContent()->serialize(), 'PST should apply.' );
233 $this->assertContains( $sysop->getName(), $mainSlot->getContent()->serialize() );
234
235 $auxSlot = $updater->getRawSlot( 'aux' );
236 $this->assertInstanceOf( SlotRecord::class, $auxSlot );
237 $this->assertContains( '~~~', $auxSlot->getContent()->serialize(), 'No PST should apply.' );
238
239 $mainOutput = $updater->getCanonicalParserOutput();
240 $this->assertContains( 'first', $mainOutput->getText() );
241 $this->assertContains( '<a ', $mainOutput->getText() );
242 $this->assertNotEmpty( $mainOutput->getLinks() );
243
244 $canonicalOutput = $updater->getCanonicalParserOutput();
245 $this->assertContains( 'first', $canonicalOutput->getText() );
246 $this->assertContains( '<a ', $canonicalOutput->getText() );
247 $this->assertContains( 'inherited ', $canonicalOutput->getText() );
248 $this->assertNotEmpty( $canonicalOutput->getLinks() );
249 }
250
257 public function testPrepareContentInherit() {
258 $sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
259 $page = $this->getPage( __METHOD__ );
260
261 $mainContent1 = new WikitextContent( 'first [[main]] ({{REVISIONUSER}}) #~~~#' );
262 $mainContent2 = new WikitextContent( 'second ({{subst:REVISIONUSER}}) #~~~#' );
263
264 $rev = $this->createRevision( $page, 'first', $mainContent1 );
265 $mainContent1 = $rev->getContent( SlotRecord::MAIN ); // get post-pst content
266 $userName = $rev->getUser()->getName();
267 $sysopName = $sysop->getName();
268
269 $update = new RevisionSlotsUpdate();
270 $update->modifyContent( SlotRecord::MAIN, $mainContent1 );
271 $updater1 = $this->getDerivedPageDataUpdater( $page );
272 $updater1->prepareContent( $sysop, $update, false );
273
274 $this->assertNotNull( $updater1->grabCurrentRevision() );
275 $this->assertTrue( $updater1->isContentPrepared() );
276 $this->assertTrue( $updater1->pageExisted() );
277 $this->assertFalse( $updater1->isCreation() );
278 $this->assertFalse( $updater1->isChange() );
279
280 $this->assertNotNull( $updater1->getRevision() );
281 $this->assertNotNull( $updater1->getRenderedRevision() );
282
283 // parser-output for null-edit uses the original author's name
284 $html = $updater1->getRenderedRevision()->getRevisionParserOutput()->getText();
285 $this->assertNotContains( $sysopName, $html, '{{REVISIONUSER}}' );
286 $this->assertNotContains( '{{REVISIONUSER}}', $html, '{{REVISIONUSER}}' );
287 $this->assertNotContains( '~~~', $html, 'signature ~~~' );
288 $this->assertContains( '(' . $userName . ')', $html, '{{REVISIONUSER}}' );
289 $this->assertContains( '>' . $userName . '<', $html, 'signature ~~~' );
290
291 // TODO: MCR: test inheritance from parent
292 $update = new RevisionSlotsUpdate();
293 $update->modifyContent( SlotRecord::MAIN, $mainContent2 );
294 $updater2 = $this->getDerivedPageDataUpdater( $page );
295 $updater2->prepareContent( $sysop, $update, false );
296
297 // non-null edit use the new user name in PST
298 $pstText = $updater2->getSlots()->getContent( SlotRecord::MAIN )->serialize();
299 $this->assertNotContains( '{{subst:REVISIONUSER}}', $pstText, '{{subst:REVISIONUSER}}' );
300 $this->assertNotContains( '~~~', $pstText, 'signature ~~~' );
301 $this->assertContains( '(' . $sysopName . ')', $pstText, '{{subst:REVISIONUSER}}' );
302 $this->assertContains( ':' . $sysopName . '|', $pstText, 'signature ~~~' );
303
304 $this->assertFalse( $updater2->isCreation() );
305 $this->assertTrue( $updater2->isChange() );
306 }
307
308 // TODO: test failure of prepareContent() when called again...
309 // - with different user
310 // - with different update
311 // - after calling prepareUpdate()
312
325 public function testPrepareUpdate() {
326 $page = $this->getPage( __METHOD__ );
327
328 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
329 $rev1 = $this->createRevision( $page, 'first', $mainContent1 );
330 $updater1 = $this->getDerivedPageDataUpdater( $page, $rev1 );
331
332 $options = []; // TODO: test *all* the options...
333 $updater1->prepareUpdate( $rev1, $options );
334
335 $this->assertTrue( $updater1->isUpdatePrepared() );
336 $this->assertTrue( $updater1->isContentPrepared() );
337 $this->assertTrue( $updater1->isCreation() );
338 $this->assertTrue( $updater1->isChange() );
339 $this->assertFalse( $updater1->isContentDeleted() );
340
341 $this->assertNotNull( $updater1->getRevision() );
342 $this->assertNotNull( $updater1->getRenderedRevision() );
343
344 $this->assertEquals( [ 'main' ], $updater1->getSlots()->getSlotRoles() );
345 $this->assertEquals( [ 'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
346 $this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
347 $this->assertEquals( [ 'main' ], $updater1->getModifiedSlotRoles() );
348 $this->assertEquals( [ 'main' ], $updater1->getTouchedSlotRoles() );
349
350 // TODO: MCR: test multiple slots, test slot removal!
351
352 $this->assertInstanceOf( SlotRecord::class, $updater1->getRawSlot( SlotRecord::MAIN ) );
353 $this->assertNotContains( '~~~~', $updater1->getRawContent( SlotRecord::MAIN )->serialize() );
354
355 $mainOutput = $updater1->getCanonicalParserOutput();
356 $this->assertContains( 'first', $mainOutput->getText() );
357 $this->assertContains( '<a ', $mainOutput->getText() );
358 $this->assertNotEmpty( $mainOutput->getLinks() );
359
360 $canonicalOutput = $updater1->getCanonicalParserOutput();
361 $this->assertContains( 'first', $canonicalOutput->getText() );
362 $this->assertContains( '<a ', $canonicalOutput->getText() );
363 $this->assertNotEmpty( $canonicalOutput->getLinks() );
364
365 $mainContent2 = new WikitextContent( 'second' );
366 $rev2 = $this->createRevision( $page, 'second', $mainContent2 );
367 $updater2 = $this->getDerivedPageDataUpdater( $page, $rev2 );
368
369 $options = []; // TODO: test *all* the options...
370 $updater2->prepareUpdate( $rev2, $options );
371
372 $this->assertFalse( $updater2->isCreation() );
373 $this->assertTrue( $updater2->isChange() );
374
375 $canonicalOutput = $updater2->getCanonicalParserOutput();
376 $this->assertContains( 'second', $canonicalOutput->getText() );
377 }
378
383 $user = $this->getTestUser()->getUser();
384 $page = $this->getPage( __METHOD__ );
385
386 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
387
388 $update = new RevisionSlotsUpdate();
389 $update->modifyContent( SlotRecord::MAIN, $mainContent1 );
390 $updater = $this->getDerivedPageDataUpdater( $page );
391 $updater->prepareContent( $user, $update, false );
392
393 $mainOutput = $updater->getSlotParserOutput( SlotRecord::MAIN );
394 $canonicalOutput = $updater->getCanonicalParserOutput();
395
396 $rev = $this->createRevision( $page, 'first', $mainContent1 );
397
398 $options = []; // TODO: test *all* the options...
399 $updater->prepareUpdate( $rev, $options );
400
401 $this->assertTrue( $updater->isUpdatePrepared() );
402 $this->assertTrue( $updater->isContentPrepared() );
403
404 $this->assertSame( $mainOutput, $updater->getSlotParserOutput( SlotRecord::MAIN ) );
405 $this->assertSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
406 }
407
413 $user = $this->getTestUser()->getUser();
414 $page = $this->getPage( __METHOD__ );
415
416 $mainContent1 = new WikitextContent( 'first --{{REVISIONID}}--' );
417
418 $update = new RevisionSlotsUpdate();
419 $update->modifyContent( SlotRecord::MAIN, $mainContent1 );
420 $updater = $this->getDerivedPageDataUpdater( $page );
421 $updater->prepareContent( $user, $update, false );
422
423 $mainOutput = $updater->getSlotParserOutput( SlotRecord::MAIN );
424 $canonicalOutput = $updater->getCanonicalParserOutput();
425
426 // prevent optimization on matching speculative ID
427 $mainOutput->setSpeculativeRevIdUsed( 0 );
428 $canonicalOutput->setSpeculativeRevIdUsed( 0 );
429
430 $rev = $this->createRevision( $page, 'first', $mainContent1 );
431
432 $options = []; // TODO: test *all* the options...
433 $updater->prepareUpdate( $rev, $options );
434
435 $this->assertTrue( $updater->isUpdatePrepared() );
436 $this->assertTrue( $updater->isContentPrepared() );
437
438 // ParserOutput objects should have been flushed.
439 $this->assertNotSame( $mainOutput, $updater->getSlotParserOutput( SlotRecord::MAIN ) );
440 $this->assertNotSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
441
442 $html = $updater->getCanonicalParserOutput()->getText();
443 $this->assertContains( '--' . $rev->getId() . '--', $html );
444
445 // TODO: MCR: ensure that when the main slot uses {{REVISIONID}} but another slot is
446 // updated, the main slot is still re-rendered!
447 }
448
449 // TODO: test failure of prepareUpdate() when called again with a different revision
450 // TODO: test failure of prepareUpdate() on inconsistency with prepareContent.
451
456 $user = $this->getTestUser()->getUser();
457
458 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
459 $update = new RevisionSlotsUpdate();
460 $update->modifyContent( SlotRecord::MAIN, $mainContent );
461
462 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
463 $updater->prepareContent( $user, $update, false );
464
465 $canonicalOutput = $updater->getCanonicalParserOutput();
466
467 $preparedEdit = $updater->getPreparedEdit();
468 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
469 $this->assertSame( $canonicalOutput, $preparedEdit->output );
470 $this->assertSame( $mainContent, $preparedEdit->newContent );
471 $this->assertSame( $updater->getRawContent( SlotRecord::MAIN ), $preparedEdit->pstContent );
472 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
473 $this->assertSame( null, $preparedEdit->revid );
474 }
475
480 $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
481 MWTimestamp::setFakeTime( function () use ( &$clock ) {
482 return $clock++;
483 } );
484
485 $page = $this->getPage( __METHOD__ );
486
487 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
488 $update = new MutableRevisionSlots();
489 $update->setContent( SlotRecord::MAIN, $mainContent );
490
491 $rev = $this->createRevision( $page, __METHOD__ );
492
493 $updater = $this->getDerivedPageDataUpdater( $page );
494 $updater->prepareUpdate( $rev );
495
496 $canonicalOutput = $updater->getCanonicalParserOutput();
497
498 $preparedEdit = $updater->getPreparedEdit();
499 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
500 $this->assertSame( $canonicalOutput, $preparedEdit->output );
501 $this->assertSame( $updater->getRawContent( SlotRecord::MAIN ), $preparedEdit->pstContent );
502 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
503 $this->assertSame( $rev->getId(), $preparedEdit->revid );
504 }
505
507 $user = $this->getTestUser()->getUser();
508 $page = $this->getPage( __METHOD__ );
509 $this->createRevision( $page, __METHOD__ );
510
511 $mainContent1 = new WikitextContent( 'first' );
512
513 $update = new RevisionSlotsUpdate();
514 $update->modifyContent( SlotRecord::MAIN, $mainContent1 );
515 $updater = $this->getDerivedPageDataUpdater( $page );
516 $updater->prepareContent( $user, $update, false );
517
518 $dataUpdates = $updater->getSecondaryDataUpdates();
519
520 $this->assertNotEmpty( $dataUpdates );
521
522 $linksUpdates = array_filter( $dataUpdates, function ( $du ) {
523 return $du instanceof LinksUpdate;
524 } );
525 $this->assertCount( 1, $linksUpdates );
526 }
527
533 private function defineMockContentModelForUpdateTesting( $name ) {
535 $handler = $this->getMockBuilder( TextContentHandler::class )
536 ->setConstructorArgs( [ $name ] )
537 ->setMethods(
538 [ 'getSecondaryDataUpdates', 'getDeletionUpdates', 'unserializeContent' ]
539 )
540 ->getMock();
541
542 $dataUpdate = new MWCallableUpdate( 'time' );
543 $dataUpdate->_name = "$name data update";
544
545 $deletionUpdate = new MWCallableUpdate( 'time' );
546 $deletionUpdate->_name = "$name deletion update";
547
548 $handler->method( 'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
549 $handler->method( 'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
550 $handler->method( 'unserializeContent' )->willReturnCallback(
551 function ( $text ) use ( $handler ) {
552 return $this->createMockContent( $handler, $text );
553 }
554 );
555
557 'wgContentHandlers', [
558 $name => function () use ( $handler ){
559 return $handler;
560 }
561 ]
562 );
563
564 return $handler;
565 }
566
573 private function createMockContent( ContentHandler $handler, $text ) {
575 $content = $this->getMockBuilder( TextContent::class )
576 ->setConstructorArgs( [ $text ] )
577 ->setMethods( [ 'getModel', 'getContentHandler' ] )
578 ->getMock();
579
580 $content->method( 'getModel' )->willReturn( $handler->getModelID() );
581 $content->method( 'getContentHandler' )->willReturn( $handler );
582
583 return $content;
584 }
585
588
590 $this->markTestSkipped( 'Slot removal cannot happen with MCR being enabled' );
591 }
592
593 $m1 = $this->defineMockContentModelForUpdateTesting( 'M1' );
594 $a1 = $this->defineMockContentModelForUpdateTesting( 'A1' );
595 $m2 = $this->defineMockContentModelForUpdateTesting( 'M2' );
596
597 $mainContent1 = $this->createMockContent( $m1, 'main 1' );
598 $auxContent1 = $this->createMockContent( $a1, 'aux 1' );
599 $mainContent2 = $this->createMockContent( $m2, 'main 2' );
600
601 $user = $this->getTestUser()->getUser();
602 $page = $this->getPage( __METHOD__ );
603 $this->createRevision(
604 $page,
605 __METHOD__,
606 [ 'main' => $mainContent1, 'aux' => $auxContent1 ]
607 );
608
609 $update = new RevisionSlotsUpdate();
610 $update->modifyContent( SlotRecord::MAIN, $mainContent2 );
611 $update->removeSlot( 'aux' );
612
613 $page = $this->getPage( __METHOD__ );
614 $updater = $this->getDerivedPageDataUpdater( $page );
615 $updater->prepareContent( $user, $update, false );
616
617 $dataUpdates = $updater->getSecondaryDataUpdates();
618
619 $this->assertNotEmpty( $dataUpdates );
620
621 $updateNames = array_map( function ( $du ) {
622 return isset( $du->_name ) ? $du->_name : get_class( $du );
623 }, $dataUpdates );
624
625 $this->assertContains( LinksUpdate::class, $updateNames );
626 $this->assertContains( 'A1 deletion update', $updateNames );
627 $this->assertContains( 'M2 data update', $updateNames );
628 $this->assertNotContains( 'M1 data update', $updateNames );
629 }
630
643 private function makeRevision(
644 Title $title,
645 RevisionSlotsUpdate $update,
646 User $user,
647 $comment,
648 $id = 0,
649 $parentId = 0
650 ) {
652
653 $rev->applyUpdate( $update );
654 $rev->setUser( $user );
655 $rev->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
656 $rev->setPageId( $title->getArticleID() );
657 $rev->setParentId( $parentId );
658
659 if ( $id ) {
660 $rev->setId( $id );
661 }
662
663 return $rev;
664 }
665
670 private function getMockTitle( $id = 23 ) {
671 $mock = $this->getMockBuilder( Title::class )
672 ->disableOriginalConstructor()
673 ->getMock();
674 $mock->expects( $this->any() )
675 ->method( 'getDBkey' )
676 ->will( $this->returnValue( __CLASS__ ) );
677 $mock->expects( $this->any() )
678 ->method( 'getArticleID' )
679 ->will( $this->returnValue( $id ) );
680
681 return $mock;
682 }
683
684 public function provideIsReusableFor() {
685 $title = $this->getMockTitle();
686
687 $user1 = User::newFromName( 'Alice' );
688 $user2 = User::newFromName( 'Bob' );
689
690 $content1 = new WikitextContent( 'one' );
691 $content2 = new WikitextContent( 'two' );
692
693 $update1 = new RevisionSlotsUpdate();
694 $update1->modifyContent( SlotRecord::MAIN, $content1 );
695
696 $update1b = new RevisionSlotsUpdate();
697 $update1b->modifyContent( 'xyz', $content1 );
698
699 $update2 = new RevisionSlotsUpdate();
700 $update2->modifyContent( SlotRecord::MAIN, $content2 );
701
702 $rev1 = $this->makeRevision( $title, $update1, $user1, 'rev1', 11 );
703 $rev1b = $this->makeRevision( $title, $update1b, $user1, 'rev1', 11 );
704
705 $rev2 = $this->makeRevision( $title, $update2, $user1, 'rev2', 12 );
706 $rev2x = $this->makeRevision( $title, $update2, $user2, 'rev2', 12 );
707 $rev2y = $this->makeRevision( $title, $update2, $user1, 'rev2', 122 );
708
709 yield 'any' => [
710 '$prepUser' => null,
711 '$prepRevision' => null,
712 '$prepUpdate' => null,
713 '$forUser' => null,
714 '$forRevision' => null,
715 '$forUpdate' => null,
716 '$forParent' => null,
717 '$isReusable' => true,
718 ];
719 yield 'for any' => [
720 '$prepUser' => $user1,
721 '$prepRevision' => $rev1,
722 '$prepUpdate' => $update1,
723 '$forUser' => null,
724 '$forRevision' => null,
725 '$forUpdate' => null,
726 '$forParent' => null,
727 '$isReusable' => true,
728 ];
729 yield 'unprepared' => [
730 '$prepUser' => null,
731 '$prepRevision' => null,
732 '$prepUpdate' => null,
733 '$forUser' => $user1,
734 '$forRevision' => $rev1,
735 '$forUpdate' => $update1,
736 '$forParent' => 0,
737 '$isReusable' => true,
738 ];
739 yield 'match prepareContent' => [
740 '$prepUser' => $user1,
741 '$prepRevision' => null,
742 '$prepUpdate' => $update1,
743 '$forUser' => $user1,
744 '$forRevision' => null,
745 '$forUpdate' => $update1,
746 '$forParent' => 0,
747 '$isReusable' => true,
748 ];
749 yield 'match prepareUpdate' => [
750 '$prepUser' => null,
751 '$prepRevision' => $rev1,
752 '$prepUpdate' => null,
753 '$forUser' => $user1,
754 '$forRevision' => $rev1,
755 '$forUpdate' => null,
756 '$forParent' => 0,
757 '$isReusable' => true,
758 ];
759 yield 'match all' => [
760 '$prepUser' => $user1,
761 '$prepRevision' => $rev1,
762 '$prepUpdate' => $update1,
763 '$forUser' => $user1,
764 '$forRevision' => $rev1,
765 '$forUpdate' => $update1,
766 '$forParent' => 0,
767 '$isReusable' => true,
768 ];
769 yield 'mismatch prepareContent update' => [
770 '$prepUser' => $user1,
771 '$prepRevision' => null,
772 '$prepUpdate' => $update1,
773 '$forUser' => $user1,
774 '$forRevision' => null,
775 '$forUpdate' => $update1b,
776 '$forParent' => 0,
777 '$isReusable' => false,
778 ];
779 yield 'mismatch prepareContent user' => [
780 '$prepUser' => $user1,
781 '$prepRevision' => null,
782 '$prepUpdate' => $update1,
783 '$forUser' => $user2,
784 '$forRevision' => null,
785 '$forUpdate' => $update1,
786 '$forParent' => 0,
787 '$isReusable' => false,
788 ];
789 yield 'mismatch prepareContent parent' => [
790 '$prepUser' => $user1,
791 '$prepRevision' => null,
792 '$prepUpdate' => $update1,
793 '$forUser' => $user1,
794 '$forRevision' => null,
795 '$forUpdate' => $update1,
796 '$forParent' => 7,
797 '$isReusable' => false,
798 ];
799 yield 'mismatch prepareUpdate revision update' => [
800 '$prepUser' => null,
801 '$prepRevision' => $rev1,
802 '$prepUpdate' => null,
803 '$forUser' => null,
804 '$forRevision' => $rev1b,
805 '$forUpdate' => null,
806 '$forParent' => 0,
807 '$isReusable' => false,
808 ];
809 yield 'mismatch prepareUpdate revision user' => [
810 '$prepUser' => null,
811 '$prepRevision' => $rev2,
812 '$prepUpdate' => null,
813 '$forUser' => null,
814 '$forRevision' => $rev2x,
815 '$forUpdate' => null,
816 '$forParent' => 0,
817 '$isReusable' => false,
818 ];
819 yield 'mismatch prepareUpdate revision id' => [
820 '$prepUser' => null,
821 '$prepRevision' => $rev2,
822 '$prepUpdate' => null,
823 '$forUser' => null,
824 '$forRevision' => $rev2y,
825 '$forUpdate' => null,
826 '$forParent' => 0,
827 '$isReusable' => false,
828 ];
829 }
830
844 public function testIsReusableFor(
845 User $prepUser = null,
846 RevisionRecord $prepRevision = null,
847 RevisionSlotsUpdate $prepUpdate = null,
848 User $forUser = null,
849 RevisionRecord $forRevision = null,
850 RevisionSlotsUpdate $forUpdate = null,
851 $forParent = null,
852 $isReusable = null
853 ) {
854 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
855
856 if ( $prepUpdate ) {
857 $updater->prepareContent( $prepUser, $prepUpdate, false );
858 }
859
860 if ( $prepRevision ) {
861 $updater->prepareUpdate( $prepRevision );
862 }
863
864 $this->assertSame(
865 $isReusable,
866 $updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
867 );
868 }
869
875 public function testDoUpdates() {
876 $page = $this->getPage( __METHOD__ );
877
878 $content = [ 'main' => new WikitextContent( 'first [[main]]' ) ];
879
880 if ( $this->hasMultiSlotSupport() ) {
881 $content['aux'] = new WikitextContent( 'Aux [[Nix]]' );
882 }
883
884 $rev = $this->createRevision( $page, 'first', $content );
885 $pageId = $page->getId();
886
887 $oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
888 $this->db->delete( 'pagelinks', '*' );
889
890 $pcache = MediaWikiServices::getInstance()->getParserCache();
891 $pcache->deleteOptionsKey( $page );
892
893 $updater = $this->getDerivedPageDataUpdater( $page, $rev );
894 $updater->setArticleCountMethod( 'link' );
895
896 $options = []; // TODO: test *all* the options...
897 $updater->prepareUpdate( $rev, $options );
898
899 $updater->doUpdates();
900
901 // links table update
902 $pageLinks = $this->db->select(
903 'pagelinks',
904 '*',
905 [ 'pl_from' => $pageId ],
906 __METHOD__,
907 [ 'ORDER BY' => 'pl_namespace, pl_title' ]
908 );
909
910 $pageLinksRow = $pageLinks->fetchObject();
911 $this->assertInternalType( 'object', $pageLinksRow );
912 $this->assertSame( 'Main', $pageLinksRow->pl_title );
913
914 if ( $this->hasMultiSlotSupport() ) {
915 $pageLinksRow = $pageLinks->fetchObject();
916 $this->assertInternalType( 'object', $pageLinksRow );
917 $this->assertSame( 'Nix', $pageLinksRow->pl_title );
918 }
919
920 // parser cache update
921 $cached = $pcache->get( $page, $updater->getCanonicalParserOptions() );
922 $this->assertInternalType( 'object', $cached );
923 $this->assertSame( $updater->getCanonicalParserOutput(), $cached );
924
925 // site stats
926 $stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
927 $this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages );
928 $this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits );
929 $this->assertSame( $oldStats->ss_good_articles + 1, (int)$stats->ss_good_articles );
930
931 // TODO: MCR: test data updates for additional slots!
932 // TODO: test update for edit without page creation
933 // TODO: test message cache purge
934 // TODO: test module cache purge
935 // TODO: test CDN purge
936 // TODO: test newtalk update
937 // TODO: test search update
938 // TODO: test site stats good_articles while turning the page into (or back from) a redir.
939 // TODO: test category membership update (with setRcWatchCategoryMembership())
940 }
941
945 public function testDoParserCacheUpdate() {
946 $page = $this->getPage( __METHOD__ );
947 $this->createRevision( $page, 'Dummy' );
948
949 $user = $this->getTestUser()->getUser();
950
951 $update = new RevisionSlotsUpdate();
952 $update->modifyContent( 'main', new WikitextContent( 'first [[Main]]' ) );
953
954 if ( $this->hasMultiSlotSupport() ) {
955 $update->modifyContent( 'aux', new WikitextContent( 'Aux [[Nix]]' ) );
956 }
957
958 // Emulate update after edit ----------
959 $pcache = MediaWikiServices::getInstance()->getParserCache();
960 $pcache->deleteOptionsKey( $page );
961
962 $rev = $this->makeRevision( $page->getTitle(), $update, $user, 'rev', null );
963 $rev->setTimestamp( '20100101000000' );
964 $rev->setParentId( $page->getLatest() );
965
966 $updater = $this->getDerivedPageDataUpdater( $page );
967 $updater->prepareContent( $user, $update, false );
968
969 $rev->setId( 11 );
970 $updater->prepareUpdate( $rev );
971
972 // Force the page timestamp, so we notice whether ParserOutput::getTimestamp
973 // or ParserOutput::getCacheTime are used.
974 $page->setTimestamp( $rev->getTimestamp() );
975 $updater->doParserCacheUpdate();
976
977 // The cached ParserOutput should not use the revision timestamp
978 $cached = $pcache->get( $page, $updater->getCanonicalParserOptions(), true );
979 $this->assertInternalType( 'object', $cached );
980 $this->assertSame( $updater->getCanonicalParserOutput(), $cached );
981
982 $this->assertSame( $rev->getTimestamp(), $cached->getCacheTime() );
983 $this->assertSame( $rev->getId(), $cached->getCacheRevisionId() );
984
985 // Emulate forced update of an old revision ----------
986 $pcache->deleteOptionsKey( $page );
987
988 $updater = $this->getDerivedPageDataUpdater( $page );
989 $updater->prepareUpdate( $rev );
990
991 // Force the page timestamp, so we notice whether ParserOutput::getTimestamp
992 // or ParserOutput::getCacheTime are used.
993 $page->setTimestamp( $rev->getTimestamp() );
994 $updater->doParserCacheUpdate();
995
996 // The cached ParserOutput should not use the revision timestamp
997 $cached = $pcache->get( $page, $updater->getCanonicalParserOptions(), true );
998 $this->assertInternalType( 'object', $cached );
999 $this->assertSame( $updater->getCanonicalParserOutput(), $cached );
1000
1001 $this->assertGreaterThan( $rev->getTimestamp(), $cached->getCacheTime() );
1002 $this->assertSame( $rev->getId(), $cached->getCacheRevisionId() );
1003 }
1004
1014
1015}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
int $wgMultiContentRevisionSchemaMigrationStage
RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
CommentStoreComment represents a comment stored by CommentStore.
A content handler knows how do deal with a specific type of content on a wiki page.
Class the manages updates of *_link tables as well as similar extension-managed tables.
Deferrable Update for closure/callback.
Library for creating and parsing MW-style timestamps.
getDefaultWikitextNS()
Returns the ID of a namespace that defaults to Wikitext.
mergeMwGlobalArrayValue( $name, $values)
Merges the given values into a MW global array variable.
static getTestUser( $groups=[])
Convenience method for getting an immutable test user.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Mutable version of RevisionSlots, for constructing a new revision.
Page revision base class.
Value object representing a content slot associated with a page revision.
A handle for managing updates for derived page data on edit, import, purge, etc.
Value object representing a modification of revision slots.
testIsReusableFor(User $prepUser=null, RevisionRecord $prepRevision=null, RevisionSlotsUpdate $prepUpdate=null, User $forUser=null, RevisionRecord $forRevision=null, RevisionSlotsUpdate $forUpdate=null, $forParent=null, $isReusable=null)
provideIsReusableFor \MediaWiki\Storage\DerivedPageDataUpdater::isReusableFor()
testDoUpdates()
\MediaWiki\Storage\DerivedPageDataUpdater::doUpdates() \MediaWiki\Storage\DerivedPageDataUpdater::doS...
testGrabCurrentRevision()
\MediaWiki\Storage\DerivedPageDataUpdater::grabCurrentRevision() \MediaWiki\Storage\DerivedPageDataUp...
makeRevision(Title $title, RevisionSlotsUpdate $update, User $user, $comment, $id=0, $parentId=0)
Creates a dummy revision object without touching the database.
testPrepareUpdateReusesParserOutput()
\MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
testPrepareUpdate()
\MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate() \MediaWiki\Storage\DerivedPageDataUpdater:...
testGetPreparedEditAfterPrepareUpdate()
\MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
testPrepareContentInherit()
\MediaWiki\Storage\DerivedPageDataUpdater::prepareContent() \MediaWiki\Storage\DerivedPageDataUpdater...
createRevision(WikiPage $page, $summary, $content=null)
Creates a revision in the database.
testPrepareContent()
\MediaWiki\Storage\DerivedPageDataUpdater::prepareContent() \MediaWiki\Storage\DerivedPageDataUpdater...
testPrepareUpdateOutputReset()
\MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate() \MediaWiki\Storage\DerivedPageDataUpdater:...
testGetPreparedEditAfterPrepareContent()
\MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
testDoParserCacheUpdate()
\MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
testGetCanonicalParserOptions()
\MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOptions()
Base content handler implementation for flat text contents.
Content object implementation for representing flat text.
Represents a title within MediaWiki.
Definition Title.php:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:592
Class representing a MediaWiki article and history.
Definition WikiPage.php:44
newPageUpdater(User $user, RevisionSlotsUpdate $forUpdate=null)
Returns a PageUpdater for creating new revisions on this page (or creating the page).
Content object for wiki text pages.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:287
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:286
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition hooks.txt:2062
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition hooks.txt:933
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1818
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:247
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Base interface for content objects.
Definition Content.php:34
$content
$page->newPageUpdater($user) $updater