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