MediaWiki REL1_30
WikiPageTest.php
Go to the documentation of this file.
1<?php
2
10
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
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
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
101 public function testDoEditContent() {
102 $page = $this->newPage( "WikiPageTest_testDoEditContent" );
103 $title = $page->getTitle();
104
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,
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 # ------------------------
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 # ------------------------
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,
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 # ------------------------
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
163 public function testDoDeleteArticle() {
164 $page = $this->createPage(
165 "WikiPageTest_testDoDeleteArticle",
166 "[[original text]] foo",
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
195 $jobs = new RunJobs;
196 $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
197 $jobs->execute();
198
199 # ------------------------
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
211 public function testDoDeleteUpdates() {
212 $page = $this->createPage(
213 "WikiPageTest_testDoDeleteArticle",
214 "[[original text]] foo",
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
225 $jobs = new RunJobs;
226 $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
227 $jobs->execute();
228
229 # ------------------------
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
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
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
275 public function testGetContentModel() {
277
278 if ( !$wgContentHandlerUseDB ) {
279 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
280 }
281
282 $page = $this->createPage(
283 "WikiPageTest_testGetContentModel",
284 "some text",
286 );
287
288 $page = new WikiPage( $page->getTitle() );
289 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
290 }
291
295 public function testGetContentHandler() {
297
298 if ( !$wgContentHandlerUseDB ) {
299 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
300 }
301
302 $page = $this->createPage(
303 "WikiPageTest_testGetContentHandler",
304 "some text",
306 );
307
308 $page = new WikiPage( $page->getTitle() );
309 $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) );
310 }
311
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
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',
367 "#REDIRECT [[hello world]]",
368 "Hello world"
369 ],
370 ];
371 }
372
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
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',
408 '',
409 'any',
410 true
411 ],
412 [ 'WikiPageTest_testIsCountable',
414 'Foo',
415 'any',
416 true
417 ],
418
419 // comma
420 [ 'WikiPageTest_testIsCountable',
422 'Foo',
423 'comma',
424 false
425 ],
426 [ 'WikiPageTest_testIsCountable',
428 'Foo, bar',
429 'comma',
430 true
431 ],
432
433 // link
434 [ 'WikiPageTest_testIsCountable',
436 'Foo',
437 'link',
438 false
439 ],
440 [ 'WikiPageTest_testIsCountable',
442 'Foo [[bar]]',
443 'link',
444 true
445 ],
446
447 // redirects
448 [ 'WikiPageTest_testIsCountable',
450 '#REDIRECT [[bar]]',
451 'any',
452 false
453 ],
454 [ 'WikiPageTest_testIsCountable',
456 '#REDIRECT [[bar]]',
457 'comma',
458 false
459 ],
460 [ 'WikiPageTest_testIsCountable',
462 '#REDIRECT [[bar]]',
463 'link',
464 false
465 ],
466
467 // not a content namespace
468 [ 'Talk:WikiPageTest_testIsCountable',
470 'Foo',
471 'any',
472 false
473 ],
474 [ 'Talk:WikiPageTest_testIsCountable',
476 'Foo, bar',
477 'comma',
478 false
479 ],
480 [ 'Talk:WikiPageTest_testIsCountable',
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
513 public function testIsCountable( $title, $model, $text, $mode, $expected ) {
515
516 $this->setMwGlobals( 'wgArticleCountMethod', $mode );
517
518 $title = Title::newFromText( $title );
519
521 && $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 [
556 "hello ''world''\n",
557 "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
558 ],
559 // @todo more...?
560 ];
561 }
562
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
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
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 ==
616hello world
617
618== test ==
619just a test
620
621== foo ==
622more 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
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
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
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
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
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
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
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
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
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}
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second redirect
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
static destroySingletons()
Destroy the singleton instances.
MediaWiki exception.
loadParamsAndArgs( $self=null, $opts=null, $args=null)
Process command line arguments $mOptions becomes an array with keys set to the option names $mArgs be...
Base class that store and restore the Language objects.
getDefaultWikitextNS()
Returns the ID of a namespace that defaults to Wikitext.
setMwGlobals( $pairs, $value=null)
Sets a global, maintaining a stashed version of the previous global to be restored in tearDown.
Maintenance script that runs pending jobs.
Definition runJobs.php:33
Represents a title within MediaWiki.
Definition Title.php:39
ContentHandler Database ^— important, causes temporary tables to be used instead of the real database...
static provideGetRedirectTarget()
testIsRedirect( $title, $model, $text, $target)
provideGetRedirectTarget WikiPage::isRedirect
__construct( $name=null, array $data=[], $dataName='')
static provideIsCountable()
testGetRevision()
WikiPage::getRevision.
testHasViewableContent( $title, $viewable, $create=false)
provideHasViewableContent WikiPage::hasViewableContent
testGetContent()
WikiPage::getContent.
static provideHasViewableContent()
testGetParserOutput( $model, $text, $expectedHtml)
provideGetParserOutput WikiPage::getParserOutput
testIsCountable( $title, $model, $text, $mode, $expected)
provideIsCountable WikiPage::isCountable
testGetRedirectTarget( $title, $model, $text, $target)
provideGetRedirectTarget WikiPage::getRedirectTarget
testDoDeleteUpdates()
WikiPage::doDeleteUpdates.
newPage( $title, $model=null)
testGetContentModel()
WikiPage::getContentModel.
testExists()
WikiPage::exists.
testGetContentHandler()
WikiPage::getContentHandler.
createPage( $page, $text, $model=null)
static provideGetParserOutput()
testDoDeleteArticle()
WikiPage::doDeleteArticle.
testDoEditContent()
WikiPage::doEditContent WikiPage::doModify WikiPage::doCreate WikiPage::doEditUpdates.
Class representing a MediaWiki article and history.
Definition WikiPage.php:37
$res
Definition database.txt:21
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------ Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server. Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It 's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use. Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves. The master writes thequery to the binlog when the transaction is committed. The slaves pollthe binlog and start executing the query as soon as it appears. They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes. Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load. MediaWiki 's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database. All edits and other write operations will berefused, with an error returned to the user. This gives the slaves achance to catch up. Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order. A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests. This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it. Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in "lagged slave mode". Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode(). The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time. Multi-row INSERT ...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it 's not practical to guarantee a low-lagenvironment. Lag will usually be less than one second, but mayoccasionally be up to 30 seconds. For scalability, it 's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer. So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn 't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum. In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------ Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks. By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent. Locks willbe held from the time when the query is done until the commit. So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction. Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add etc
Definition design.txt:19
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:236
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:237
const EDIT_NEW
Definition Defines.php:153
the array() calling protocol came about after MediaWiki 1.4rc1.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e rollback
Definition hooks.txt:1757
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:962
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:1976
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1760
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26