Merge "ApiComparePages: Don't error with no prev/next rev"
[lhc/web/wiklou.git] / tests / phpunit / includes / Storage / PageUpdaterTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Storage;
4
5 use CommentStoreComment;
6 use Content;
7 use MediaWiki\MediaWikiServices;
8 use MediaWiki\Revision\RevisionRecord;
9 use MediaWiki\Revision\SlotRecord;
10 use MediaWikiTestCase;
11 use ParserOptions;
12 use RecentChange;
13 use Revision;
14 use TextContent;
15 use Title;
16 use User;
17 use WikiPage;
18
19 /**
20 * @covers \MediaWiki\Storage\PageUpdater
21 * @group Database
22 */
23 class PageUpdaterTest extends MediaWikiTestCase {
24
25 public function setUp() {
26 parent::setUp();
27
28 MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
29 'aux',
30 CONTENT_MODEL_WIKITEXT
31 );
32 }
33
34 private function getDummyTitle( $method ) {
35 return Title::newFromText( $method, $this->getDefaultWikitextNS() );
36 }
37
38 /**
39 * @param int $revId
40 *
41 * @return null|RecentChange
42 */
43 private function getRecentChangeFor( $revId ) {
44 $qi = RecentChange::getQueryInfo();
45 $row = $this->db->selectRow(
46 $qi['tables'],
47 $qi['fields'],
48 [ 'rc_this_oldid' => $revId ],
49 __METHOD__,
50 [],
51 $qi['joins']
52 );
53
54 return $row ? RecentChange::newFromRow( $row ) : null;
55 }
56
57 // TODO: test setAjaxEditStash();
58
59 /**
60 * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
61 * @covers \WikiPage::newPageUpdater()
62 */
63 public function testCreatePage() {
64 $user = $this->getTestUser()->getUser();
65
66 $title = $this->getDummyTitle( __METHOD__ );
67 $page = WikiPage::factory( $title );
68 $updater = $page->newPageUpdater( $user );
69
70 $oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
71
72 $this->assertFalse( $updater->wasCommitted(), 'wasCommitted' );
73 $this->assertFalse( $updater->getOriginalRevisionId(), 'getOriginalRevisionId' );
74 $this->assertSame( 0, $updater->getUndidRevisionId(), 'getUndidRevisionId' );
75
76 $updater->addTag( 'foo' );
77 $updater->addTags( [ 'bar', 'qux' ] );
78
79 $tags = $updater->getExplicitTags();
80 sort( $tags );
81 $this->assertSame( [ 'bar', 'foo', 'qux' ], $tags, 'getExplicitTags' );
82
83 // TODO: MCR: test additional slots
84 $content = new TextContent( 'Lorem Ipsum' );
85 $updater->setContent( SlotRecord::MAIN, $content );
86
87 $parent = $updater->grabParentRevision();
88
89 $this->assertNull( $parent, 'getParentRevision' );
90 $this->assertFalse( $updater->wasCommitted(), 'wasCommitted' );
91
92 // TODO: test that hasEditConflict() grabs the parent revision
93 $this->assertFalse( $updater->hasEditConflict( 0 ), 'hasEditConflict' );
94 $this->assertTrue( $updater->hasEditConflict( 1 ), 'hasEditConflict' );
95
96 // TODO: test failure with EDIT_UPDATE
97 // TODO: test EDIT_MINOR, EDIT_BOT, etc
98 $summary = CommentStoreComment::newUnsavedComment( 'Just a test' );
99 $rev = $updater->saveRevision( $summary );
100
101 $this->assertNotNull( $rev );
102 $this->assertSame( 0, $rev->getParentId() );
103 $this->assertSame( $summary->text, $rev->getComment( RevisionRecord::RAW )->text );
104 $this->assertSame( $user->getName(), $rev->getUser( RevisionRecord::RAW )->getName() );
105
106 $this->assertTrue( $updater->wasCommitted(), 'wasCommitted()' );
107 $this->assertTrue( $updater->wasSuccessful(), 'wasSuccessful()' );
108 $this->assertTrue( $updater->getStatus()->isOK(), 'getStatus()->isOK()' );
109 $this->assertTrue( $updater->isNew(), 'isNew()' );
110 $this->assertFalse( $updater->isUnchanged(), 'isUnchanged()' );
111 $this->assertNotNull( $updater->getNewRevision(), 'getNewRevision()' );
112 $this->assertInstanceOf( Revision::class, $updater->getStatus()->value['revision'] );
113
114 $rev = $updater->getNewRevision();
115 $revContent = $rev->getContent( SlotRecord::MAIN );
116 $this->assertSame( 'Lorem Ipsum', $revContent->serialize(), 'revision content' );
117
118 // were the WikiPage and Title objects updated?
119 $this->assertTrue( $page->exists(), 'WikiPage::exists()' );
120 $this->assertTrue( $title->exists(), 'Title::exists()' );
121 $this->assertSame( $rev->getId(), $page->getLatest(), 'WikiPage::getRevision()' );
122 $this->assertNotNull( $page->getRevision(), 'WikiPage::getRevision()' );
123
124 // re-load
125 $page2 = WikiPage::factory( $title );
126 $this->assertTrue( $page2->exists(), 'WikiPage::exists()' );
127 $this->assertSame( $rev->getId(), $page2->getLatest(), 'WikiPage::getRevision()' );
128 $this->assertNotNull( $page2->getRevision(), 'WikiPage::getRevision()' );
129
130 // Check RC entry
131 $rc = $this->getRecentChangeFor( $rev->getId() );
132 $this->assertNotNull( $rc, 'RecentChange' );
133
134 // check site stats - this asserts that derived data updates where run.
135 $stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
136 $this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages );
137 $this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits );
138
139 // re-edit with same content - should be a "null-edit"
140 $updater = $page->newPageUpdater( $user );
141 $updater->setContent( SlotRecord::MAIN, $content );
142
143 $summary = CommentStoreComment::newUnsavedComment( 'to to re-edit' );
144 $rev = $updater->saveRevision( $summary );
145 $status = $updater->getStatus();
146
147 $this->assertNull( $rev, 'getNewRevision()' );
148 $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
149 $this->assertTrue( $updater->isUnchanged(), 'isUnchanged' );
150 $this->assertTrue( $updater->wasSuccessful(), 'wasSuccessful()' );
151 $this->assertTrue( $status->isOK(), 'getStatus()->isOK()' );
152 $this->assertTrue( $status->hasMessage( 'edit-no-change' ), 'edit-no-change' );
153 }
154
155 /**
156 * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
157 * @covers \WikiPage::newPageUpdater()
158 */
159 public function testUpdatePage() {
160 $user = $this->getTestUser()->getUser();
161
162 $title = $this->getDummyTitle( __METHOD__ );
163 $this->insertPage( $title );
164
165 $page = WikiPage::factory( $title );
166 $parentId = $page->getLatest();
167
168 $updater = $page->newPageUpdater( $user );
169
170 $oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
171
172 $updater->setOriginalRevisionId( 7 );
173 $this->assertSame( 7, $updater->getOriginalRevisionId(), 'getOriginalRevisionId' );
174
175 $this->assertFalse( $updater->hasEditConflict( $parentId ), 'hasEditConflict' );
176 $this->assertTrue( $updater->hasEditConflict( $parentId - 1 ), 'hasEditConflict' );
177 $this->assertTrue( $updater->hasEditConflict( 0 ), 'hasEditConflict' );
178
179 // TODO: MCR: test additional slots
180 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
181
182 // TODO: test all flags for saveRevision()!
183 $summary = CommentStoreComment::newUnsavedComment( 'Just a test' );
184 $rev = $updater->saveRevision( $summary );
185
186 $this->assertNotNull( $rev );
187 $this->assertSame( $parentId, $rev->getParentId() );
188 $this->assertSame( $summary->text, $rev->getComment( RevisionRecord::RAW )->text );
189 $this->assertSame( $user->getName(), $rev->getUser( RevisionRecord::RAW )->getName() );
190
191 $this->assertTrue( $updater->wasCommitted(), 'wasCommitted()' );
192 $this->assertTrue( $updater->wasSuccessful(), 'wasSuccessful()' );
193 $this->assertTrue( $updater->getStatus()->isOK(), 'getStatus()->isOK()' );
194 $this->assertFalse( $updater->isNew(), 'isNew()' );
195 $this->assertNotNull( $updater->getNewRevision(), 'getNewRevision()' );
196 $this->assertInstanceOf( Revision::class, $updater->getStatus()->value['revision'] );
197 $this->assertFalse( $updater->isUnchanged(), 'isUnchanged()' );
198
199 // TODO: Test null revision (with different user): new revision!
200
201 $rev = $updater->getNewRevision();
202 $revContent = $rev->getContent( SlotRecord::MAIN );
203 $this->assertSame( 'Lorem Ipsum', $revContent->serialize(), 'revision content' );
204
205 // were the WikiPage and Title objects updated?
206 $this->assertTrue( $page->exists(), 'WikiPage::exists()' );
207 $this->assertTrue( $title->exists(), 'Title::exists()' );
208 $this->assertSame( $rev->getId(), $page->getLatest(), 'WikiPage::getRevision()' );
209 $this->assertNotNull( $page->getRevision(), 'WikiPage::getRevision()' );
210
211 // re-load
212 $page2 = WikiPage::factory( $title );
213 $this->assertTrue( $page2->exists(), 'WikiPage::exists()' );
214 $this->assertSame( $rev->getId(), $page2->getLatest(), 'WikiPage::getRevision()' );
215 $this->assertNotNull( $page2->getRevision(), 'WikiPage::getRevision()' );
216
217 // Check RC entry
218 $rc = $this->getRecentChangeFor( $rev->getId() );
219 $this->assertNotNull( $rc, 'RecentChange' );
220
221 // re-edit
222 $updater = $page->newPageUpdater( $user );
223 $updater->setContent( SlotRecord::MAIN, new TextContent( 'dolor sit amet' ) );
224
225 $summary = CommentStoreComment::newUnsavedComment( 're-edit' );
226 $updater->saveRevision( $summary );
227 $this->assertTrue( $updater->wasSuccessful(), 'wasSuccessful()' );
228 $this->assertTrue( $updater->getStatus()->isOK(), 'getStatus()->isOK()' );
229
230 // check site stats - this asserts that derived data updates where run.
231 $stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
232 $this->assertNotNull( $stats, 'site_stats' );
233 $this->assertSame( $oldStats->ss_total_pages + 0, (int)$stats->ss_total_pages );
234 $this->assertSame( $oldStats->ss_total_edits + 2, (int)$stats->ss_total_edits );
235 }
236
237 /**
238 * Creates a revision in the database.
239 *
240 * @param WikiPage $page
241 * @param $summary
242 * @param null|string|Content $content
243 *
244 * @return RevisionRecord|null
245 */
246 private function createRevision( WikiPage $page, $summary, $content = null ) {
247 $user = $this->getTestUser()->getUser();
248 $comment = CommentStoreComment::newUnsavedComment( $summary );
249
250 if ( !$content instanceof Content ) {
251 $content = new TextContent( $content ?? $summary );
252 }
253
254 $updater = $page->newPageUpdater( $user );
255 $updater->setContent( SlotRecord::MAIN, $content );
256 $rev = $updater->saveRevision( $comment );
257 return $rev;
258 }
259
260 /**
261 * @covers \MediaWiki\Storage\PageUpdater::grabParentRevision()
262 * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
263 */
264 public function testCompareAndSwapFailure() {
265 $user = $this->getTestUser()->getUser();
266
267 $title = $this->getDummyTitle( __METHOD__ );
268
269 // start editing non-existing page
270 $page = WikiPage::factory( $title );
271 $updater = $page->newPageUpdater( $user );
272 $updater->grabParentRevision();
273
274 // create page concurrently
275 $concurrentPage = WikiPage::factory( $title );
276 $this->createRevision( $concurrentPage, __METHOD__ . '-one' );
277
278 // try creating the page - should trigger CAS failure.
279 $summary = CommentStoreComment::newUnsavedComment( 'create?!' );
280 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem ipsum' ) );
281 $updater->saveRevision( $summary );
282 $status = $updater->getStatus();
283
284 $this->assertFalse( $updater->wasSuccessful(), 'wasSuccessful()' );
285 $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
286 $this->assertFalse( $status->isOK(), 'getStatus()->isOK()' );
287 $this->assertTrue( $status->hasMessage( 'edit-already-exists' ), 'edit-conflict' );
288
289 // start editing existing page
290 $page = WikiPage::factory( $title );
291 $updater = $page->newPageUpdater( $user );
292 $updater->grabParentRevision();
293
294 // update page concurrently
295 $concurrentPage = WikiPage::factory( $title );
296 $this->createRevision( $concurrentPage, __METHOD__ . '-two' );
297
298 // try creating the page - should trigger CAS failure.
299 $summary = CommentStoreComment::newUnsavedComment( 'edit?!' );
300 $updater->setContent( SlotRecord::MAIN, new TextContent( 'dolor sit amet' ) );
301 $updater->saveRevision( $summary );
302 $status = $updater->getStatus();
303
304 $this->assertFalse( $updater->wasSuccessful(), 'wasSuccessful()' );
305 $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
306 $this->assertFalse( $status->isOK(), 'getStatus()->isOK()' );
307 $this->assertTrue( $status->hasMessage( 'edit-conflict' ), 'edit-conflict' );
308 }
309
310 /**
311 * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
312 */
313 public function testFailureOnEditFlags() {
314 $user = $this->getTestUser()->getUser();
315
316 $title = $this->getDummyTitle( __METHOD__ );
317
318 // start editing non-existing page
319 $page = WikiPage::factory( $title );
320 $updater = $page->newPageUpdater( $user );
321
322 // update with EDIT_UPDATE flag should fail
323 $summary = CommentStoreComment::newUnsavedComment( 'udpate?!' );
324 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem ipsum' ) );
325 $updater->saveRevision( $summary, EDIT_UPDATE );
326 $status = $updater->getStatus();
327
328 $this->assertFalse( $updater->wasSuccessful(), 'wasSuccessful()' );
329 $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
330 $this->assertFalse( $status->isOK(), 'getStatus()->isOK()' );
331 $this->assertTrue( $status->hasMessage( 'edit-gone-missing' ), 'edit-gone-missing' );
332
333 // create the page
334 $this->createRevision( $page, __METHOD__ );
335
336 // update with EDIT_NEW flag should fail
337 $summary = CommentStoreComment::newUnsavedComment( 'create?!' );
338 $updater = $page->newPageUpdater( $user );
339 $updater->setContent( SlotRecord::MAIN, new TextContent( 'dolor sit amet' ) );
340 $updater->saveRevision( $summary, EDIT_NEW );
341 $status = $updater->getStatus();
342
343 $this->assertFalse( $updater->wasSuccessful(), 'wasSuccessful()' );
344 $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
345 $this->assertFalse( $status->isOK(), 'getStatus()->isOK()' );
346 $this->assertTrue( $status->hasMessage( 'edit-already-exists' ), 'edit-already-exists' );
347 }
348
349 /**
350 * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
351 */
352 public function testFailureOnBadContentModel() {
353 $user = $this->getTestUser()->getUser();
354 $title = $this->getDummyTitle( __METHOD__ );
355
356 // start editing non-existing page
357 $page = WikiPage::factory( $title );
358 $updater = $page->newPageUpdater( $user );
359
360 // plain text content should fail in aux slot (the main slot doesn't care)
361 $updater->setContent( 'main', new TextContent( 'Main Content' ) );
362 $updater->setContent( 'aux', new TextContent( 'Aux Content' ) );
363
364 $summary = CommentStoreComment::newUnsavedComment( 'udpate?!' );
365 $updater->saveRevision( $summary, EDIT_UPDATE );
366 $status = $updater->getStatus();
367
368 $this->assertFalse( $updater->wasSuccessful(), 'wasSuccessful()' );
369 $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
370 $this->assertFalse( $status->isOK(), 'getStatus()->isOK()' );
371 $this->assertTrue(
372 $status->hasMessage( 'content-not-allowed-here' ),
373 'content-not-allowed-here'
374 );
375 }
376
377 public function provideSetRcPatrolStatus( $patrolled ) {
378 yield [ RecentChange::PRC_UNPATROLLED ];
379 yield [ RecentChange::PRC_AUTOPATROLLED ];
380 }
381
382 /**
383 * @dataProvider provideSetRcPatrolStatus
384 * @covers \MediaWiki\Storage\PageUpdater::setRcPatrolStatus()
385 */
386 public function testSetRcPatrolStatus( $patrolled ) {
387 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
388
389 $user = $this->getTestUser()->getUser();
390
391 $title = $this->getDummyTitle( __METHOD__ );
392
393 $page = WikiPage::factory( $title );
394 $updater = $page->newPageUpdater( $user );
395
396 $summary = CommentStoreComment::newUnsavedComment( 'Lorem ipsum ' . $patrolled );
397 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem ipsum ' . $patrolled ) );
398 $updater->setRcPatrolStatus( $patrolled );
399 $rev = $updater->saveRevision( $summary );
400
401 $rc = $revisionStore->getRecentChange( $rev );
402 $this->assertEquals( $patrolled, $rc->getAttribute( 'rc_patrolled' ) );
403 }
404
405 /**
406 * @covers \MediaWiki\Storage\PageUpdater::inheritSlot()
407 * @covers \MediaWiki\Storage\PageUpdater::setContent()
408 */
409 public function testInheritSlot() {
410 $user = $this->getTestUser()->getUser();
411 $title = $this->getDummyTitle( __METHOD__ );
412 $page = WikiPage::factory( $title );
413
414 $updater = $page->newPageUpdater( $user );
415 $summary = CommentStoreComment::newUnsavedComment( 'one' );
416 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem ipsum' ) );
417 $rev1 = $updater->saveRevision( $summary, EDIT_NEW );
418
419 $updater = $page->newPageUpdater( $user );
420 $summary = CommentStoreComment::newUnsavedComment( 'two' );
421 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Foo Bar' ) );
422 $rev2 = $updater->saveRevision( $summary, EDIT_UPDATE );
423
424 $updater = $page->newPageUpdater( $user );
425 $summary = CommentStoreComment::newUnsavedComment( 'three' );
426 $updater->inheritSlot( $rev1->getSlot( SlotRecord::MAIN ) );
427 $rev3 = $updater->saveRevision( $summary, EDIT_UPDATE );
428
429 $this->assertNotSame( $rev1->getId(), $rev3->getId() );
430 $this->assertNotSame( $rev2->getId(), $rev3->getId() );
431
432 $main1 = $rev1->getSlot( SlotRecord::MAIN );
433 $main3 = $rev3->getSlot( SlotRecord::MAIN );
434
435 $this->assertNotSame( $main1->getRevision(), $main3->getRevision() );
436 $this->assertSame( $main1->getAddress(), $main3->getAddress() );
437 $this->assertTrue( $main1->getContent()->equals( $main3->getContent() ) );
438 }
439
440 // TODO: MCR: test adding multiple slots, inheriting parent slots, and removing slots.
441
442 public function testSetUseAutomaticEditSummaries() {
443 $this->setContentLang( 'qqx' );
444 $user = $this->getTestUser()->getUser();
445
446 $title = $this->getDummyTitle( __METHOD__ );
447 $page = WikiPage::factory( $title );
448
449 $updater = $page->newPageUpdater( $user );
450 $updater->setUseAutomaticEditSummaries( true );
451 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
452
453 // empty comment triggers auto-summary
454 $summary = CommentStoreComment::newUnsavedComment( '' );
455 $updater->saveRevision( $summary, EDIT_AUTOSUMMARY );
456
457 $rev = $updater->getNewRevision();
458 $comment = $rev->getComment( RevisionRecord::RAW );
459 $this->assertSame( '(autosumm-new: Lorem Ipsum)', $comment->text, 'comment text' );
460
461 // check that this also works when blanking the page
462 $updater = $page->newPageUpdater( $user );
463 $updater->setUseAutomaticEditSummaries( true );
464 $updater->setContent( SlotRecord::MAIN, new TextContent( '' ) );
465
466 $summary = CommentStoreComment::newUnsavedComment( '' );
467 $updater->saveRevision( $summary, EDIT_AUTOSUMMARY );
468
469 $rev = $updater->getNewRevision();
470 $comment = $rev->getComment( RevisionRecord::RAW );
471 $this->assertSame( '(autosumm-blank)', $comment->text, 'comment text' );
472
473 // check that we can also disable edit-summaries
474 $title2 = $this->getDummyTitle( __METHOD__ . '/2' );
475 $page2 = WikiPage::factory( $title2 );
476
477 $updater = $page2->newPageUpdater( $user );
478 $updater->setUseAutomaticEditSummaries( false );
479 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
480
481 $summary = CommentStoreComment::newUnsavedComment( '' );
482 $updater->saveRevision( $summary, EDIT_AUTOSUMMARY );
483
484 $rev = $updater->getNewRevision();
485 $comment = $rev->getComment( RevisionRecord::RAW );
486 $this->assertSame( '', $comment->text, 'comment text should still be lank' );
487
488 // check that we don't do auto.summaries without the EDIT_AUTOSUMMARY flag
489 $updater = $page2->newPageUpdater( $user );
490 $updater->setUseAutomaticEditSummaries( true );
491 $updater->setContent( SlotRecord::MAIN, new TextContent( '' ) );
492
493 $summary = CommentStoreComment::newUnsavedComment( '' );
494 $updater->saveRevision( $summary, 0 );
495
496 $rev = $updater->getNewRevision();
497 $comment = $rev->getComment( RevisionRecord::RAW );
498 $this->assertSame( '', $comment->text, 'comment text' );
499 }
500
501 public function provideSetUsePageCreationLog() {
502 yield [ true, [ [ 'create', 'create' ] ] ];
503 yield [ false, [] ];
504 }
505
506 /**
507 * @dataProvider provideSetUsePageCreationLog
508 * @param bool $use
509 */
510 public function testSetUsePageCreationLog( $use, $expected ) {
511 $user = $this->getTestUser()->getUser();
512 $title = $this->getDummyTitle( __METHOD__ . ( $use ? '_logged' : '_unlogged' ) );
513 $page = WikiPage::factory( $title );
514
515 $updater = $page->newPageUpdater( $user );
516 $updater->setUsePageCreationLog( $use );
517 $summary = CommentStoreComment::newUnsavedComment( 'cmt' );
518 $updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
519 $updater->saveRevision( $summary, EDIT_NEW );
520
521 $rev = $updater->getNewRevision();
522 $this->assertSelect(
523 'logging',
524 [ 'log_type', 'log_action' ],
525 [ 'log_page' => $rev->getPageId() ],
526 $expected
527 );
528 }
529
530 public function provideMagicWords() {
531 yield 'PAGEID' => [
532 'Test {{PAGEID}} Test',
533 function ( RevisionRecord $rev ) {
534 return $rev->getPageId();
535 }
536 ];
537
538 yield 'REVISIONID' => [
539 'Test {{REVISIONID}} Test',
540 function ( RevisionRecord $rev ) {
541 return $rev->getId();
542 }
543 ];
544
545 yield 'REVISIONUSER' => [
546 'Test {{REVISIONUSER}} Test',
547 function ( RevisionRecord $rev ) {
548 return $rev->getUser()->getName();
549 }
550 ];
551
552 yield 'REVISIONTIMESTAMP' => [
553 'Test {{REVISIONTIMESTAMP}} Test',
554 function ( RevisionRecord $rev ) {
555 return $rev->getTimestamp();
556 }
557 ];
558
559 yield 'subst:REVISIONUSER' => [
560 'Test {{subst:REVISIONUSER}} Test',
561 function ( RevisionRecord $rev ) {
562 return $rev->getUser()->getName();
563 },
564 'subst'
565 ];
566
567 yield 'subst:PAGENAME' => [
568 'Test {{subst:PAGENAME}} Test',
569 function ( RevisionRecord $rev ) {
570 return 'PageUpdaterTest::testMagicWords';
571 },
572 'subst'
573 ];
574 }
575
576 /**
577 * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
578 *
579 * Integration test for PageUpdater, DerivedPageDataUpdater, RevisionRenderer
580 * and RenderedRevision, that ensures that magic words depending on revision meta-data
581 * are handled correctly. Note that each magic word needs to be tested separately,
582 * to assert correct behavior for each "vary" flag in the ParserOutput.
583 *
584 * @dataProvider provideMagicWords
585 */
586 public function testMagicWords( $wikitext, $callback, $subst = false ) {
587 $user = User::newFromName( 'A user for ' . __METHOD__ );
588 $user->addToDatabase();
589
590 $title = $this->getDummyTitle( __METHOD__ . '-' . $this->getName() );
591 $this->insertPage( $title );
592
593 $page = WikiPage::factory( $title );
594 $updater = $page->newPageUpdater( $user );
595
596 $updater->setContent( SlotRecord::MAIN, new \WikitextContent( $wikitext ) );
597
598 $summary = CommentStoreComment::newUnsavedComment( 'Just a test' );
599 $rev = $updater->saveRevision( $summary, EDIT_UPDATE );
600
601 if ( !$rev ) {
602 $this->fail( $updater->getStatus()->getWikiText() );
603 }
604
605 $expected = strval( $callback( $rev ) );
606
607 $output = $page->getParserOutput( ParserOptions::newCanonical( 'canonical' ) );
608 $html = $output->getText();
609 $text = $rev->getContent( SlotRecord::MAIN )->serialize();
610
611 if ( $subst ) {
612 $this->assertContains( $expected, $text, 'In Wikitext' );
613 }
614
615 $this->assertContains( $expected, $html, 'In HTML' );
616 }
617
618 }