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