7use PHPUnit\Framework\MockObject\MockObject;
8use Wikimedia\TestingAccessWrapper;
18 parent::__construct( $name, $data, $dataName );
20 $this->tablesUsed = array_merge(
64 'wgMultiContentRevisionSchemaMigrationStage',
67 $this->pagesToDelete = [];
73 foreach ( $this->pagesToDelete as $p ) {
78 $p->doDeleteArticle(
"testing done." );
94 private function newPage( $title, $model =
null ) {
95 if ( is_string( $title ) ) {
97 $title = Title::newFromText( $title, $ns );
102 $this->pagesToDelete[] = $p;
115 if ( is_string( $page ) || $page instanceof
Title ) {
116 $page = $this->
newPage( $page, $model );
124 $content = ContentHandler::makeContent(
$content, $page->getTitle(), $model );
131 $updater = $page->newPageUpdater( $user );
133 foreach (
$content as $role => $cnt ) {
134 $updater->setContent( $role, $cnt );
137 $updater->saveRevision( CommentStoreComment::newUnsavedComment(
"testing" ) );
147 $sysop = $this->
getTestUser( [
'sysop' ] )->getUser();
149 $page = $this->
createPage( __METHOD__, __METHOD__,
null, $user );
150 $title = $page->getTitle();
152 $content = ContentHandler::makeContent(
153 "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
154 .
" nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
158 $content2 = ContentHandler::makeContent(
159 "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
160 .
"Stet clita kasd [[gubergren]], no sea takimata sanctus est. ~~~~",
165 $edit = $page->prepareContentForEdit(
$content,
null, $user,
null,
false );
167 $this->assertInstanceOf(
168 ParserOptions::class,
172 $this->assertContains(
'</a>', $edit->output->getText(),
"output" );
173 $this->assertContains(
174 'consetetur sadipscing elitr',
175 $edit->output->getText(),
179 $this->assertTrue(
$content->equals( $edit->newContent ),
"newContent field" );
180 $this->assertTrue(
$content->equals( $edit->pstContent ),
"pstContent field" );
181 $this->assertSame( $edit->output, $edit->output,
"output field" );
182 $this->assertSame( $edit->popts, $edit->popts,
"popts field" );
183 $this->assertSame(
null, $edit->revid,
"revid field" );
186 $sameEdit = $page->prepareContentForEdit(
$content,
null, $user,
null,
false );
188 $this->assertSame( $edit->pstContent, $sameEdit->pstContent,
're-use output' );
189 $this->assertSame( $edit->output, $sameEdit->output,
're-use output' );
192 $rev = $page->getRevision();
193 $edit2 = $page->prepareContentForEdit( $content2,
null, $user,
null,
false );
195 $this->assertContains(
'At vero eos', $edit2->pstContent->serialize(),
"content" );
198 $this->assertContains(
'[[gubergren]]', $edit2->pstContent->serialize() );
199 $this->assertNotContains(
'~~~~', $edit2->pstContent->serialize() );
201 $edit3 = $page->prepareContentForEdit( $content2,
null, $sysop,
null,
false );
216 $siteStatsUpdate = SiteStatsUpdate::factory(
217 [
'edits' => 1000,
'articles' => 1000,
'pages' => 1000 ]
219 $siteStatsUpdate->doUpdate();
221 $page = $this->
createPage( __METHOD__, __METHOD__ );
226 'page' => $page->getId(),
227 'title' => $page->getTitle(),
228 'comment' => __METHOD__,
229 'minor_edit' =>
true,
230 'text' => __METHOD__ .
' [[|foo]][[bar]]',
231 'user' => $user->getId(),
232 'user_text' => $user->getName(),
233 'timestamp' =>
'20170707040404',
239 $page->doEditUpdates( $revision, $user );
244 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $page->getId() ] );
245 $n =
$res->numRows();
248 $this->assertEquals( 1, $n,
'pagelinks should contain only one link if PST was not applied' );
258 $page = $this->
newPage( __METHOD__ );
259 $title = $page->getTitle();
263 $user2 = $this->
getTestUser( [
'confirmed' ] )->getUser();
265 $content = ContentHandler::makeContent(
266 "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
267 .
" nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
272 $preparedEditBefore = $page->prepareContentForEdit(
$content,
null, $user1 );
276 $this->assertTrue(
$status->isOK(),
'OK' );
277 $this->assertTrue(
$status->value[
'new'],
'new' );
278 $this->assertNotNull(
$status->value[
'revision'],
'revision' );
279 $this->assertSame(
$status->value[
'revision']->getId(), $page->getRevision()->getId() );
280 $this->assertSame(
$status->value[
'revision']->getSha1(), $page->getRevision()->getSha1() );
281 $this->assertTrue(
$status->value[
'revision']->getContent()->equals(
$content ),
'equals' );
283 $rev = $page->getRevision();
284 $preparedEditAfter = $page->prepareContentForEdit(
$content,
$rev, $user1 );
286 $this->assertNotNull(
$rev->getRecentChange() );
287 $this->assertSame(
$rev->getId(), (
int)
$rev->getRecentChange()->getAttribute(
'rc_this_oldid' ) );
290 $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
292 $id = $page->getId();
297 [
'log_type',
'log_action' ],
298 [
'log_page' => $id ],
299 [ [
'create',
'create' ] ]
302 $this->assertTrue( $title->getArticleID() > 0,
"Title object should have new page id" );
303 $this->assertTrue( $id > 0,
"WikiPage should have new page id" );
304 $this->assertTrue( $title->exists(),
"Title object should indicate that the page now exists" );
305 $this->assertTrue( $page->exists(),
"WikiPage object should indicate that the page now exists" );
307 # ------------------------
309 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
310 $n =
$res->numRows();
313 $this->assertEquals( 1, $n,
'pagelinks should contain one link from the page' );
315 # ------------------------
318 $retrieved = $page->getContent();
319 $this->assertTrue(
$content->equals( $retrieved ),
'retrieved content doesn\'t equal original' );
321 # ------------------------
326 $this->assertTrue(
$status->isOK(),
'OK' );
327 $this->assertFalse(
$status->value[
'new'],
'new' );
328 $this->assertNull(
$status->value[
'revision'],
'revision' );
329 $this->assertNotNull( $page->getRevision() );
330 $this->assertTrue( $page->getRevision()->getContent()->equals(
$content ),
'equals' );
332 # ------------------------
333 $content = ContentHandler::makeContent(
334 "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
335 .
"Stet clita kasd [[gubergren]], no sea takimata sanctus est. ~~~~",
341 $this->assertTrue(
$status->isOK(),
'OK' );
342 $this->assertFalse(
$status->value[
'new'],
'new' );
343 $this->assertNotNull(
$status->value[
'revision'],
'revision' );
344 $this->assertSame(
$status->value[
'revision']->getId(), $page->getRevision()->getId() );
345 $this->assertSame(
$status->value[
'revision']->getSha1(), $page->getRevision()->getSha1() );
348 'not equals (PST must substitute signature)'
351 $rev = $page->getRevision();
352 $this->assertNotNull(
$rev->getRecentChange() );
353 $this->assertSame(
$rev->getId(), (
int)
$rev->getRecentChange()->getAttribute(
'rc_this_oldid' ) );
355 # ------------------------
358 $retrieved = $page->getContent();
359 $newText = $retrieved->serialize();
360 $this->assertContains(
'[[gubergren]]', $newText,
'New text must replace old text.' );
361 $this->assertNotContains(
'~~~~', $newText,
'PST must substitute signature.' );
363 # ------------------------
365 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
366 $n =
$res->numRows();
369 $this->assertEquals( 2, $n,
'pagelinks should contain two links from the page' );
376 $title = Title::newFromText( __METHOD__ );
377 $page = WikiPage::factory( $title );
378 $content = ContentHandler::makeContent(
'$1 van $2', $title );
382 $status1 = $page->doEditContent(
$content, __METHOD__ );
383 $status2 = $page->doEditContent(
$content, __METHOD__ );
385 $this->assertTrue( $status1->isOK(),
'OK' );
386 $this->assertTrue( $status2->isOK(),
'OK' );
388 $this->assertTrue( isset( $status1->value[
'revision'] ),
'OK' );
389 $this->assertFalse( isset( $status2->value[
'revision'] ),
'OK' );
402 "[[original text]] foo",
405 $id = $page->getId();
407 $page->doDeleteArticle(
"testing deletion" );
410 $page->getTitle()->getArticleID() > 0,
411 "Title object should now have page id 0"
413 $this->assertFalse( $page->getId() > 0,
"WikiPage should now have page id 0" );
416 "WikiPage::exists should return false after page was deleted"
420 "WikiPage::getContent should return null after page was deleted"
423 $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
426 "Title::exists should return false after page was deleted"
430 JobQueueGroup::destroySingletons();
435 # ------------------------
437 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
438 $n =
$res->numRows();
441 $this->assertEquals( 0, $n,
'pagelinks should contain no more links from the page' );
450 "[[original text]] foo",
453 $id = $page->getId();
456 $status = $page->doDeleteArticleReal(
457 "testing user 0 deletion",
465 $actorQuery = ActorMigration::newMigration()->getJoin(
'log_user' );
467 [
'logging' ] + $actorQuery[
'tables'],
472 'log_user' => $actorQuery[
'fields'][
'log_user'],
473 'log_user_text' => $actorQuery[
'fields'][
'log_user_text'],
477 [
'log_id' => $logId ],
481 'testing user 0 deletion',
484 (
string)$page->getTitle()->getNamespace(),
485 $page->getTitle()->getDBkey(),
498 "[[original text]] foo",
501 $id = $page->getId();
505 $status = $page->doDeleteArticleReal(
506 "testing sysop deletion",
514 $actorQuery = ActorMigration::newMigration()->getJoin(
'log_user' );
516 [
'logging' ] + $actorQuery[
'tables'],
521 'log_user' => $actorQuery[
'fields'][
'log_user'],
522 'log_user_text' => $actorQuery[
'fields'][
'log_user_text'],
526 [
'log_id' => $logId ],
530 'testing sysop deletion',
531 (
string)$user->getId(),
533 (
string)$page->getTitle()->getNamespace(),
534 $page->getTitle()->getDBkey(),
549 "[[original text]] foo",
552 $id = $page->getId();
556 $status = $page->doDeleteArticleReal(
565 $actorQuery = ActorMigration::newMigration()->getJoin(
'log_user' );
567 [
'logging' ] + $actorQuery[
'tables'],
572 'log_user' => $actorQuery[
'fields'][
'log_user'],
573 'log_user_text' => $actorQuery[
'fields'][
'log_user_text'],
577 [
'log_id' => $logId ],
582 (
string)$user->getId(),
584 (
string)$page->getTitle()->getNamespace(),
585 $page->getTitle()->getDBkey(),
593 "WikiPage::getContent should return null after the page was suppressed for general users"
598 "WikiPage::getContent should return null after the page was suppressed for user zero"
603 "WikiPage::getContent should return null after the page was suppressed even for a sysop"
614 "[[original text]] foo",
617 $id = $page->getId();
618 $page->loadPageData();
622 $page->doDeleteUpdates( $page->getId(), $page->getContent(), $page->getRevision(), $user );
625 JobQueueGroup::destroySingletons();
630 # ------------------------
632 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
633 $n =
$res->numRows();
636 $this->assertEquals( 0, $n,
'pagelinks should contain no more links from the page' );
646 $handler = $this->getMockBuilder( TextContentHandler::class )
647 ->setConstructorArgs( [ $name ] )
649 [
'getSecondaryDataUpdates',
'getDeletionUpdates',
'unserializeContent' ]
654 $dataUpdate->_name =
"$name data update";
657 $deletionUpdate->_name =
"$name deletion update";
659 $handler->method(
'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
660 $handler->method(
'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
661 $handler->method(
'unserializeContent' )->willReturnCallback(
662 function ( $text ) use (
$handler ) {
668 'wgContentHandlers', [
669 $name =>
function () use (
$handler ){
686 $content = $this->getMockBuilder( TextContent::class )
687 ->setConstructorArgs( [ $text ] )
688 ->setMethods( [
'getModel',
'getContentHandler' ] )
702 $page =
new WikiPage( Title::newFromText( __METHOD__ ) );
705 [
'main' => $mainContent1 ]
708 $dataUpdates = $page->getDeletionUpdates( $page->getRevisionRecord() );
709 $this->assertNotEmpty( $dataUpdates );
711 $updateNames = array_map(
function ( $du ) {
712 return isset( $du->_name ) ? $du->_name : get_class( $du );
715 $this->assertContains( LinksDeletionUpdate::class, $updateNames );
716 $this->assertContains(
'M1 deletion update', $updateNames );
723 $page = $this->
newPage( __METHOD__ );
725 $rev = $page->getRevision();
726 $this->assertNull(
$rev );
731 $rev = $page->getRevision();
733 $this->assertEquals( $page->getLatest(),
$rev->getId() );
734 $this->assertEquals(
"some text",
$rev->getContent()->getNativeData() );
741 $page = $this->
newPage( __METHOD__ );
750 $this->assertEquals(
"some text",
$content->getNativeData() );
757 $page = $this->
newPage( __METHOD__ );
758 $this->assertFalse( $page->exists() );
762 $this->assertTrue( $page->exists() );
764 $page =
new WikiPage( $page->getTitle() );
765 $this->assertTrue( $page->exists() );
768 $page->doDeleteArticle(
"done testing" );
769 $this->assertFalse( $page->exists() );
771 $page =
new WikiPage( $page->getTitle() );
772 $this->assertFalse( $page->exists() );
777 [
'WikiPageTest_testHasViewableContent',
false,
true ],
778 [
'Special:WikiPageTest_testHasViewableContent',
false ],
779 [
'MediaWiki:WikiPageTest_testHasViewableContent',
false ],
780 [
'Special:Userlogin',
true ],
781 [
'MediaWiki:help',
true ],
790 $page = $this->
newPage( $title );
791 $this->assertEquals( $viewable, $page->hasViewableContent() );
795 $this->assertTrue( $page->hasViewableContent() );
797 $page =
new WikiPage( $page->getTitle() );
798 $this->assertTrue( $page->hasViewableContent() );
806 'WikiPageTest_testGetRedirectTarget_2',
808 "#REDIRECT [[hello world]]",
814 'WikiPageTest_testGetRedirectTarget_3',
816 "#REDIRECT [[Media:hello_world]]",
828 'wgCapitalLinks' =>
true,
831 $page = $this->
createPage( $title, $text, $model );
833 # sanity check, because this test seems to fail for no reason for some people.
834 $c = $page->getContent();
835 $this->assertEquals( WikitextContent::class, get_class( $c ) );
837 # now, test the actual redirect
838 $t = $page->getRedirectTarget();
839 $this->assertEquals( $target, is_null(
$t ) ?
null :
$t->getPrefixedText() );
847 $page = $this->
createPage( $title, $text, $model );
848 $this->assertEquals( !is_null( $target ), $page->isRedirect() );
855 [
'WikiPageTest_testIsCountable',
861 [
'WikiPageTest_testIsCountable',
869 [
'WikiPageTest_testIsCountable',
875 [
'WikiPageTest_testIsCountable',
883 [
'WikiPageTest_testIsCountable',
889 [
'WikiPageTest_testIsCountable',
897 [
'Talk:WikiPageTest_testIsCountable',
903 [
'Talk:WikiPageTest_testIsCountable',
911 [
'MediaWiki:WikiPageTest_testIsCountable.js',
917 [
'MediaWiki:WikiPageTest_testIsCountable.js',
935 $title = Title::newFromText( $title );
939 && ContentHandler::getDefaultModelFor( $title ) != $model
941 $this->markTestSkipped(
"Can not use non-default content model $model for "
942 . $title->getPrefixedDBkey() .
" with \$wgContentHandlerUseDB disabled." );
945 $page = $this->
createPage( $title, $text, $model );
947 $editInfo = $page->prepareContentForEdit( $page->getContent() );
949 $v = $page->isCountable();
950 $w = $page->isCountable( $editInfo );
955 "isCountable( null ) returned unexpected value " . var_export( $v,
true )
956 .
" instead of " . var_export( $expected,
true )
957 .
" in mode `$mode` for text \"$text\""
963 "isCountable( \$editInfo ) returned unexpected value " . var_export( $v,
true )
964 .
" instead of " . var_export( $expected,
true )
965 .
" in mode `$mode` for text \"$text\""
974 "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
985 $page = $this->
createPage( __METHOD__, $text, $model );
987 $opt = $page->makeParserOptions(
'canonical' );
988 $po = $page->getParserOutput(
$opt );
989 $text = $po->getText();
991 $text = trim( preg_replace(
'/<!--.*?-->/sm',
'', $text ) ); # strip injected comments
992 $text = preg_replace(
'!\s*(</p>|</div>)!sm',
'\1', $text ); # don
't let tidy confuse us
994 $this->assertEquals( $expectedHtml, $text );
1002 public function testGetParserOutput_nonexisting() {
1003 $page = new WikiPage( Title::newFromText( __METHOD__ ) );
1005 $opt = new ParserOptions();
1006 $po = $page->getParserOutput( $opt );
1008 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
1014 public function testGetParserOutput_badrev() {
1015 $page = $this->createPage( __METHOD__, 'dummy
', CONTENT_MODEL_WIKITEXT );
1017 $opt = new ParserOptions();
1018 $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 );
1020 // @todo would be neat to also test deleted revision
1022 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." );
1025 public static $sections =
1039 public function dataReplaceSection() {
1040 // NOTE: assume the Help namespace to contain wikitext
1042 [ 'Help:WikiPageTest_testReplaceSection
',
1043 CONTENT_MODEL_WIKITEXT,
1048 trim( preg_replace( '/^Intro/sm
', 'No more
', self::$sections ) )
1050 [ 'Help:WikiPageTest_testReplaceSection
',
1051 CONTENT_MODEL_WIKITEXT,
1058 [ 'Help:WikiPageTest_testReplaceSection
',
1059 CONTENT_MODEL_WIKITEXT,
1062 "== TEST ==\nmore fun",
1064 trim( preg_replace( '/^== test ==.*== foo ==/sm
',
1065 "== TEST ==\nmore fun\n\n== foo ==",
1068 [ 'Help:WikiPageTest_testReplaceSection
',
1069 CONTENT_MODEL_WIKITEXT,
1074 trim( self::$sections )
1076 [ 'Help:WikiPageTest_testReplaceSection
',
1077 CONTENT_MODEL_WIKITEXT,
1082 trim( self::$sections ) . "\n\n== New ==\n\nNo more"
1091 public function testReplaceSectionContent( $title, $model, $text, $section,
1092 $with, $sectionTitle, $expected
1094 $page = $this->createPage( $title, $text, $model );
1096 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
1097 $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
1099 $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
1106 public function testReplaceSectionAtRev( $title, $model, $text, $section,
1107 $with, $sectionTitle, $expected
1109 $page = $this->createPage( $title, $text, $model );
1110 $baseRevId = $page->getLatest();
1112 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
1113 $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId );
1115 $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
1121 public function testGetOldestRevision() {
1122 $page = $this->newPage( __METHOD__ );
1123 $page->doEditContent(
1124 new WikitextContent( 'one
' ),
1128 $rev1 = $page->getRevision();
1130 $page = new WikiPage( $page->getTitle() );
1131 $page->doEditContent(
1132 new WikitextContent( 'two
' ),
1137 $page = new WikiPage( $page->getTitle() );
1138 $page->doEditContent(
1139 new WikitextContent( 'three
' ),
1145 $this->assertNotEquals(
1147 $page->getRevision()->getId(),
1148 '$page->getRevision()->getId()
'
1152 $this->assertEquals(
1154 $page->getOldestRevision()->getId(),
1155 '$page->getOldestRevision()->getId()
'
1163 public function testDoRollback() {
1164 // FIXME: fails under postgres
1165 $this->markTestSkippedIfDbType( 'postgres
' );
1167 $admin = $this->getTestSysop()->getUser();
1168 $user1 = $this->getTestUser()->getUser();
1169 // Use the confirmed group for user2 to make sure the user is different
1170 $user2 = $this->getTestUser( [ 'confirmed
' ] )->getUser();
1172 // make sure we can test autopatrolling
1173 $this->setMwGlobals( 'wgUseRCPatrol
', true );
1175 // TODO: MCR: test rollback of multiple slots!
1176 $page = $this->newPage( __METHOD__ );
1180 $status1 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
1181 "section one", EDIT_NEW, false, $admin );
1184 $status2 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
1185 "adding section two", 0, false, $user1 );
1187 $text .= "\n\nthree";
1188 $status3 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
1189 "adding section three", 0, false, $user2 );
1194 $rev1 = $status1->getValue()['revision
'];
1195 $rev2 = $status2->getValue()['revision
'];
1196 $rev3 = $status3->getValue()['revision
'];
1203 $this->assertEquals( 3, Revision::countByPageId( wfGetDB( DB_REPLICA ), $page->getId() ) );
1204 $this->assertEquals( $admin->getName(), $rev1->getUserText() );
1205 $this->assertEquals( $user1->getName(), $rev2->getUserText() );
1206 $this->assertEquals( $user2->getName(), $rev3->getUserText() );
1208 // Now, try the actual rollback
1209 $token = $admin->getEditToken( '
rollback' );
1210 $rollbackErrors = $page->doRollback(
1219 if ( $rollbackErrors ) {
1221 "Rollback failed:\n" .
1222 print_r( $rollbackErrors, true ) . ";\n" .
1223 print_r( $resultDetails, true )
1227 $page = new WikiPage( $page->getTitle() );
1228 $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
1229 "rollback did not revert to the correct revision" );
1230 $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
1232 $rc = MediaWikiServices::getInstance()->getRevisionStore()->getRecentChange(
1233 $page->getRevision()->getRevisionRecord()
1236 $this->assertNotNull( $rc, 'RecentChanges entry
' );
1237 $this->assertEquals(
1238 RecentChange::PRC_AUTOPATROLLED,
1239 $rc->getAttribute( 'rc_patrolled
' ),
1243 // TODO: MCR: assert origin once we write slot data
1244 // $mainSlot = $page->getRevision()->getRevisionRecord()->getSlot( SlotRecord::MAIN );
1245 // $this->assertTrue( $mainSlot->isInherited(), 'isInherited
' );
1246 // $this->assertSame( $rev2->getId(), $mainSlot->getOrigin(), 'getOrigin
' );
1253 public function testDoRollbackFailureSameContent() {
1254 $admin = $this->getTestSysop()->getUser();
1257 $page = $this->newPage( __METHOD__ );
1258 $page->doEditContent(
1259 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1265 $rev1 = $page->getRevision();
1267 $user1 = $this->getTestUser( [ '
sysop' ] )->getUser();
1269 $page = new WikiPage( $page->getTitle() );
1270 $page->doEditContent(
1271 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1272 "adding section two",
1278 # now, do a the rollback from the same user was doing the edit before
1279 $resultDetails = [];
1280 $token = $user1->getEditToken( 'rollback' );
1281 $errors = $page->doRollback(
1283 "testing revert same user",
1290 $this->assertEquals( [], $errors, "Rollback failed same user" );
1292 # now, try the rollback
1293 $resultDetails = [];
1294 $token = $admin->getEditToken( 'rollback' );
1295 $errors = $page->doRollback(
1304 $this->assertEquals(
1314 "Rollback not failed"
1317 $page = new WikiPage( $page->getTitle() );
1318 $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
1319 "rollback did not revert to the correct revision" );
1320 $this->assertEquals( "one", $page->getContent()->getNativeData() );
1327 public function testDoRollbackTagging() {
1328 if ( !in_array( 'mw-
rollback', ChangeTags::getSoftwareTags() ) ) {
1329 $this->markTestSkipped( 'Rollback tag deactivated, skipped the test.
' );
1332 $admin = new User();
1333 $admin->setName( 'Administrator
' );
1334 $admin->addToDatabase();
1336 $text = 'First line
';
1337 $page = $this->newPage( 'WikiPageTest_testDoRollbackTagging
' );
1338 $page->doEditContent(
1339 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1346 $secondUser = new User();
1347 $secondUser->setName( '92.65.217.32
' );
1348 $text .= '\n\nSecond line
';
1349 $page = new WikiPage( $page->getTitle() );
1350 $page->doEditContent(
1351 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1352 'Adding second line
',
1358 // Now, try the rollback
1359 $admin->addGroup( 'sysop' ); // Make the test user a sysop
1360 $token = $admin->getEditToken( 'rollback' );
1361 $errors = $page->doRollback(
1362 $secondUser->getName(),
1370 // If doRollback completed without errors
1371 if ( $errors === [] ) {
1372 $tags = $resultDetails[ 'tags
' ];
1373 $this->assertContains( 'mw-
rollback', $tags );
1377 public function provideGetAutoDeleteReason() {
1387 [ "first edit", null ],
1389 "/first edit.*only contributor/",
1395 [ "first edit", null ],
1396 [ "second edit", null ],
1398 "/second edit.*only contributor/",
1404 [ "first edit", "127.0.2.22" ],
1405 [ "second edit", "127.0.3.33" ],
1415 . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
1416 . " nonumy eirmod tempor invidunt ut labore et dolore magna "
1417 . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
1418 . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
1419 . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'",
1423 '/first edit:.*\.\.\."/
',
1429 [ "first edit", "127.0.2.22" ],
1430 [ "", "127.0.3.33" ],
1432 "/before blanking.*first edit/",
1443 public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
1446 // NOTE: assume Help namespace to contain wikitext
1447 $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
1451 foreach ( $edits as $edit ) {
1454 if ( !empty( $edit[1] ) ) {
1455 $user->setName( $edit[1] );
1460 $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
1462 $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
1467 $reason = $page->getAutoDeleteReason( $hasHistory );
1469 if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) {
1470 $this->assertEquals( $expectedResult, $reason );
1472 $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
1473 "Autosummary didn't match expected pattern $expectedResult: $reason
" );
1476 $this->assertEquals( $expectedHistory, $hasHistory,
1477 "expected \$hasHistory to be
" . var_export( $expectedHistory, true ) );
1479 $page->doDeleteArticle( "done
" );
1482 public function providePreSaveTransform() {
1484 [ 'hello this is ~~~',
1485 "hello
this is [[
Special:Contributions/127.0.0.1|127.0.0.1]]
",
1487 [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1488 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1496 public function testWikiPageFactory() {
1497 $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
1498 $page = WikiPage::factory( $title );
1499 $this->assertEquals( WikiFilePage::class, get_class( $page ) );
1501 $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
1502 $page = WikiPage::factory( $title );
1503 $this->assertEquals( WikiCategoryPage::class, get_class( $page ) );
1505 $title = Title::makeTitle( NS_MAIN, 'SomePage' );
1506 $page = WikiPage::factory( $title );
1507 $this->assertEquals( WikiPage::class, get_class( $page ) );
1514 public function testLoadPageData() {
1515 $title = Title::makeTitle( NS_MAIN, 'SomePage' );
1516 $page = WikiPage::factory( $title );
1518 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1519 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1520 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1521 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1523 $page->loadPageData( IDBAccessObject::READ_NORMAL );
1524 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1525 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1526 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1527 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1529 $page->loadPageData( IDBAccessObject::READ_LATEST );
1530 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1531 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1532 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1533 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1535 $page->loadPageData( IDBAccessObject::READ_LOCKING );
1536 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1537 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1538 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1539 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1541 $page->loadPageData( IDBAccessObject::READ_EXCLUSIVE );
1542 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1543 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1544 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1545 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1554 public function testCommentMigrationOnDeletion( $writeStage, $readStage ) {
1555 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $writeStage );
1556 $this->overrideMwServices();
1558 $dbr = wfGetDB( DB_REPLICA );
1560 $page = $this->createPage(
1563 CONTENT_MODEL_WIKITEXT
1565 $revid = $page->getLatest();
1566 if ( $writeStage > MIGRATION_OLD ) {
1567 $comment_id = $dbr->selectField(
1568 'revision_comment_temp',
1569 'revcomment_comment_id',
1570 [ 'revcomment_rev' => $revid ],
1575 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $readStage );
1576 $this->overrideMwServices();
1578 $page->doDeleteArticle( "testing deletion
" );
1580 if ( $readStage > MIGRATION_OLD ) {
1581 // Didn't leave behind any 'revision_comment_temp' rows
1582 $n = $dbr->selectField(
1583 'revision_comment_temp', 'COUNT(*)', [ 'revcomment_rev' => $revid ], __METHOD__
1585 $this->assertEquals( 0, $n, 'no entry in revision_comment_temp after deletion' );
1587 // Copied or upgraded the comment_id, as applicable
1588 $ar_comment_id = $dbr->selectField(
1591 [ 'ar_rev_id' => $revid ],
1594 if ( $writeStage > MIGRATION_OLD ) {
1595 $this->assertSame( $comment_id, $ar_comment_id );
1597 $this->assertNotEquals( 0, $ar_comment_id );
1601 // Copied rev_comment, if applicable
1602 if ( $readStage <= MIGRATION_WRITE_BOTH && $writeStage <= MIGRATION_WRITE_BOTH ) {
1603 $ar_comment = $dbr->selectField(
1606 [ 'ar_rev_id' => $revid ],
1609 $this->assertSame( 'testing', $ar_comment );
1613 public function provideCommentMigrationOnDeletion() {
1615 [ MIGRATION_OLD, MIGRATION_OLD ],
1616 [ MIGRATION_OLD, MIGRATION_WRITE_BOTH ],
1617 [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
1618 [ MIGRATION_WRITE_BOTH, MIGRATION_OLD ],
1619 [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_BOTH ],
1620 [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
1621 [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
1622 [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_BOTH ],
1623 [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_NEW ],
1624 [ MIGRATION_WRITE_NEW, MIGRATION_NEW ],
1625 [ MIGRATION_NEW, MIGRATION_WRITE_BOTH ],
1626 [ MIGRATION_NEW, MIGRATION_WRITE_NEW ],
1627 [ MIGRATION_NEW, MIGRATION_NEW ],
1634 public function testUpdateCategoryCounts() {
1635 $page = new WikiPage( Title::newFromText( __METHOD__ ) );
1637 // Add an initial category
1638 $page->updateCategoryCounts( [ 'A' ], [], 0 );
1640 $this->assertEquals( 1, Category::newFromName( 'A' )->getPageCount() );
1641 $this->assertEquals( 0, Category::newFromName( 'B' )->getPageCount() );
1642 $this->assertEquals( 0, Category::newFromName( 'C' )->getPageCount() );
1644 // Add a new category
1645 $page->updateCategoryCounts( [ 'B' ], [], 0 );
1647 $this->assertEquals( 1, Category::newFromName( 'A' )->getPageCount() );
1648 $this->assertEquals( 1, Category::newFromName( 'B' )->getPageCount() );
1649 $this->assertEquals( 0, Category::newFromName( 'C' )->getPageCount() );
1651 // Add and remove a category
1652 $page->updateCategoryCounts( [ 'C' ], [ 'A' ], 0 );
1654 $this->assertEquals( 0, Category::newFromName( 'A' )->getPageCount() );
1655 $this->assertEquals( 1, Category::newFromName( 'B' )->getPageCount() );
1656 $this->assertEquals( 1, Category::newFromName( 'C' )->getPageCount() );
1659 public function provideUpdateRedirectOn() {
1660 yield [ '#REDIRECT [[Foo]]', true, null, true, true, 0 ];
1661 yield [ '#REDIRECT [[Foo]]', true, 'Foo', true, false, 1 ];
1662 yield [ 'SomeText', false, null, false, true, 0 ];
1663 yield [ 'SomeText', false, 'Foo', false, false, 1 ];
1677 public function testUpdateRedirectOn(
1679 $initialRedirectState,
1685 // FIXME: fails under sqlite and postgres
1686 $this->markTestSkippedIfDbType( 'sqlite' );
1687 $this->markTestSkippedIfDbType( 'postgres' );
1688 static $pageCounter = 0;
1691 $page = $this->createPage( Title::newFromText( __METHOD__ . $pageCounter ), $initialText );
1692 $this->assertSame( $initialRedirectState, $page->isRedirect() );
1694 $redirectTitle = is_string( $redirectTitle )
1695 ? Title::newFromText( $redirectTitle )
1698 $success = $page->updateRedirectOn( $this->db, $redirectTitle, $lastRevIsRedirect );
1699 $this->assertSame( $expectedSuccess, $success, 'Success assertion' );
1705 $this->assertRedirectTableCountForPageId( $page->getId(), $expectedRowCount );
1708 private function assertRedirectTableCountForPageId( $pageId, $expected ) {
1709 $this->assertSelect(
1712 [ 'rd_from' => $pageId ],
1713 [ [ strval( $expected ) ] ]
1720 public function testInsertRedirectEntry_insertsRedirectEntry() {
1721 $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
1722 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1724 $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
1725 $targetTitle->mInterwiki = 'eninter';
1726 $page->insertRedirectEntry( $targetTitle, null );
1728 $this->assertSelect(
1730 [ 'rd_from', 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
1731 [ 'rd_from' => $page->getId() ],
1733 strval( $page->getId() ),
1734 strval( $targetTitle->getNamespace() ),
1735 strval( $targetTitle->getDBkey() ),
1736 strval( $targetTitle->getFragment() ),
1737 strval( $targetTitle->getInterwiki() ),
1745 public function testInsertRedirectEntry_insertsRedirectEntryWithPageLatest() {
1746 $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
1747 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1749 $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
1750 $targetTitle->mInterwiki = 'eninter';
1751 $page->insertRedirectEntry( $targetTitle, $page->getLatest() );
1753 $this->assertSelect(
1755 [ 'rd_from', 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
1756 [ 'rd_from' => $page->getId() ],
1758 strval( $page->getId() ),
1759 strval( $targetTitle->getNamespace() ),
1760 strval( $targetTitle->getDBkey() ),
1761 strval( $targetTitle->getFragment() ),
1762 strval( $targetTitle->getInterwiki() ),
1770 public function testInsertRedirectEntry_doesNotInsertIfPageLatestIncorrect() {
1771 $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
1772 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1774 $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
1775 $targetTitle->mInterwiki = 'eninter';
1776 $page->insertRedirectEntry( $targetTitle, 215251 );
1778 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1781 private function getRow( array $overrides = [] ) {
1785 'page_is_redirect' => '1',
1786 'page_latest' => '99',
1787 'page_namespace' => '3',
1788 'page_title' => 'JaJaTitle',
1789 'page_restrictions' => 'edit=autoconfirmed,sysop:move=sysop',
1790 'page_touched' => '20120101020202',
1791 'page_links_updated' => '20140101020202',
1793 foreach ( $overrides as $key => $value ) {
1794 $row[$key] = $value;
1796 return (object)$row;
1799 public function provideNewFromRowSuccess() {
1800 yield 'basic row' => [
1802 function ( WikiPage $wikiPage, self $test ) {
1803 $test->assertSame( 44, $wikiPage->getId() );
1804 $test->assertSame( 76, $wikiPage->getTitle()->getLength() );
1805 $test->assertTrue( $wikiPage->isRedirect() );
1806 $test->assertSame( 99, $wikiPage->getLatest() );
1807 $test->assertSame( 3, $wikiPage->getTitle()->getNamespace() );
1808 $test->assertSame( 'JaJaTitle', $wikiPage->getTitle()->getDBkey() );
1811 'edit' => [ 'autoconfirmed', 'sysop' ],
1812 'move' => [ 'sysop' ],
1814 $wikiPage->getTitle()->getAllRestrictions()
1816 $test->assertSame( '20120101020202', $wikiPage->getTouched() );
1817 $test->assertSame( '20140101020202', $wikiPage->getLinksTimestamp() );
1820 yield 'different timestamp formats' => [
1822 'page_touched' => '2012-01-01 02:02:02',
1823 'page_links_updated' => '2014-01-01 02:02:02',
1825 function ( WikiPage $wikiPage, self $test ) {
1826 $test->assertSame( '20120101020202', $wikiPage->getTouched() );
1827 $test->assertSame( '20140101020202', $wikiPage->getLinksTimestamp() );
1830 yield 'no restrictions' => [
1832 'page_restrictions' => '',
1834 function ( WikiPage $wikiPage, self $test ) {
1840 $wikiPage->getTitle()->getAllRestrictions()
1844 yield 'not redirect' => [
1846 'page_is_redirect' => '0',
1848 function ( WikiPage $wikiPage, self $test ) {
1849 $test->assertFalse( $wikiPage->isRedirect() );
1862 public function testNewFromRow( $row, $assertions ) {
1863 $page = WikiPage::newFromRow( $row, 'fromdb' );
1864 $assertions( $page, $this );
1867 public function provideTestNewFromId_returnsNullOnBadPageId() {
1876 public function testNewFromId_returnsNullOnBadPageId( $pageId ) {
1877 $this->assertNull( WikiPage::newFromID( $pageId ) );
1883 public function testNewFromId_appearsToFetchCorrectRow() {
1884 $createdPage = $this->createPage( __METHOD__, 'Xsfaij09' );
1885 $fetchedPage = WikiPage::newFromID( $createdPage->getId() );
1886 $this->assertSame( $createdPage->getId(), $fetchedPage->getId() );
1887 $this->assertEquals(
1888 $createdPage->getContent()->getNativeData(),
1889 $fetchedPage->getContent()->getNativeData()
1896 public function testNewFromId_returnsNullOnNonExistingId() {
1897 $this->assertNull( WikiPage::newFromID( 2147483647 ) );
1900 public function provideTestInsertProtectNullRevision() {
1901 // phpcs:disable Generic.Files.LineLength
1904 [ 'edit' => 'sysop' ],
1905 [ 'edit' => '20200101040404' ],
1909 '(goat-message-key: WikiPageDbTestBase::testInsertProtectNullRevision, UTSysop)(colon-separator)Goat Reason(word-separator)(parentheses: (protect-summary-desc: (restriction-edit), (protect-level-sysop), (protect-expiring: 04:04, 1 (january) 2020, 1 (january) 2020, 04:04)))'
1913 [ 'edit' => 'sysop', 'move' => 'something' ],
1914 [ 'edit' => '20200101040404', 'move' => '20210101050505' ],
1918 '(goat-key: WikiPageDbTestBase::testInsertProtectNullRevision, UTSysop)(colon-separator)Goat Goat(word-separator)(parentheses: (protect-summary-desc: (restriction-edit), (protect-level-sysop), (protect-expiring: 04:04, 1 (january) 2020, 1 (january) 2020, 04:04))(word-separator)(protect-summary-desc: (restriction-move), (protect-level-something), (protect-expiring: 05:05, 1 (january) 2021, 1 (january) 2021, 05:05)))'
1936 public function testInsertProtectNullRevision(
1945 $this->setContentLang( 'qqx' );
1947 $page = $this->createPage( __METHOD__, 'Goat' );
1949 $user = $user === null ? $user : $this->getTestSysop()->getUser();
1951 $result = $page->insertProtectNullRevision(
1960 $this->assertTrue( $result instanceof Revision );
1961 $this->assertSame( $expectedComment, $result->getComment( Revision::RAW ) );
1967 public function testUpdateRevisionOn_existingPage() {
1968 $user = $this->getTestSysop()->getUser();
1969 $page = $this->createPage( __METHOD__, 'StartText' );
1971 $revision = new Revision(
1974 'page' => $page->getId(),
1975 'title' => $page->getTitle(),
1976 'comment' => __METHOD__,
1977 'minor_edit' => true,
1978 'text' => __METHOD__ . '-text',
1979 'len' => strlen( __METHOD__ . '-text' ),
1980 'user' => $user->getId(),
1981 'user_text' => $user->getName(),
1982 'timestamp' => '20170707040404',
1983 'content_model' => CONTENT_MODEL_WIKITEXT,
1984 'content_format' => CONTENT_FORMAT_WIKITEXT,
1988 $result = $page->updateRevisionOn( $this->db, $revision );
1989 $this->assertTrue( $result );
1990 $this->assertSame( 9989, $page->getLatest() );
1991 $this->assertEquals( $revision, $page->getRevision() );
1997 public function testUpdateRevisionOn_NonExistingPage() {
1998 $user = $this->getTestSysop()->getUser();
1999 $page = $this->createPage( __METHOD__, 'StartText' );
2000 $page->doDeleteArticle( 'reason' );
2002 $revision = new Revision(
2005 'page' => $page->getId(),
2006 'title' => $page->getTitle(),
2007 'comment' => __METHOD__,
2008 'minor_edit' => true,
2009 'text' => __METHOD__ . '-text',
2010 'len' => strlen( __METHOD__ . '-text' ),
2011 'user' => $user->getId(),
2012 'user_text' => $user->getName(),
2013 'timestamp' => '20170707040404',
2014 'content_model' => CONTENT_MODEL_WIKITEXT,
2015 'content_format' => CONTENT_FORMAT_WIKITEXT,
2019 $result = $page->updateRevisionOn( $this->db, $revision );
2020 $this->assertFalse( $result );
2026 public function testUpdateIfNewerOn_olderRevision() {
2027 $user = $this->getTestSysop()->getUser();
2028 $page = $this->createPage( __METHOD__, 'StartText' );
2029 $initialRevision = $page->getRevision();
2031 $olderTimeStamp = wfTimestamp(
2033 wfTimestamp( TS_UNIX, $initialRevision->getTimestamp() ) - 1
2036 $olderRevison = new Revision(
2039 'page' => $page->getId(),
2040 'title' => $page->getTitle(),
2041 'comment' => __METHOD__,
2042 'minor_edit' => true,
2043 'text' => __METHOD__ . '-text',
2044 'len' => strlen( __METHOD__ . '-text' ),
2045 'user' => $user->getId(),
2046 'user_text' => $user->getName(),
2047 'timestamp' => $olderTimeStamp,
2048 'content_model' => CONTENT_MODEL_WIKITEXT,
2049 'content_format' => CONTENT_FORMAT_WIKITEXT,
2053 $result = $page->updateIfNewerOn( $this->db, $olderRevison );
2054 $this->assertFalse( $result );
2060 public function testUpdateIfNewerOn_newerRevision() {
2061 $user = $this->getTestSysop()->getUser();
2062 $page = $this->createPage( __METHOD__, 'StartText' );
2063 $initialRevision = $page->getRevision();
2065 $newerTimeStamp = wfTimestamp(
2067 wfTimestamp( TS_UNIX, $initialRevision->getTimestamp() ) + 1
2070 $newerRevision = new Revision(
2073 'page' => $page->getId(),
2074 'title' => $page->getTitle(),
2075 'comment' => __METHOD__,
2076 'minor_edit' => true,
2077 'text' => __METHOD__ . '-text',
2078 'len' => strlen( __METHOD__ . '-text' ),
2079 'user' => $user->getId(),
2080 'user_text' => $user->getName(),
2081 'timestamp' => $newerTimeStamp,
2082 'content_model' => CONTENT_MODEL_WIKITEXT,
2083 'content_format' => CONTENT_FORMAT_WIKITEXT,
2086 $result = $page->updateIfNewerOn( $this->db, $newerRevision );
2087 $this->assertTrue( $result );
2093 public function testInsertOn() {
2094 $title = Title::newFromText( __METHOD__ );
2095 $page = new WikiPage( $title );
2097 $startTimeStamp = wfTimestampNow();
2098 $result = $page->insertOn( $this->db );
2099 $endTimeStamp = wfTimestampNow();
2101 $this->assertInternalType( 'int', $result );
2102 $this->assertTrue( $result > 0 );
2104 $condition = [ 'page_id' => $result ];
2106 // Check the default fields have been filled
2107 $this->assertSelect(
2112 'page_restrictions',
2130 // Check the page_random field has been filled
2131 $pageRandom = $this->db->selectField( 'page', 'page_random', $condition );
2132 $this->assertTrue( (float)$pageRandom < 1 && (float)$pageRandom > 0 );
2134 // Assert the touched timestamp in the DB is roughly when we inserted the page
2135 $pageTouched = $this->db->selectField( 'page', 'page_touched', $condition );
2137 wfTimestamp( TS_UNIX, $startTimeStamp )
2138 <= wfTimestamp( TS_UNIX, $pageTouched )
2141 wfTimestamp( TS_UNIX, $endTimeStamp )
2142 >= wfTimestamp( TS_UNIX, $pageTouched )
2145 // Try inserting the same page again and checking the result is false (no change)
2146 $result = $page->insertOn( $this->db );
2147 $this->assertFalse( $result );
2153 public function testInsertOn_idSpecified() {
2154 $title = Title::newFromText( __METHOD__ );
2155 $page = new WikiPage( $title );
2158 $result = $page->insertOn( $this->db, $id );
2160 $this->assertSame( $id, $result );
2162 $condition = [ 'page_id' => $result ];
2164 // Check there is actually a row in the db
2165 $this->assertSelect(
2173 public function provideTestDoUpdateRestrictions_setBasicRestrictions() {
2174 // Note: Once the current dates passes the date in these tests they will fail.
2175 yield 'move something' => [
2177 [ 'move' => 'something' ],
2179 [ 'edit' => [], 'move' => [ 'something' ] ],
2182 yield 'move something, edit blank' => [
2184 [ 'move' => 'something', 'edit' => '' ],
2186 [ 'edit' => [], 'move' => [ 'something' ] ],
2189 yield 'edit sysop, with expiry' => [
2191 [ 'edit' => 'sysop' ],
2192 [ 'edit' => '21330101020202' ],
2193 [ 'edit' => [ 'sysop' ], 'move' => [] ],
2194 [ 'edit' => '21330101020202' ],
2196 yield 'move and edit, move with expiry' => [
2198 [ 'move' => 'something', 'edit' => 'another' ],
2199 [ 'move' => '22220202010101' ],
2200 [ 'edit' => [ 'another' ], 'move' => [ 'something' ] ],
2201 [ 'move' => '22220202010101' ],
2203 yield 'move and edit, edit with infinity expiry' => [
2205 [ 'move' => 'something', 'edit' => 'another' ],
2206 [ 'edit' => 'infinity' ],
2207 [ 'edit' => [ 'another' ], 'move' => [ 'something' ] ],
2208 [ 'edit' => 'infinity' ],
2210 yield 'non existing, create something' => [
2212 [ 'create' => 'something' ],
2214 [ 'create' => [ 'something' ] ],
2217 yield 'non existing, create something with expiry' => [
2219 [ 'create' => 'something' ],
2220 [ 'create' => '23451212112233' ],
2221 [ 'create' => [ 'something' ] ],
2222 [ 'create' => '23451212112233' ],
2230 public function testDoUpdateRestrictions_setBasicRestrictions(
2234 array $expectedRestrictions,
2235 array $expectedRestrictionExpiries
2237 if ( $pageExists ) {
2238 $page = $this->createPage( __METHOD__, 'ABC' );
2240 $page = new WikiPage( Title::newFromText( __METHOD__ . '-nonexist' ) );
2242 $user = $this->getTestSysop()->getUser();
2245 $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, 'aReason', $user, [] );
2247 $logId = $status->getValue();
2248 $allRestrictions = $page->getTitle()->getAllRestrictions();
2250 $this->assertTrue( $status->isGood() );
2251 $this->assertInternalType( 'int', $logId );
2252 $this->assertSame( $expectedRestrictions, $allRestrictions );
2253 foreach ( $expectedRestrictionExpiries as $key => $value ) {
2254 $this->assertSame( $value, $page->getTitle()->getRestrictionExpiry( $key ) );
2257 // Make sure the log entry looks good
2258 // log_params is not checked here
2259 $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
2260 $this->assertSelect(
2261 [ 'logging' ] + $actorQuery['tables'],
2264 'log_user' => $actorQuery['fields']['log_user'],
2265 'log_user_text' => $actorQuery['fields']['log_user_text'],
2269 [ 'log_id' => $logId ],
2272 (string)$user->getId(),
2274 (string)$page->getTitle()->getNamespace(),
2275 $page->getTitle()->getDBkey(),
2278 $actorQuery['joins']
2285 public function testDoUpdateRestrictions_failsOnReadOnly() {
2286 $page = $this->createPage( __METHOD__, 'ABC' );
2287 $user = $this->getTestSysop()->getUser();
2291 $readOnly = $this->getMockBuilder( ReadOnlyMode::class )
2292 ->disableOriginalConstructor()
2293 ->setMethods( [ 'isReadOnly', 'getReason' ] )
2295 $readOnly->expects( $this->once() )
2296 ->method( 'isReadOnly' )
2297 ->will( $this->returnValue( true ) );
2298 $readOnly->expects( $this->once() )
2299 ->method( 'getReason' )
2300 ->will( $this->returnValue( 'Some Read Only Reason' ) );
2301 $this->setService( 'ReadOnlyMode', $readOnly );
2303 $status = $page->doUpdateRestrictions( [], [], $cascade, 'aReason', $user, [] );
2304 $this->assertFalse( $status->isOK() );
2305 $this->assertSame( 'readonlytext', $status->getMessage()->getKey() );
2311 public function testDoUpdateRestrictions_returnsGoodIfNothingChanged() {
2312 $page = $this->createPage( __METHOD__, 'ABC' );
2313 $user = $this->getTestSysop()->getUser();
2315 $limit = [ 'edit' => 'sysop' ];
2317 $status = $page->doUpdateRestrictions(
2326 // The first entry should have a logId as it did something
2327 $this->assertTrue( $status->isGood() );
2328 $this->assertInternalType( 'int', $status->getValue() );
2330 $status = $page->doUpdateRestrictions(
2339 // The second entry should not have a logId as nothing changed
2340 $this->assertTrue( $status->isGood() );
2341 $this->assertNull( $status->getValue() );
2347 public function testDoUpdateRestrictions_logEntryTypeAndAction() {
2348 $page = $this->createPage( __METHOD__, 'ABC' );
2349 $user = $this->getTestSysop()->getUser();
2353 $status = $page->doUpdateRestrictions(
2354 [ 'edit' => 'sysop' ],
2361 $this->assertTrue( $status->isGood() );
2362 $this->assertInternalType( 'int', $status->getValue() );
2363 $this->assertSelect(
2365 [ 'log_type', 'log_action' ],
2366 [ 'log_id' => $status->getValue() ],
2367 [ [ 'protect', 'protect' ] ]
2370 // Modify the protection
2371 $status = $page->doUpdateRestrictions(
2372 [ 'edit' => 'somethingElse' ],
2379 $this->assertTrue( $status->isGood() );
2380 $this->assertInternalType( 'int', $status->getValue() );
2381 $this->assertSelect(
2383 [ 'log_type', 'log_action' ],
2384 [ 'log_id' => $status->getValue() ],
2385 [ [ 'protect', 'modify' ] ]
2388 // Remove the protection
2389 $status = $page->doUpdateRestrictions(
2397 $this->assertTrue( $status->isGood() );
2398 $this->assertInternalType( 'int', $status->getValue() );
2399 $this->assertSelect(
2401 [ 'log_type', 'log_action' ],
2402 [ 'log_id' => $status->getValue() ],
2403 [ [ 'protect', 'unprotect' ] ]
2411 public function testNewPageUpdater() {
2412 $user = $this->getTestUser()->getUser();
2413 $page = $this->newPage( __METHOD__, __METHOD__ );
2416 $content = $this->getMockBuilder( WikitextContent::class )
2417 ->setConstructorArgs( [ 'Hello World' ] )
2418 ->setMethods( [ 'getParserOutput' ] )
2420 $content->expects( $this->once() )
2421 ->method( 'getParserOutput' )
2422 ->willReturn( new ParserOutput( 'HTML' ) );
2424 $preparedEditBefore = $page->prepareContentForEdit( $content, null, $user );
2426 // provide context, so the cache can be kept in place
2427 $slotsUpdate = new revisionSlotsUpdate();
2428 $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
2430 $updater = $page->newPageUpdater( $user, $slotsUpdate );
2431 $updater->setContent( SlotRecord::MAIN, $content );
2432 $revision = $updater->saveRevision(
2433 CommentStoreComment::newUnsavedComment( 'test' ),
2437 $preparedEditAfter = $page->prepareContentForEdit( $content, $revision, $user );
2439 $this->assertSame( $revision->getId(), $page->getLatest() );
2441 // Parsed output must remain cached throughout.
2442 $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
2449 public function testGetDerivedDataUpdater() {
2450 $admin = $this->getTestSysop()->getUser();
2453 $page = $this->createPage( __METHOD__, __METHOD__ );
2454 $page = TestingAccessWrapper::newFromObject( $page );
2456 $revision = $page->getRevision()->getRevisionRecord();
2457 $user = $revision->getUser();
2459 $slotsUpdate = new RevisionSlotsUpdate();
2460 $slotsUpdate->modifyContent( SlotRecord::MAIN, new WikitextContent( 'Hello World' ) );
2462 // get a virgin updater
2463 $updater1 = $page->getDerivedDataUpdater( $user );
2464 $this->assertFalse( $updater1->isUpdatePrepared() );
2466 $updater1->prepareUpdate( $revision );
2468 // Re-use updater with same revision or content, even if base changed
2469 $this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, $revision ) );
2471 $slotsUpdate = RevisionSlotsUpdate::newFromContent(
2472 [ SlotRecord::MAIN => $revision->getContent( SlotRecord::MAIN ) ]
2474 $this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, null, $slotsUpdate ) );
2476 // Don't re-use for edit if base revision ID changed
2477 $this->assertNotSame(
2479 $page->getDerivedDataUpdater( $user, null, $slotsUpdate, true )
2482 // Don't re-use with different user
2483 $updater2a = $page->getDerivedDataUpdater( $admin, null, $slotsUpdate );
2484 $updater2a->prepareContent( $admin, $slotsUpdate, false );
2486 $updater2b = $page->getDerivedDataUpdater( $user, null, $slotsUpdate );
2487 $updater2b->prepareContent( $user, $slotsUpdate, false );
2488 $this->assertNotSame( $updater2a, $updater2b );
2490 // Don't re-use with different content
2491 $updater3 = $page->getDerivedDataUpdater( $admin, null, $slotsUpdate );
2492 $updater3->prepareUpdate( $revision );
2493 $this->assertNotSame( $updater2b, $updater3 );
2495 // Don't re-use if no context given
2496 $updater4 = $page->getDerivedDataUpdater( $admin );
2497 $updater4->prepareUpdate( $revision );
2498 $this->assertNotSame( $updater3, $updater4 );
2500 // Don't re-use if AGAIN no context given
2501 $updater5 = $page->getDerivedDataUpdater( $admin );
2502 $this->assertNotSame( $updater4, $updater5 );
2504 // Don't re-use cached "virgin
" unprepared updater
2505 $updater6 = $page->getDerivedDataUpdater( $admin, $revision );
2506 $this->assertNotSame( $updater5, $updater6 );
2509 protected function assertPreparedEditEquals(
2510 PreparedEdit $edit, PreparedEdit $edit2, $message = ''
2512 // suppress differences caused by a clock tick between generating the two PreparedEdits
2513 if ( abs( $edit->timestamp - $edit2->timestamp ) < 3 ) {
2514 $edit2 = clone $edit2;
2515 $edit2->timestamp = $edit->timestamp;
2517 $this->assertEquals( $edit, $edit2, $message );
2520 protected function assertPreparedEditNotEquals(
2521 PreparedEdit $edit, PreparedEdit $edit2, $message = ''
2523 if ( abs( $edit->timestamp - $edit2->timestamp ) < 3 ) {
2524 $edit2 = clone $edit2;
2525 $edit2->timestamp = $edit->timestamp;
2527 $this->assertNotEquals( $edit, $edit2, $message );
could not be made into a sysop(Did you enter the name correctly?) <
$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.
A content handler knows how do deal with a specific type of content on a wiki page.
Deferrable Update for closure/callback.
loadParamsAndArgs( $self=null, $opts=null, $args=null)
Process command line arguments $mOptions becomes an array with keys set to the option names $mArgs be...
Maintenance script that runs pending jobs.
Represents a title within MediaWiki.
testDoEditContent_twice()
WikiPage::doEditContent.
testGetRedirectTarget( $title, $model, $text, $target)
provideGetRedirectTarget WikiPage::getRedirectTarget
testGetContent()
WikiPage::getContent.
newPage( $title, $model=null)
testPrepareContentForEdit()
WikiPage::prepareContentForEdit.
testExists()
WikiPage::exists.
assertPreparedEditNotEquals(PreparedEdit $edit, PreparedEdit $edit2, $message='')
testHasViewableContent( $title, $viewable, $create=false)
provideHasViewableContent WikiPage::hasViewableContent
testDoEditContent()
WikiPage::doEditContent WikiPage::prepareContentForEdit.
createPage( $page, $content, $model=null, $user=null)
assertPreparedEditEquals(PreparedEdit $edit, PreparedEdit $edit2, $message='')
defineMockContentModelForUpdateTesting( $name)
testGetRevision()
WikiPage::getRevision.
provideHasViewableContent()
testDoDeleteArticleReal_user0()
WikiPage::doDeleteArticleReal.
provideGetRedirectTarget()
testIsCountable( $title, $model, $text, $mode, $expected)
provideIsCountable WikiPage::isCountable
testDoDeleteArticle()
Undeletion is covered in PageArchiveTest::testUndeleteRevisions() TODO: Revision deletion.
testDoDeleteArticleReal_suppress()
TODO: Test more stuff about suppression.
testDoEditUpdates()
WikiPage::doEditUpdates.
testDoDeleteArticleReal_userSysop()
WikiPage::doDeleteArticleReal.
testIsRedirect( $title, $model, $text, $target)
provideGetRedirectTarget WikiPage::isRedirect
testDoDeleteUpdates()
WikiPage::doDeleteUpdates.
__construct( $name=null, array $data=[], $dataName='')
createMockContent(ContentHandler $handler, $text)
testGetParserOutput( $model, $text, $expectedHtml)
provideGetParserOutput WikiPage::getParserOutput
Class representing a MediaWiki article and history.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e rollback
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
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy: boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as
patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
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
processing should stop and the error should be shown to the user * false
const CONTENT_MODEL_WIKITEXT
const CONTENT_FORMAT_WIKITEXT
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$page->newPageUpdater($user) $updater