SpecialPages: Add ul { margin-top: 0; margin-bottom: 0 } for multicolumn
[lhc/web/wiklou.git] / tests / phpunit / includes / page / WikiPageTest.php
1 <?php
2
3 /**
4 * @group ContentHandler
5 * @group Database
6 * @group medium
7 */
8 class WikiPageTest extends MediaWikiLangTestCase {
9
10 private $pagesToDelete;
11
12 public function __construct( $name = null, array $data = [], $dataName = '' ) {
13 parent::__construct( $name, $data, $dataName );
14
15 $this->tablesUsed = array_merge(
16 $this->tablesUsed,
17 [ 'page',
18 'revision',
19 'archive',
20 'ip_changes',
21 'text',
22
23 'recentchanges',
24 'logging',
25
26 'page_props',
27 'pagelinks',
28 'categorylinks',
29 'langlinks',
30 'externallinks',
31 'imagelinks',
32 'templatelinks',
33 'iwlinks' ] );
34 }
35
36 protected function setUp() {
37 parent::setUp();
38 $this->pagesToDelete = [];
39 }
40
41 protected function tearDown() {
42 foreach ( $this->pagesToDelete as $p ) {
43 /* @var $p WikiPage */
44
45 try {
46 if ( $p->exists() ) {
47 $p->doDeleteArticle( "testing done." );
48 }
49 } catch ( MWException $ex ) {
50 // fail silently
51 }
52 }
53 parent::tearDown();
54 }
55
56 /**
57 * @param Title|string $title
58 * @param string|null $model
59 * @return WikiPage
60 */
61 private function newPage( $title, $model = null ) {
62 if ( is_string( $title ) ) {
63 $ns = $this->getDefaultWikitextNS();
64 $title = Title::newFromText( $title, $ns );
65 }
66
67 $p = new WikiPage( $title );
68
69 $this->pagesToDelete[] = $p;
70
71 return $p;
72 }
73
74 /**
75 * @param string|Title|WikiPage $page
76 * @param string $text
77 * @param int $model
78 *
79 * @return WikiPage
80 */
81 private function createPage( $page, $text, $model = null ) {
82 if ( is_string( $page ) || $page instanceof Title ) {
83 $page = $this->newPage( $page, $model );
84 }
85
86 $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
87 $page->doEditContent( $content, "testing", EDIT_NEW );
88
89 return $page;
90 }
91
92 /**
93 * @covers WikiPage::doEditContent
94 * @covers WikiPage::doModify
95 * @covers WikiPage::doCreate
96 * @covers WikiPage::doEditUpdates
97 */
98 public function testDoEditContent() {
99 $page = $this->newPage( __METHOD__ );
100 $title = $page->getTitle();
101
102 $content = ContentHandler::makeContent(
103 "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
104 . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
105 $title,
106 CONTENT_MODEL_WIKITEXT
107 );
108
109 $page->doEditContent( $content, "[[testing]] 1" );
110
111 $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
112 $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
113 $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
114 $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
115
116 $id = $page->getId();
117
118 # ------------------------
119 $dbr = wfGetDB( DB_REPLICA );
120 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
121 $n = $res->numRows();
122 $res->free();
123
124 $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
125
126 # ------------------------
127 $page = new WikiPage( $title );
128
129 $retrieved = $page->getContent();
130 $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
131
132 # ------------------------
133 $content = ContentHandler::makeContent(
134 "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
135 . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
136 $title,
137 CONTENT_MODEL_WIKITEXT
138 );
139
140 $page->doEditContent( $content, "testing 2" );
141
142 # ------------------------
143 $page = new WikiPage( $title );
144
145 $retrieved = $page->getContent();
146 $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
147
148 # ------------------------
149 $dbr = wfGetDB( DB_REPLICA );
150 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
151 $n = $res->numRows();
152 $res->free();
153
154 $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
155 }
156
157 /**
158 * @covers WikiPage::doDeleteArticle
159 */
160 public function testDoDeleteArticle() {
161 $page = $this->createPage(
162 __METHOD__,
163 "[[original text]] foo",
164 CONTENT_MODEL_WIKITEXT
165 );
166 $id = $page->getId();
167
168 $page->doDeleteArticle( "testing deletion" );
169
170 $this->assertFalse(
171 $page->getTitle()->getArticleID() > 0,
172 "Title object should now have page id 0"
173 );
174 $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
175 $this->assertFalse(
176 $page->exists(),
177 "WikiPage::exists should return false after page was deleted"
178 );
179 $this->assertNull(
180 $page->getContent(),
181 "WikiPage::getContent should return null after page was deleted"
182 );
183
184 $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
185 $this->assertFalse(
186 $t->exists(),
187 "Title::exists should return false after page was deleted"
188 );
189
190 // Run the job queue
191 JobQueueGroup::destroySingletons();
192 $jobs = new RunJobs;
193 $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
194 $jobs->execute();
195
196 # ------------------------
197 $dbr = wfGetDB( DB_REPLICA );
198 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
199 $n = $res->numRows();
200 $res->free();
201
202 $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
203 }
204
205 /**
206 * @covers WikiPage::doDeleteUpdates
207 */
208 public function testDoDeleteUpdates() {
209 $page = $this->createPage(
210 __METHOD__,
211 "[[original text]] foo",
212 CONTENT_MODEL_WIKITEXT
213 );
214 $id = $page->getId();
215
216 // Similar to MovePage logic
217 wfGetDB( DB_MASTER )->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
218 $page->doDeleteUpdates( $id );
219
220 // Run the job queue
221 JobQueueGroup::destroySingletons();
222 $jobs = new RunJobs;
223 $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
224 $jobs->execute();
225
226 # ------------------------
227 $dbr = wfGetDB( DB_REPLICA );
228 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
229 $n = $res->numRows();
230 $res->free();
231
232 $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
233 }
234
235 /**
236 * @covers WikiPage::getRevision
237 */
238 public function testGetRevision() {
239 $page = $this->newPage( __METHOD__ );
240
241 $rev = $page->getRevision();
242 $this->assertNull( $rev );
243
244 # -----------------
245 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
246
247 $rev = $page->getRevision();
248
249 $this->assertEquals( $page->getLatest(), $rev->getId() );
250 $this->assertEquals( "some text", $rev->getContent()->getNativeData() );
251 }
252
253 /**
254 * @covers WikiPage::getContent
255 */
256 public function testGetContent() {
257 $page = $this->newPage( __METHOD__ );
258
259 $content = $page->getContent();
260 $this->assertNull( $content );
261
262 # -----------------
263 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
264
265 $content = $page->getContent();
266 $this->assertEquals( "some text", $content->getNativeData() );
267 }
268
269 /**
270 * @covers WikiPage::getContentModel
271 */
272 public function testGetContentModel() {
273 global $wgContentHandlerUseDB;
274
275 if ( !$wgContentHandlerUseDB ) {
276 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
277 }
278
279 $page = $this->createPage(
280 __METHOD__,
281 "some text",
282 CONTENT_MODEL_JAVASCRIPT
283 );
284
285 $page = new WikiPage( $page->getTitle() );
286 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
287 }
288
289 /**
290 * @covers WikiPage::getContentHandler
291 */
292 public function testGetContentHandler() {
293 global $wgContentHandlerUseDB;
294
295 if ( !$wgContentHandlerUseDB ) {
296 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
297 }
298
299 $page = $this->createPage(
300 __METHOD__,
301 "some text",
302 CONTENT_MODEL_JAVASCRIPT
303 );
304
305 $page = new WikiPage( $page->getTitle() );
306 $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) );
307 }
308
309 /**
310 * @covers WikiPage::exists
311 */
312 public function testExists() {
313 $page = $this->newPage( __METHOD__ );
314 $this->assertFalse( $page->exists() );
315
316 # -----------------
317 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
318 $this->assertTrue( $page->exists() );
319
320 $page = new WikiPage( $page->getTitle() );
321 $this->assertTrue( $page->exists() );
322
323 # -----------------
324 $page->doDeleteArticle( "done testing" );
325 $this->assertFalse( $page->exists() );
326
327 $page = new WikiPage( $page->getTitle() );
328 $this->assertFalse( $page->exists() );
329 }
330
331 public function provideHasViewableContent() {
332 return [
333 [ 'WikiPageTest_testHasViewableContent', false, true ],
334 [ 'Special:WikiPageTest_testHasViewableContent', false ],
335 [ 'MediaWiki:WikiPageTest_testHasViewableContent', false ],
336 [ 'Special:Userlogin', true ],
337 [ 'MediaWiki:help', true ],
338 ];
339 }
340
341 /**
342 * @dataProvider provideHasViewableContent
343 * @covers WikiPage::hasViewableContent
344 */
345 public function testHasViewableContent( $title, $viewable, $create = false ) {
346 $page = $this->newPage( $title );
347 $this->assertEquals( $viewable, $page->hasViewableContent() );
348
349 if ( $create ) {
350 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
351 $this->assertTrue( $page->hasViewableContent() );
352
353 $page = new WikiPage( $page->getTitle() );
354 $this->assertTrue( $page->hasViewableContent() );
355 }
356 }
357
358 public function provideGetRedirectTarget() {
359 return [
360 [ 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ],
361 [
362 'WikiPageTest_testGetRedirectTarget_2',
363 CONTENT_MODEL_WIKITEXT,
364 "#REDIRECT [[hello world]]",
365 "Hello world"
366 ],
367 ];
368 }
369
370 /**
371 * @dataProvider provideGetRedirectTarget
372 * @covers WikiPage::getRedirectTarget
373 */
374 public function testGetRedirectTarget( $title, $model, $text, $target ) {
375 $this->setMwGlobals( [
376 'wgCapitalLinks' => true,
377 ] );
378
379 $page = $this->createPage( $title, $text, $model );
380
381 # sanity check, because this test seems to fail for no reason for some people.
382 $c = $page->getContent();
383 $this->assertEquals( 'WikitextContent', get_class( $c ) );
384
385 # now, test the actual redirect
386 $t = $page->getRedirectTarget();
387 $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() );
388 }
389
390 /**
391 * @dataProvider provideGetRedirectTarget
392 * @covers WikiPage::isRedirect
393 */
394 public function testIsRedirect( $title, $model, $text, $target ) {
395 $page = $this->createPage( $title, $text, $model );
396 $this->assertEquals( !is_null( $target ), $page->isRedirect() );
397 }
398
399 public function provideIsCountable() {
400 return [
401
402 // any
403 [ 'WikiPageTest_testIsCountable',
404 CONTENT_MODEL_WIKITEXT,
405 '',
406 'any',
407 true
408 ],
409 [ 'WikiPageTest_testIsCountable',
410 CONTENT_MODEL_WIKITEXT,
411 'Foo',
412 'any',
413 true
414 ],
415
416 // comma
417 [ 'WikiPageTest_testIsCountable',
418 CONTENT_MODEL_WIKITEXT,
419 'Foo',
420 'comma',
421 false
422 ],
423 [ 'WikiPageTest_testIsCountable',
424 CONTENT_MODEL_WIKITEXT,
425 'Foo, bar',
426 'comma',
427 true
428 ],
429
430 // link
431 [ 'WikiPageTest_testIsCountable',
432 CONTENT_MODEL_WIKITEXT,
433 'Foo',
434 'link',
435 false
436 ],
437 [ 'WikiPageTest_testIsCountable',
438 CONTENT_MODEL_WIKITEXT,
439 'Foo [[bar]]',
440 'link',
441 true
442 ],
443
444 // redirects
445 [ 'WikiPageTest_testIsCountable',
446 CONTENT_MODEL_WIKITEXT,
447 '#REDIRECT [[bar]]',
448 'any',
449 false
450 ],
451 [ 'WikiPageTest_testIsCountable',
452 CONTENT_MODEL_WIKITEXT,
453 '#REDIRECT [[bar]]',
454 'comma',
455 false
456 ],
457 [ 'WikiPageTest_testIsCountable',
458 CONTENT_MODEL_WIKITEXT,
459 '#REDIRECT [[bar]]',
460 'link',
461 false
462 ],
463
464 // not a content namespace
465 [ 'Talk:WikiPageTest_testIsCountable',
466 CONTENT_MODEL_WIKITEXT,
467 'Foo',
468 'any',
469 false
470 ],
471 [ 'Talk:WikiPageTest_testIsCountable',
472 CONTENT_MODEL_WIKITEXT,
473 'Foo, bar',
474 'comma',
475 false
476 ],
477 [ 'Talk:WikiPageTest_testIsCountable',
478 CONTENT_MODEL_WIKITEXT,
479 'Foo [[bar]]',
480 'link',
481 false
482 ],
483
484 // not a content namespace, different model
485 [ 'MediaWiki:WikiPageTest_testIsCountable.js',
486 null,
487 'Foo',
488 'any',
489 false
490 ],
491 [ 'MediaWiki:WikiPageTest_testIsCountable.js',
492 null,
493 'Foo, bar',
494 'comma',
495 false
496 ],
497 [ 'MediaWiki:WikiPageTest_testIsCountable.js',
498 null,
499 'Foo [[bar]]',
500 'link',
501 false
502 ],
503 ];
504 }
505
506 /**
507 * @dataProvider provideIsCountable
508 * @covers WikiPage::isCountable
509 */
510 public function testIsCountable( $title, $model, $text, $mode, $expected ) {
511 global $wgContentHandlerUseDB;
512
513 $this->setMwGlobals( 'wgArticleCountMethod', $mode );
514
515 $title = Title::newFromText( $title );
516
517 if ( !$wgContentHandlerUseDB
518 && $model
519 && ContentHandler::getDefaultModelFor( $title ) != $model
520 ) {
521 $this->markTestSkipped( "Can not use non-default content model $model for "
522 . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." );
523 }
524
525 $page = $this->createPage( $title, $text, $model );
526
527 $editInfo = $page->prepareContentForEdit( $page->getContent() );
528
529 $v = $page->isCountable();
530 $w = $page->isCountable( $editInfo );
531
532 $this->assertEquals(
533 $expected,
534 $v,
535 "isCountable( null ) returned unexpected value " . var_export( $v, true )
536 . " instead of " . var_export( $expected, true )
537 . " in mode `$mode` for text \"$text\""
538 );
539
540 $this->assertEquals(
541 $expected,
542 $w,
543 "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true )
544 . " instead of " . var_export( $expected, true )
545 . " in mode `$mode` for text \"$text\""
546 );
547 }
548
549 public function provideGetParserOutput() {
550 return [
551 [
552 CONTENT_MODEL_WIKITEXT,
553 "hello ''world''\n",
554 "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
555 ],
556 // @todo more...?
557 ];
558 }
559
560 /**
561 * @dataProvider provideGetParserOutput
562 * @covers WikiPage::getParserOutput
563 */
564 public function testGetParserOutput( $model, $text, $expectedHtml ) {
565 $page = $this->createPage( __METHOD__, $text, $model );
566
567 $opt = $page->makeParserOptions( 'canonical' );
568 $po = $page->getParserOutput( $opt );
569 $text = $po->getText();
570
571 $text = trim( preg_replace( '/<!--.*?-->/sm', '', $text ) ); # strip injected comments
572 $text = preg_replace( '!\s*(</p>|</div>)!sm', '\1', $text ); # don't let tidy confuse us
573
574 $this->assertEquals( $expectedHtml, $text );
575
576 return $po;
577 }
578
579 /**
580 * @covers WikiPage::getParserOutput
581 */
582 public function testGetParserOutput_nonexisting() {
583 $page = new WikiPage( Title::newFromText( __METHOD__ ) );
584
585 $opt = new ParserOptions();
586 $po = $page->getParserOutput( $opt );
587
588 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
589 }
590
591 /**
592 * @covers WikiPage::getParserOutput
593 */
594 public function testGetParserOutput_badrev() {
595 $page = $this->createPage( __METHOD__, 'dummy', CONTENT_MODEL_WIKITEXT );
596
597 $opt = new ParserOptions();
598 $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 );
599
600 // @todo would be neat to also test deleted revision
601
602 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." );
603 }
604
605 public static $sections =
606
607 "Intro
608
609 == stuff ==
610 hello world
611
612 == test ==
613 just a test
614
615 == foo ==
616 more stuff
617 ";
618
619 public function dataReplaceSection() {
620 // NOTE: assume the Help namespace to contain wikitext
621 return [
622 [ 'Help:WikiPageTest_testReplaceSection',
623 CONTENT_MODEL_WIKITEXT,
624 self::$sections,
625 "0",
626 "No more",
627 null,
628 trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
629 ],
630 [ 'Help:WikiPageTest_testReplaceSection',
631 CONTENT_MODEL_WIKITEXT,
632 self::$sections,
633 "",
634 "No more",
635 null,
636 "No more"
637 ],
638 [ 'Help:WikiPageTest_testReplaceSection',
639 CONTENT_MODEL_WIKITEXT,
640 self::$sections,
641 "2",
642 "== TEST ==\nmore fun",
643 null,
644 trim( preg_replace( '/^== test ==.*== foo ==/sm',
645 "== TEST ==\nmore fun\n\n== foo ==",
646 self::$sections ) )
647 ],
648 [ 'Help:WikiPageTest_testReplaceSection',
649 CONTENT_MODEL_WIKITEXT,
650 self::$sections,
651 "8",
652 "No more",
653 null,
654 trim( self::$sections )
655 ],
656 [ 'Help:WikiPageTest_testReplaceSection',
657 CONTENT_MODEL_WIKITEXT,
658 self::$sections,
659 "new",
660 "No more",
661 "New",
662 trim( self::$sections ) . "\n\n== New ==\n\nNo more"
663 ],
664 ];
665 }
666
667 /**
668 * @dataProvider dataReplaceSection
669 * @covers WikiPage::replaceSectionContent
670 */
671 public function testReplaceSectionContent( $title, $model, $text, $section,
672 $with, $sectionTitle, $expected
673 ) {
674 $page = $this->createPage( $title, $text, $model );
675
676 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
677 $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
678
679 $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
680 }
681
682 /**
683 * @dataProvider dataReplaceSection
684 * @covers WikiPage::replaceSectionAtRev
685 */
686 public function testReplaceSectionAtRev( $title, $model, $text, $section,
687 $with, $sectionTitle, $expected
688 ) {
689 $page = $this->createPage( $title, $text, $model );
690 $baseRevId = $page->getLatest();
691
692 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
693 $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId );
694
695 $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
696 }
697
698 /**
699 * @covers WikiPage::getOldestRevision
700 */
701 public function testGetOldestRevision() {
702 $page = $this->newPage( __METHOD__ );
703 $page->doEditContent(
704 new WikitextContent( 'one' ),
705 "first edit",
706 EDIT_NEW
707 );
708 $rev1 = $page->getRevision();
709
710 $page = new WikiPage( $page->getTitle() );
711 $page->doEditContent(
712 new WikitextContent( 'two' ),
713 "second edit",
714 EDIT_UPDATE
715 );
716
717 $page = new WikiPage( $page->getTitle() );
718 $page->doEditContent(
719 new WikitextContent( 'three' ),
720 "third edit",
721 EDIT_UPDATE
722 );
723
724 // sanity check
725 $this->assertNotEquals(
726 $rev1->getId(),
727 $page->getRevision()->getId(),
728 '$page->getRevision()->getId()'
729 );
730
731 // actual test
732 $this->assertEquals(
733 $rev1->getId(),
734 $page->getOldestRevision()->getId(),
735 '$page->getOldestRevision()->getId()'
736 );
737 }
738
739 /**
740 * @todo FIXME: this is a better rollback test than the one below, but it
741 * keeps failing in jenkins for some reason.
742 */
743 public function broken_testDoRollback() {
744 $admin = $this->getTestSysop()->getUser();
745
746 $text = "one";
747 $page = $this->newPage( __METHOD__ );
748 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
749 "section one", EDIT_NEW, false, $admin );
750
751 $user1 = $this->getTestUser()->getUser();
752 $text .= "\n\ntwo";
753 $page = new WikiPage( $page->getTitle() );
754 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
755 "adding section two", 0, false, $user1 );
756
757 $user2 = $this->getTestUser()->getUser();
758 $text .= "\n\nthree";
759 $page = new WikiPage( $page->getTitle() );
760 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
761 "adding section three", 0, false, $user2 );
762
763 # we are having issues with doRollback spuriously failing. Apparently
764 # the last revision somehow goes missing or not committed under some
765 # circumstances. So, make sure the last revision has the right user name.
766 $dbr = wfGetDB( DB_REPLICA );
767 $this->assertEquals( 3, Revision::countByPageId( $dbr, $page->getId() ) );
768
769 $page = new WikiPage( $page->getTitle() );
770 $rev3 = $page->getRevision();
771 $this->assertEquals( '127.0.2.13', $rev3->getUserText() );
772
773 $rev2 = $rev3->getPrevious();
774 $this->assertEquals( '127.0.1.11', $rev2->getUserText() );
775
776 $rev1 = $rev2->getPrevious();
777 $this->assertEquals( 'Admin', $rev1->getUserText() );
778
779 # now, try the actual rollback
780 $token = $admin->getEditToken(
781 [ $page->getTitle()->getPrefixedText(), $user2->getName() ],
782 null
783 );
784 $errors = $page->doRollback(
785 $user2->getName(),
786 "testing revert",
787 $token,
788 false,
789 $details,
790 $admin
791 );
792
793 if ( $errors ) {
794 $this->fail( "Rollback failed:\n" . print_r( $errors, true )
795 . ";\n" . print_r( $details, true ) );
796 }
797
798 $page = new WikiPage( $page->getTitle() );
799 $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
800 "rollback did not revert to the correct revision" );
801 $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
802 }
803
804 /**
805 * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason.
806 * @covers WikiPage::doRollback
807 */
808 public function testDoRollback() {
809 $admin = $this->getTestSysop()->getUser();
810
811 $text = "one";
812 $page = $this->newPage( __METHOD__ );
813 $page->doEditContent(
814 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
815 "section one",
816 EDIT_NEW,
817 false,
818 $admin
819 );
820 $rev1 = $page->getRevision();
821
822 $user1 = $this->getTestUser()->getUser();
823 $text .= "\n\ntwo";
824 $page = new WikiPage( $page->getTitle() );
825 $page->doEditContent(
826 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
827 "adding section two",
828 0,
829 false,
830 $user1
831 );
832
833 # now, try the rollback
834 $token = $admin->getEditToken( 'rollback' );
835 $errors = $page->doRollback(
836 $user1->getName(),
837 "testing revert",
838 $token,
839 false,
840 $details,
841 $admin
842 );
843
844 if ( $errors ) {
845 $this->fail( "Rollback failed:\n" . print_r( $errors, true )
846 . ";\n" . print_r( $details, true ) );
847 }
848
849 $page = new WikiPage( $page->getTitle() );
850 $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
851 "rollback did not revert to the correct revision" );
852 $this->assertEquals( "one", $page->getContent()->getNativeData() );
853 }
854
855 /**
856 * @covers WikiPage::doRollback
857 */
858 public function testDoRollbackFailureSameContent() {
859 $admin = $this->getTestSysop()->getUser();
860
861 $text = "one";
862 $page = $this->newPage( __METHOD__ );
863 $page->doEditContent(
864 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
865 "section one",
866 EDIT_NEW,
867 false,
868 $admin
869 );
870 $rev1 = $page->getRevision();
871
872 $user1 = $this->getTestUser( [ 'sysop' ] )->getUser();
873 $text .= "\n\ntwo";
874 $page = new WikiPage( $page->getTitle() );
875 $page->doEditContent(
876 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
877 "adding section two",
878 0,
879 false,
880 $user1
881 );
882
883 # now, do a the rollback from the same user was doing the edit before
884 $resultDetails = [];
885 $token = $user1->getEditToken( 'rollback' );
886 $errors = $page->doRollback(
887 $user1->getName(),
888 "testing revert same user",
889 $token,
890 false,
891 $resultDetails,
892 $admin
893 );
894
895 $this->assertEquals( [], $errors, "Rollback failed same user" );
896
897 # now, try the rollback
898 $resultDetails = [];
899 $token = $admin->getEditToken( 'rollback' );
900 $errors = $page->doRollback(
901 $user1->getName(),
902 "testing revert",
903 $token,
904 false,
905 $resultDetails,
906 $admin
907 );
908
909 $this->assertEquals(
910 [
911 [
912 'alreadyrolled',
913 __METHOD__,
914 $user1->getName(),
915 $admin->getName(),
916 ],
917 ],
918 $errors,
919 "Rollback not failed"
920 );
921
922 $page = new WikiPage( $page->getTitle() );
923 $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
924 "rollback did not revert to the correct revision" );
925 $this->assertEquals( "one", $page->getContent()->getNativeData() );
926 }
927
928 /**
929 * Tests tagging for edits that do rollback action
930 * @covers WikiPage::doRollback
931 */
932 public function testDoRollbackTagging() {
933 if ( !in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
934 $this->markTestSkipped( 'Rollback tag deactivated, skipped the test.' );
935 }
936
937 $admin = new User();
938 $admin->setName( 'Administrator' );
939 $admin->addToDatabase();
940
941 $text = 'First line';
942 $page = $this->newPage( 'WikiPageTest_testDoRollbackTagging' );
943 $page->doEditContent(
944 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
945 'Added first line',
946 EDIT_NEW,
947 false,
948 $admin
949 );
950
951 $secondUser = new User();
952 $secondUser->setName( '92.65.217.32' );
953 $text .= '\n\nSecond line';
954 $page = new WikiPage( $page->getTitle() );
955 $page->doEditContent(
956 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
957 'Adding second line',
958 0,
959 false,
960 $secondUser
961 );
962
963 // Now, try the rollback
964 $admin->addGroup( 'sysop' ); // Make the test user a sysop
965 $token = $admin->getEditToken( 'rollback' );
966 $errors = $page->doRollback(
967 $secondUser->getName(),
968 'testing rollback',
969 $token,
970 false,
971 $resultDetails,
972 $admin
973 );
974
975 // If doRollback completed without errors
976 if ( $errors === [] ) {
977 $tags = $resultDetails[ 'tags' ];
978 $this->assertContains( 'mw-rollback', $tags );
979 }
980 }
981
982 public function provideGetAutoDeleteReason() {
983 return [
984 [
985 [],
986 false,
987 false
988 ],
989
990 [
991 [
992 [ "first edit", null ],
993 ],
994 "/first edit.*only contributor/",
995 false
996 ],
997
998 [
999 [
1000 [ "first edit", null ],
1001 [ "second edit", null ],
1002 ],
1003 "/second edit.*only contributor/",
1004 true
1005 ],
1006
1007 [
1008 [
1009 [ "first edit", "127.0.2.22" ],
1010 [ "second edit", "127.0.3.33" ],
1011 ],
1012 "/second edit/",
1013 true
1014 ],
1015
1016 [
1017 [
1018 [
1019 "first edit: "
1020 . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
1021 . " nonumy eirmod tempor invidunt ut labore et dolore magna "
1022 . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
1023 . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
1024 . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'",
1025 null
1026 ],
1027 ],
1028 '/first edit:.*\.\.\."/',
1029 false
1030 ],
1031
1032 [
1033 [
1034 [ "first edit", "127.0.2.22" ],
1035 [ "", "127.0.3.33" ],
1036 ],
1037 "/before blanking.*first edit/",
1038 true
1039 ],
1040
1041 ];
1042 }
1043
1044 /**
1045 * @dataProvider provideGetAutoDeleteReason
1046 * @covers WikiPage::getAutoDeleteReason
1047 */
1048 public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
1049 global $wgUser;
1050
1051 // NOTE: assume Help namespace to contain wikitext
1052 $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
1053
1054 $c = 1;
1055
1056 foreach ( $edits as $edit ) {
1057 $user = new User();
1058
1059 if ( !empty( $edit[1] ) ) {
1060 $user->setName( $edit[1] );
1061 } else {
1062 $user = $wgUser;
1063 }
1064
1065 $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
1066
1067 $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
1068
1069 $c += 1;
1070 }
1071
1072 $reason = $page->getAutoDeleteReason( $hasHistory );
1073
1074 if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) {
1075 $this->assertEquals( $expectedResult, $reason );
1076 } else {
1077 $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
1078 "Autosummary didn't match expected pattern $expectedResult: $reason" );
1079 }
1080
1081 $this->assertEquals( $expectedHistory, $hasHistory,
1082 "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
1083
1084 $page->doDeleteArticle( "done" );
1085 }
1086
1087 public function providePreSaveTransform() {
1088 return [
1089 [ 'hello this is ~~~',
1090 "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
1091 ],
1092 [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1093 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1094 ],
1095 ];
1096 }
1097
1098 /**
1099 * @covers WikiPage::factory
1100 */
1101 public function testWikiPageFactory() {
1102 $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
1103 $page = WikiPage::factory( $title );
1104 $this->assertEquals( 'WikiFilePage', get_class( $page ) );
1105
1106 $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
1107 $page = WikiPage::factory( $title );
1108 $this->assertEquals( 'WikiCategoryPage', get_class( $page ) );
1109
1110 $title = Title::makeTitle( NS_MAIN, 'SomePage' );
1111 $page = WikiPage::factory( $title );
1112 $this->assertEquals( 'WikiPage', get_class( $page ) );
1113 }
1114
1115 /**
1116 * @dataProvider provideCommentMigrationOnDeletion
1117 *
1118 * @param int $writeStage
1119 * @param int $readStage
1120 */
1121 public function testCommentMigrationOnDeletion( $writeStage, $readStage ) {
1122 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $writeStage );
1123 $dbr = wfGetDB( DB_REPLICA );
1124
1125 $page = $this->createPage(
1126 __METHOD__,
1127 "foo",
1128 CONTENT_MODEL_WIKITEXT
1129 );
1130 $revid = $page->getLatest();
1131 if ( $writeStage > MIGRATION_OLD ) {
1132 $comment_id = $dbr->selectField(
1133 'revision_comment_temp',
1134 'revcomment_comment_id',
1135 [ 'revcomment_rev' => $revid ],
1136 __METHOD__
1137 );
1138 }
1139
1140 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $readStage );
1141
1142 $page->doDeleteArticle( "testing deletion" );
1143
1144 if ( $readStage > MIGRATION_OLD ) {
1145 // Didn't leave behind any 'revision_comment_temp' rows
1146 $n = $dbr->selectField(
1147 'revision_comment_temp', 'COUNT(*)', [ 'revcomment_rev' => $revid ], __METHOD__
1148 );
1149 $this->assertEquals( 0, $n, 'no entry in revision_comment_temp after deletion' );
1150
1151 // Copied or upgraded the comment_id, as applicable
1152 $ar_comment_id = $dbr->selectField(
1153 'archive',
1154 'ar_comment_id',
1155 [ 'ar_rev_id' => $revid ],
1156 __METHOD__
1157 );
1158 if ( $writeStage > MIGRATION_OLD ) {
1159 $this->assertSame( $comment_id, $ar_comment_id );
1160 } else {
1161 $this->assertNotEquals( 0, $ar_comment_id );
1162 }
1163 }
1164
1165 // Copied rev_comment, if applicable
1166 if ( $readStage <= MIGRATION_WRITE_BOTH && $writeStage <= MIGRATION_WRITE_BOTH ) {
1167 $ar_comment = $dbr->selectField(
1168 'archive',
1169 'ar_comment',
1170 [ 'ar_rev_id' => $revid ],
1171 __METHOD__
1172 );
1173 $this->assertSame( 'testing', $ar_comment );
1174 }
1175 }
1176
1177 public function provideCommentMigrationOnDeletion() {
1178 return [
1179 [ MIGRATION_OLD, MIGRATION_OLD ],
1180 [ MIGRATION_OLD, MIGRATION_WRITE_BOTH ],
1181 [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
1182 [ MIGRATION_WRITE_BOTH, MIGRATION_OLD ],
1183 [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_BOTH ],
1184 [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
1185 [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
1186 [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_BOTH ],
1187 [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_NEW ],
1188 [ MIGRATION_WRITE_NEW, MIGRATION_NEW ],
1189 [ MIGRATION_NEW, MIGRATION_WRITE_BOTH ],
1190 [ MIGRATION_NEW, MIGRATION_WRITE_NEW ],
1191 [ MIGRATION_NEW, MIGRATION_NEW ],
1192 ];
1193 }
1194
1195 }