7 use PHPUnit\Framework\MockObject\MockObject;
8 use 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." );
95 if ( is_string(
$title ) ) {
102 $this->pagesToDelete[] = $p;
115 if ( is_string( $page ) || $page instanceof
Title ) {
116 $page = $this->
newPage( $page, $model );
134 $updater->setContent( $role, $cnt );
139 $this->fail(
$updater->getStatus()->getWikiText() );
150 $sysop = $this->
getTestUser( [
'sysop' ] )->getUser();
153 $title = $page->getTitle();
156 "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
157 .
" nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
162 "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
163 .
"Stet clita kasd [[gubergren]], no sea takimata sanctus est. ~~~~",
168 $edit = $page->prepareContentForEdit(
$content,
null,
$user,
null,
false );
170 $this->assertInstanceOf(
175 $this->assertContains(
'</a>', $edit->output->getText(),
"output" );
176 $this->assertContains(
177 'consetetur sadipscing elitr',
178 $edit->output->getText(),
182 $this->assertTrue(
$content->equals( $edit->newContent ),
"newContent field" );
183 $this->assertTrue(
$content->equals( $edit->pstContent ),
"pstContent field" );
184 $this->assertSame( $edit->output, $edit->output,
"output field" );
185 $this->assertSame( $edit->popts, $edit->popts,
"popts field" );
186 $this->assertSame(
null, $edit->revid,
"revid field" );
189 $sameEdit = $page->prepareContentForEdit(
$content,
null,
$user,
null,
false );
191 $this->assertSame( $edit->pstContent, $sameEdit->pstContent,
're-use output' );
192 $this->assertSame( $edit->output, $sameEdit->output,
're-use output' );
195 $rev = $page->getRevision();
196 $edit2 = $page->prepareContentForEdit( $content2,
null,
$user,
null,
false );
198 $this->assertContains(
'At vero eos', $edit2->pstContent->serialize(),
"content" );
201 $this->assertContains(
'[[gubergren]]', $edit2->pstContent->serialize() );
202 $this->assertNotContains(
'~~~~', $edit2->pstContent->serialize() );
204 $edit3 = $page->prepareContentForEdit( $content2,
null, $sysop,
null,
false );
220 [
'edits' => 1000,
'articles' => 1000,
'pages' => 1000 ]
222 $siteStatsUpdate->doUpdate();
224 $page = $this->
createPage( __METHOD__, __METHOD__ );
229 'page' => $page->getId(),
230 'title' => $page->getTitle(),
231 'comment' => __METHOD__,
232 'minor_edit' =>
true,
233 'text' => __METHOD__ .
' [[|foo]][[bar]]',
234 'user' =>
$user->getId(),
235 'user_text' =>
$user->getName(),
236 'timestamp' =>
'20170707040404',
242 $page->doEditUpdates( $revision,
$user );
247 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $page->getId() ] );
248 $n =
$res->numRows();
251 $this->assertEquals( 1, $n,
'pagelinks should contain only one link if PST was not applied' );
261 $page = $this->
newPage( __METHOD__ );
262 $title = $page->getTitle();
266 $user2 = $this->
getTestUser( [
'confirmed' ] )->getUser();
269 "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
270 .
" nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
275 $preparedEditBefore = $page->prepareContentForEdit(
$content,
null, $user1 );
279 $this->assertTrue(
$status->isOK(),
'OK' );
280 $this->assertTrue(
$status->value[
'new'],
'new' );
281 $this->assertNotNull(
$status->value[
'revision'],
'revision' );
282 $this->assertSame(
$status->value[
'revision']->getId(), $page->getRevision()->getId() );
283 $this->assertSame(
$status->value[
'revision']->getSha1(), $page->getRevision()->getSha1() );
284 $this->assertTrue(
$status->value[
'revision']->getContent()->equals(
$content ),
'equals' );
286 $rev = $page->getRevision();
287 $preparedEditAfter = $page->prepareContentForEdit(
$content,
$rev, $user1 );
289 $this->assertNotNull(
$rev->getRecentChange() );
290 $this->assertSame(
$rev->getId(), (int)
$rev->getRecentChange()->getAttribute(
'rc_this_oldid' ) );
293 $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
295 $id = $page->getId();
300 [
'log_type',
'log_action' ],
301 [
'log_page' => $id ],
302 [ [
'create',
'create' ] ]
305 $this->assertTrue(
$title->getArticleID() > 0,
"Title object should have new page id" );
306 $this->assertTrue( $id > 0,
"WikiPage should have new page id" );
307 $this->assertTrue(
$title->exists(),
"Title object should indicate that the page now exists" );
308 $this->assertTrue( $page->exists(),
"WikiPage object should indicate that the page now exists" );
310 # ------------------------
312 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
313 $n =
$res->numRows();
316 $this->assertEquals( 1, $n,
'pagelinks should contain one link from the page' );
318 # ------------------------
321 $retrieved = $page->getContent();
322 $this->assertTrue(
$content->equals( $retrieved ),
'retrieved content doesn\'t equal original' );
324 # ------------------------
329 $this->assertTrue(
$status->isOK(),
'OK' );
330 $this->assertFalse(
$status->value[
'new'],
'new' );
331 $this->assertNull(
$status->value[
'revision'],
'revision' );
332 $this->assertNotNull( $page->getRevision() );
333 $this->assertTrue( $page->getRevision()->getContent()->equals(
$content ),
'equals' );
335 # ------------------------
337 "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
338 .
"Stet clita kasd [[gubergren]], no sea takimata sanctus est. ~~~~",
344 $this->assertTrue(
$status->isOK(),
'OK' );
345 $this->assertFalse(
$status->value[
'new'],
'new' );
346 $this->assertNotNull(
$status->value[
'revision'],
'revision' );
347 $this->assertSame(
$status->value[
'revision']->getId(), $page->getRevision()->getId() );
348 $this->assertSame(
$status->value[
'revision']->getSha1(), $page->getRevision()->getSha1() );
351 'not equals (PST must substitute signature)'
354 $rev = $page->getRevision();
355 $this->assertNotNull(
$rev->getRecentChange() );
356 $this->assertSame(
$rev->getId(), (int)
$rev->getRecentChange()->getAttribute(
'rc_this_oldid' ) );
358 # ------------------------
361 $retrieved = $page->getContent();
362 $newText = $retrieved->serialize();
363 $this->assertContains(
'[[gubergren]]', $newText,
'New text must replace old text.' );
364 $this->assertNotContains(
'~~~~', $newText,
'PST must substitute signature.' );
366 # ------------------------
368 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
369 $n =
$res->numRows();
372 $this->assertEquals( 2, $n,
'pagelinks should contain two links from the page' );
385 $status1 = $page->doEditContent(
$content, __METHOD__ );
386 $status2 = $page->doEditContent(
$content, __METHOD__ );
388 $this->assertTrue( $status1->isOK(),
'OK' );
389 $this->assertTrue( $status2->isOK(),
'OK' );
391 $this->assertTrue( isset( $status1->value[
'revision'] ),
'OK' );
392 $this->assertFalse( isset( $status2->value[
'revision'] ),
'OK' );
405 "[[original text]] foo",
408 $id = $page->getId();
410 $page->doDeleteArticle(
"testing deletion" );
413 $page->getTitle()->getArticleID() > 0,
414 "Title object should now have page id 0"
416 $this->assertFalse( $page->getId() > 0,
"WikiPage should now have page id 0" );
419 "WikiPage::exists should return false after page was deleted"
423 "WikiPage::getContent should return null after page was deleted"
429 "Title::exists should return false after page was deleted"
438 # ------------------------
440 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
441 $n =
$res->numRows();
444 $this->assertEquals( 0, $n,
'pagelinks should contain no more links from the page' );
453 "[[original text]] foo",
456 $id = $page->getId();
459 $status = $page->doDeleteArticleReal(
460 "testing user 0 deletion",
469 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'log_comment' );
471 [
'logging' ] + $actorQuery[
'tables'] + $commentQuery[
'tables'],
475 'log_comment' => $commentQuery[
'fields'][
'log_comment_text'],
476 'log_user' => $actorQuery[
'fields'][
'log_user'],
477 'log_user_text' => $actorQuery[
'fields'][
'log_user_text'],
481 [
'log_id' => $logId ],
485 'testing user 0 deletion',
488 (
string)$page->getTitle()->getNamespace(),
489 $page->getTitle()->getDBkey(),
492 $actorQuery[
'joins'] + $commentQuery[
'joins']
502 "[[original text]] foo",
505 $id = $page->getId();
509 $status = $page->doDeleteArticleReal(
510 "testing sysop deletion",
519 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'log_comment' );
521 [
'logging' ] + $actorQuery[
'tables'] + $commentQuery[
'tables'],
525 'log_comment' => $commentQuery[
'fields'][
'log_comment_text'],
526 'log_user' => $actorQuery[
'fields'][
'log_user'],
527 'log_user_text' => $actorQuery[
'fields'][
'log_user_text'],
531 [
'log_id' => $logId ],
535 'testing sysop deletion',
536 (
string)
$user->getId(),
538 (
string)$page->getTitle()->getNamespace(),
539 $page->getTitle()->getDBkey(),
542 $actorQuery[
'joins'] + $commentQuery[
'joins']
554 "[[original text]] foo",
557 $id = $page->getId();
561 $status = $page->doDeleteArticleReal(
571 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin(
'log_comment' );
573 [
'logging' ] + $actorQuery[
'tables'] + $commentQuery[
'tables'],
577 'log_comment' => $commentQuery[
'fields'][
'log_comment_text'],
578 'log_user' => $actorQuery[
'fields'][
'log_user'],
579 'log_user_text' => $actorQuery[
'fields'][
'log_user_text'],
583 [
'log_id' => $logId ],
588 (
string)
$user->getId(),
590 (
string)$page->getTitle()->getNamespace(),
591 $page->getTitle()->getDBkey(),
594 $actorQuery[
'joins'] + $commentQuery[
'joins']
599 "WikiPage::getContent should return null after the page was suppressed for general users"
604 "WikiPage::getContent should return null after the page was suppressed for user zero"
609 "WikiPage::getContent should return null after the page was suppressed even for a sysop"
620 "[[original text]] foo",
623 $id = $page->getId();
624 $page->loadPageData();
628 $page->doDeleteUpdates( $page->getId(), $page->getContent(), $page->getRevision(),
$user );
636 # ------------------------
638 $res =
$dbr->select(
'pagelinks',
'*', [
'pl_from' => $id ] );
639 $n =
$res->numRows();
642 $this->assertEquals( 0, $n,
'pagelinks should contain no more links from the page' );
653 ->setConstructorArgs( [
$name ] )
655 [
'getSecondaryDataUpdates',
'getDeletionUpdates',
'unserializeContent' ]
660 $dataUpdate->_name =
"$name data update";
663 $deletionUpdate->_name =
"$name deletion update";
665 $handler->method(
'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
666 $handler->method(
'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
667 $handler->method(
'unserializeContent' )->willReturnCallback(
674 'wgContentHandlers', [
693 ->setConstructorArgs( [ $text ] )
694 ->setMethods( [
'getModel',
'getContentHandler' ] )
711 [
'main' => $mainContent1 ]
714 $dataUpdates = $page->getDeletionUpdates( $page->getRevisionRecord() );
715 $this->assertNotEmpty( $dataUpdates );
717 $updateNames = array_map(
function ( $du ) {
718 return isset( $du->_name ) ? $du->_name : get_class( $du );
722 $this->assertContains(
'M1 deletion update', $updateNames );
729 $page = $this->
newPage( __METHOD__ );
731 $rev = $page->getRevision();
732 $this->assertNull(
$rev );
737 $rev = $page->getRevision();
739 $this->assertEquals( $page->getLatest(),
$rev->getId() );
740 $this->assertEquals(
"some text",
$rev->getContent()->getText() );
747 $page = $this->
newPage( __METHOD__ );
756 $this->assertEquals(
"some text",
$content->getText() );
763 $page = $this->
newPage( __METHOD__ );
764 $this->assertFalse( $page->exists() );
768 $this->assertTrue( $page->exists() );
770 $page =
new WikiPage( $page->getTitle() );
771 $this->assertTrue( $page->exists() );
775 $this->assertFalse( $page->exists() );
777 $page =
new WikiPage( $page->getTitle() );
778 $this->assertFalse( $page->exists() );
783 [
'WikiPageTest_testHasViewableContent',
false,
true ],
784 [
'Special:WikiPageTest_testHasViewableContent',
false ],
785 [
'MediaWiki:WikiPageTest_testHasViewableContent',
false ],
786 [
'Special:Userlogin',
true ],
787 [
'MediaWiki:help',
true ],
797 $this->assertEquals( $viewable, $page->hasViewableContent() );
801 $this->assertTrue( $page->hasViewableContent() );
803 $page =
new WikiPage( $page->getTitle() );
804 $this->assertTrue( $page->hasViewableContent() );
812 'WikiPageTest_testGetRedirectTarget_2',
814 "#REDIRECT [[hello world]]",
820 'WikiPageTest_testGetRedirectTarget_3',
822 "#REDIRECT [[Media:hello_world]]",
827 'WikiPageTest_testGetRedirectTarget_4',
830 '#REDIRECT [[Foobar#🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴]]',
832 'Foobar#🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴🏴...'
843 'wgCapitalLinks' =>
true,
848 # sanity check, because this test seems to fail for no reason for some people.
849 $c = $page->getContent();
852 # now, test the actual redirect
853 $t = $page->getRedirectTarget();
854 $this->assertEquals( $target,
$t ?
$t->getFullText() :
null );
863 $this->assertEquals( !is_null( $target ), $page->isRedirect() );
870 [
'WikiPageTest_testIsCountable',
876 [
'WikiPageTest_testIsCountable',
884 [
'WikiPageTest_testIsCountable',
890 [
'WikiPageTest_testIsCountable',
898 [
'WikiPageTest_testIsCountable',
904 [
'WikiPageTest_testIsCountable',
912 [
'Talk:WikiPageTest_testIsCountable',
918 [
'Talk:WikiPageTest_testIsCountable',
926 [
'MediaWiki:WikiPageTest_testIsCountable.js',
932 [
'MediaWiki:WikiPageTest_testIsCountable.js',
956 $this->markTestSkipped(
"Can not use non-default content model $model for "
957 .
$title->getPrefixedDBkey() .
" with \$wgContentHandlerUseDB disabled." );
962 $editInfo = $page->prepareContentForEdit( $page->getContent() );
964 $v = $page->isCountable();
965 $w = $page->isCountable( $editInfo );
970 "isCountable( null ) returned unexpected value " . var_export( $v,
true )
971 .
" instead of " . var_export( $expected,
true )
972 .
" in mode `$mode` for text \"$text\""
978 "isCountable( \$editInfo ) returned unexpected value " . var_export( $v,
true )
979 .
" instead of " . var_export( $expected,
true )
980 .
" in mode `$mode` for text \"$text\""
989 "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
1000 $page = $this->
createPage( __METHOD__, $text, $model );
1002 $opt = $page->makeParserOptions(
'canonical' );
1003 $po = $page->getParserOutput(
$opt );
1004 $text = $po->getText();
1006 $text = trim( preg_replace(
'/<!--.*?-->/sm',
'', $text ) ); # strip injected comments
1007 $text = preg_replace(
'!\s*(</p>|</div>)!sm',
'\1', $text ); # don
't let tidy confuse us
1009 $this->assertEquals( $expectedHtml, $text );
1017 public function testGetParserOutput_nonexisting() {
1018 $page = new WikiPage( Title::newFromText( __METHOD__ ) );
1020 $opt = new ParserOptions();
1021 $po = $page->getParserOutput( $opt );
1023 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
1029 public function testGetParserOutput_badrev() {
1030 $page = $this->createPage( __METHOD__, 'dummy
', CONTENT_MODEL_WIKITEXT );
1032 $opt = new ParserOptions();
1033 $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 );
1035 // @todo would be neat to also test deleted revision
1037 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." );
1040 public static $sections =
1054 public function dataReplaceSection() {
1055 // NOTE: assume the Help namespace to contain wikitext
1057 [ 'Help:WikiPageTest_testReplaceSection
',
1058 CONTENT_MODEL_WIKITEXT,
1063 trim( preg_replace( '/^Intro/sm
', 'No more
', self::$sections ) )
1065 [ 'Help:WikiPageTest_testReplaceSection
',
1066 CONTENT_MODEL_WIKITEXT,
1073 [ 'Help:WikiPageTest_testReplaceSection
',
1074 CONTENT_MODEL_WIKITEXT,
1077 "== TEST ==\nmore fun",
1079 trim( preg_replace( '/^== test ==.*== foo ==/sm
',
1080 "== TEST ==\nmore fun\n\n== foo ==",
1083 [ 'Help:WikiPageTest_testReplaceSection
',
1084 CONTENT_MODEL_WIKITEXT,
1089 trim( self::$sections )
1091 [ 'Help:WikiPageTest_testReplaceSection
',
1092 CONTENT_MODEL_WIKITEXT,
1097 trim( self::$sections ) . "\n\n== New ==\n\nNo more"
1106 public function testReplaceSectionContent( $title, $model, $text, $section,
1107 $with, $sectionTitle, $expected
1109 $page = $this->createPage( $title, $text, $model );
1111 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
1113 $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
1115 $this->assertEquals( $expected, $c ? trim( $c->getText() ) : null );
1122 public function testReplaceSectionAtRev( $title, $model, $text, $section,
1123 $with, $sectionTitle, $expected
1125 $page = $this->createPage( $title, $text, $model );
1126 $baseRevId = $page->getLatest();
1128 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
1130 $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId );
1132 $this->assertEquals( $expected, $c ? trim( $c->getText() ) : null );
1138 public function testGetOldestRevision() {
1139 $page = $this->newPage( __METHOD__ );
1140 $page->doEditContent(
1141 new WikitextContent( 'one
' ),
1145 $rev1 = $page->getRevision();
1147 $page = new WikiPage( $page->getTitle() );
1148 $page->doEditContent(
1149 new WikitextContent( 'two
' ),
1154 $page = new WikiPage( $page->getTitle() );
1155 $page->doEditContent(
1156 new WikitextContent( 'three
' ),
1162 $this->assertNotEquals(
1164 $page->getRevision()->getId(),
1165 '$page->getRevision()->getId()
'
1169 $this->assertEquals(
1171 $page->getOldestRevision()->getId(),
1172 '$page->getOldestRevision()->getId()
'
1180 public function testDoRollback() {
1181 // FIXME: fails under postgres
1182 $this->markTestSkippedIfDbType( 'postgres
' );
1184 $admin = $this->getTestSysop()->getUser();
1185 $user1 = $this->getTestUser()->getUser();
1186 // Use the confirmed group for user2 to make sure the user is different
1187 $user2 = $this->getTestUser( [ 'confirmed
' ] )->getUser();
1189 // make sure we can test autopatrolling
1190 $this->setMwGlobals( 'wgUseRCPatrol
', true );
1192 // TODO: MCR: test rollback of multiple slots!
1193 $page = $this->newPage( __METHOD__ );
1197 $status1 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
1198 "section one", EDIT_NEW, false, $admin );
1201 $status2 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
1202 "adding section two", 0, false, $user1 );
1204 $text .= "\n\nthree";
1205 $status3 = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
1206 "adding section three", 0, false, $user2 );
1211 $rev1 = $status1->getValue()['revision'];
1212 $rev2 = $status2->getValue()['revision'];
1213 $rev3 = $status3->getValue()['revision'];
1220 $this->assertEquals( 3, Revision::countByPageId( wfGetDB( DB_REPLICA ), $page->getId() ) );
1221 $this->assertEquals( $admin->getName(), $rev1->getUserText() );
1222 $this->assertEquals( $user1->getName(), $rev2->getUserText() );
1223 $this->assertEquals( $user2->getName(), $rev3->getUserText() );
1225 // Now, try the actual rollback
1226 $token = $admin->getEditToken( 'rollback' );
1227 $rollbackErrors = $page->doRollback(
1236 if ( $rollbackErrors ) {
1238 "Rollback failed:\n" .
1239 print_r( $rollbackErrors, true ) . ";\n" .
1240 print_r( $resultDetails, true )
1244 $page = new WikiPage( $page->getTitle() );
1245 $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
1246 "rollback did not revert to the correct revision" );
1247 $this->assertEquals( "one\n\ntwo", $page->getContent()->getText() );
1249 $rc = MediaWikiServices::getInstance()->getRevisionStore()->getRecentChange(
1250 $page->getRevision()->getRevisionRecord()
1253 $this->assertNotNull( $rc, 'RecentChanges entry
' );
1254 $this->assertEquals(
1255 RecentChange::PRC_AUTOPATROLLED,
1256 $rc->getAttribute( 'rc_patrolled
' ),
1260 // TODO: MCR: assert origin once we write slot data
1261 // $mainSlot = $page->getRevision()->getRevisionRecord()->getSlot( SlotRecord::MAIN );
1262 // $this->assertTrue( $mainSlot->isInherited(), 'isInherited
' );
1263 // $this->assertSame( $rev2->getId(), $mainSlot->getOrigin(), 'getOrigin
' );
1270 public function testDoRollbackFailureSameContent() {
1271 $admin = $this->getTestSysop()->getUser();
1274 $page = $this->newPage( __METHOD__ );
1275 $page->doEditContent(
1276 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1282 $rev1 = $page->getRevision();
1284 $user1 = $this->getTestUser( [ 'sysop' ] )->getUser();
1286 $page = new WikiPage( $page->getTitle() );
1287 $page->doEditContent(
1288 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1289 "adding section two",
1295 # now, do a the rollback from the same user was doing the edit before
1296 $resultDetails = [];
1297 $token = $user1->getEditToken( 'rollback' );
1298 $errors = $page->doRollback(
1300 "testing revert same user",
1307 $this->assertEquals( [], $errors, "Rollback failed same user" );
1309 # now, try the rollback
1310 $resultDetails = [];
1311 $token = $admin->getEditToken( 'rollback' );
1312 $errors = $page->doRollback(
1321 $this->assertEquals(
1331 "Rollback not failed"
1334 $page = new WikiPage( $page->getTitle() );
1335 $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
1336 "rollback did not revert to the correct revision" );
1337 $this->assertEquals( "one", $page->getContent()->getText() );
1344 public function testDoRollbackTagging() {
1345 if ( !in_array( 'mw-
rollback', ChangeTags::getSoftwareTags() ) ) {
1346 $this->markTestSkipped( 'Rollback
tag deactivated, skipped the test.
' );
1349 $admin = new User();
1350 $admin->setName( 'Administrator
' );
1351 $admin->addToDatabase();
1353 $text = 'First line
';
1354 $page = $this->newPage( 'WikiPageTest_testDoRollbackTagging
' );
1355 $page->doEditContent(
1356 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1363 $secondUser = new User();
1364 $secondUser->setName( '92.65.217.32
' );
1365 $text .= '\n\nSecond line
';
1366 $page = new WikiPage( $page->getTitle() );
1367 $page->doEditContent(
1368 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
1369 'Adding second line
',
1375 // Now, try the rollback
1376 $admin->addGroup( 'sysop' ); // Make the test user a sysop
1377 $token = $admin->getEditToken( 'rollback' );
1378 $errors = $page->doRollback(
1379 $secondUser->getName(),
1387 // If doRollback completed without errors
1388 if ( $errors === [] ) {
1389 $tags = $resultDetails[ 'tags
' ];
1390 $this->assertContains( 'mw-
rollback', $tags );
1394 public function provideGetAutoDeleteReason() {
1404 [ "first edit", null ],
1406 "/first edit.*only contributor/",
1412 [ "first edit", null ],
1413 [ "second edit", null ],
1415 "/second edit.*only contributor/",
1421 [ "first edit", "127.0.2.22" ],
1422 [ "second edit", "127.0.3.33" ],
1432 . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
1433 . " nonumy eirmod tempor invidunt ut labore et dolore magna "
1434 . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
1435 . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
1436 . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'",
1440 '/first edit:.*\.\.\."/
',
1446 [ "first edit", "127.0.2.22" ],
1447 [ "", "127.0.3.33" ],
1449 "/before blanking.*first edit/",
1460 public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
1463 // NOTE: assume Help namespace to contain wikitext
1464 $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
1468 foreach ( $edits as $edit ) {
1471 if ( !empty( $edit[1] ) ) {
1472 $user->setName( $edit[1] );
1477 $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
1479 $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
1484 $reason = $page->getAutoDeleteReason( $hasHistory );
1486 if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) {
1487 $this->assertEquals( $expectedResult, $reason );
1489 $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
1490 "Autosummary didn't match expected pattern $expectedResult: $reason
" );
1493 $this->assertEquals( $expectedHistory, $hasHistory,
1494 "expected \$hasHistory to be
" . var_export( $expectedHistory, true ) );
1496 $page->doDeleteArticle( "done
" );
1499 public function providePreSaveTransform() {
1501 [ 'hello this is ~~~',
1502 "hello
this is [[
Special:Contributions/127.0.0.1|127.0.0.1]]
",
1504 [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1505 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1513 public function testWikiPageFactory() {
1514 $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
1515 $page = WikiPage::factory( $title );
1516 $this->assertEquals( WikiFilePage::class, get_class( $page ) );
1518 $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
1519 $page = WikiPage::factory( $title );
1520 $this->assertEquals( WikiCategoryPage::class, get_class( $page ) );
1522 $title = Title::makeTitle( NS_MAIN, 'SomePage' );
1523 $page = WikiPage::factory( $title );
1524 $this->assertEquals( WikiPage::class, get_class( $page ) );
1531 public function testLoadPageData() {
1532 $title = Title::makeTitle( NS_MAIN, 'SomePage' );
1533 $page = WikiPage::factory( $title );
1535 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1536 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1537 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1538 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1540 $page->loadPageData( IDBAccessObject::READ_NORMAL );
1541 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1542 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1543 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1544 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1546 $page->loadPageData( IDBAccessObject::READ_LATEST );
1547 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1548 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1549 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1550 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1552 $page->loadPageData( IDBAccessObject::READ_LOCKING );
1553 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1554 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1555 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1556 $this->assertFalse( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1558 $page->loadPageData( IDBAccessObject::READ_EXCLUSIVE );
1559 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_NORMAL ) );
1560 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LATEST ) );
1561 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_LOCKING ) );
1562 $this->assertTrue( $page->wasLoadedFrom( IDBAccessObject::READ_EXCLUSIVE ) );
1568 public function testUpdateCategoryCounts() {
1569 $page = new WikiPage( Title::newFromText( __METHOD__ ) );
1571 // Add an initial category
1572 $page->updateCategoryCounts( [ 'A' ], [], 0 );
1574 $this->assertEquals( 1, Category::newFromName( 'A' )->getPageCount() );
1575 $this->assertEquals( 0, Category::newFromName( 'B' )->getPageCount() );
1576 $this->assertEquals( 0, Category::newFromName( 'C' )->getPageCount() );
1578 // Add a new category
1579 $page->updateCategoryCounts( [ 'B' ], [], 0 );
1581 $this->assertEquals( 1, Category::newFromName( 'A' )->getPageCount() );
1582 $this->assertEquals( 1, Category::newFromName( 'B' )->getPageCount() );
1583 $this->assertEquals( 0, Category::newFromName( 'C' )->getPageCount() );
1585 // Add and remove a category
1586 $page->updateCategoryCounts( [ 'C' ], [ 'A' ], 0 );
1588 $this->assertEquals( 0, Category::newFromName( 'A' )->getPageCount() );
1589 $this->assertEquals( 1, Category::newFromName( 'B' )->getPageCount() );
1590 $this->assertEquals( 1, Category::newFromName( 'C' )->getPageCount() );
1593 public function provideUpdateRedirectOn() {
1594 yield [ '#REDIRECT [[Foo]]', true, null, true, true, 0 ];
1595 yield [ '#REDIRECT [[Foo]]', true, 'Foo', true, false, 1 ];
1596 yield [ 'SomeText', false, null, false, true, 0 ];
1597 yield [ 'SomeText', false, 'Foo', false, false, 1 ];
1611 public function testUpdateRedirectOn(
1613 $initialRedirectState,
1619 // FIXME: fails under sqlite and postgres
1620 $this->markTestSkippedIfDbType( 'sqlite' );
1621 $this->markTestSkippedIfDbType( 'postgres' );
1622 static $pageCounter = 0;
1625 $page = $this->createPage( Title::newFromText( __METHOD__ . $pageCounter ), $initialText );
1626 $this->assertSame( $initialRedirectState, $page->isRedirect() );
1628 $redirectTitle = is_string( $redirectTitle )
1629 ? Title::newFromText( $redirectTitle )
1632 $success = $page->updateRedirectOn( $this->db, $redirectTitle, $lastRevIsRedirect );
1633 $this->assertSame( $expectedSuccess, $success, 'Success assertion' );
1639 $this->assertRedirectTableCountForPageId( $page->getId(), $expectedRowCount );
1642 private function assertRedirectTableCountForPageId( $pageId, $expected ) {
1643 $this->assertSelect(
1646 [ 'rd_from' => $pageId ],
1647 [ [ strval( $expected ) ] ]
1654 public function testInsertRedirectEntry_insertsRedirectEntry() {
1655 $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
1656 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1658 $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
1659 $targetTitle->mInterwiki = 'eninter';
1660 $page->insertRedirectEntry( $targetTitle, null );
1662 $this->assertSelect(
1664 [ 'rd_from', 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
1665 [ 'rd_from' => $page->getId() ],
1667 strval( $page->getId() ),
1668 strval( $targetTitle->getNamespace() ),
1669 strval( $targetTitle->getDBkey() ),
1670 strval( $targetTitle->getFragment() ),
1671 strval( $targetTitle->getInterwiki() ),
1679 public function testInsertRedirectEntry_insertsRedirectEntryWithPageLatest() {
1680 $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
1681 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1683 $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
1684 $targetTitle->mInterwiki = 'eninter';
1685 $page->insertRedirectEntry( $targetTitle, $page->getLatest() );
1687 $this->assertSelect(
1689 [ 'rd_from', 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
1690 [ 'rd_from' => $page->getId() ],
1692 strval( $page->getId() ),
1693 strval( $targetTitle->getNamespace() ),
1694 strval( $targetTitle->getDBkey() ),
1695 strval( $targetTitle->getFragment() ),
1696 strval( $targetTitle->getInterwiki() ),
1704 public function testInsertRedirectEntry_doesNotInsertIfPageLatestIncorrect() {
1705 $page = $this->createPage( Title::newFromText( __METHOD__ ), 'A' );
1706 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1708 $targetTitle = Title::newFromText( 'SomeTarget#Frag' );
1709 $targetTitle->mInterwiki = 'eninter';
1710 $page->insertRedirectEntry( $targetTitle, 215251 );
1712 $this->assertRedirectTableCountForPageId( $page->getId(), 0 );
1715 private function getRow( array $overrides = [] ) {
1719 'page_is_redirect' => '1',
1720 'page_latest' => '99',
1721 'page_namespace' => '3',
1722 'page_title' => 'JaJaTitle',
1723 'page_restrictions' => 'edit=autoconfirmed,sysop:move=sysop',
1724 'page_touched' => '20120101020202',
1725 'page_links_updated' => '20140101020202',
1727 foreach ( $overrides as $key => $value ) {
1728 $row[$key] = $value;
1730 return (object)$row;
1733 public function provideNewFromRowSuccess() {
1734 yield 'basic row' => [
1736 function ( WikiPage $wikiPage, self $test ) {
1737 $test->assertSame( 44, $wikiPage->getId() );
1738 $test->assertSame( 76, $wikiPage->getTitle()->getLength() );
1739 $test->assertTrue( $wikiPage->isRedirect() );
1740 $test->assertSame( 99, $wikiPage->getLatest() );
1741 $test->assertSame( 3, $wikiPage->getTitle()->getNamespace() );
1742 $test->assertSame( 'JaJaTitle', $wikiPage->getTitle()->getDBkey() );
1745 'edit' => [ 'autoconfirmed', 'sysop' ],
1746 'move' => [ 'sysop' ],
1748 $wikiPage->getTitle()->getAllRestrictions()
1750 $test->assertSame( '20120101020202', $wikiPage->getTouched() );
1751 $test->assertSame( '20140101020202', $wikiPage->getLinksTimestamp() );
1754 yield 'different timestamp formats' => [
1756 'page_touched' => '2012-01-01 02:02:02',
1757 'page_links_updated' => '2014-01-01 02:02:02',
1759 function ( WikiPage $wikiPage, self $test ) {
1760 $test->assertSame( '20120101020202', $wikiPage->getTouched() );
1761 $test->assertSame( '20140101020202', $wikiPage->getLinksTimestamp() );
1764 yield 'no restrictions' => [
1766 'page_restrictions' => '',
1768 function ( WikiPage $wikiPage, self $test ) {
1774 $wikiPage->getTitle()->getAllRestrictions()
1778 yield 'not redirect' => [
1780 'page_is_redirect' => '0',
1782 function ( WikiPage $wikiPage, self $test ) {
1783 $test->assertFalse( $wikiPage->isRedirect() );
1796 public function testNewFromRow( $row, $assertions ) {
1797 $page = WikiPage::newFromRow( $row, 'fromdb' );
1798 $assertions( $page, $this );
1801 public function provideTestNewFromId_returnsNullOnBadPageId() {
1810 public function testNewFromId_returnsNullOnBadPageId( $pageId ) {
1811 $this->assertNull( WikiPage::newFromID( $pageId ) );
1817 public function testNewFromId_appearsToFetchCorrectRow() {
1818 $createdPage = $this->createPage( __METHOD__, 'Xsfaij09' );
1819 $fetchedPage = WikiPage::newFromID( $createdPage->getId() );
1820 $this->assertSame( $createdPage->getId(), $fetchedPage->getId() );
1821 $this->assertEquals(
1822 $createdPage->getContent()->getText(),
1823 $fetchedPage->getContent()->getText()
1830 public function testNewFromId_returnsNullOnNonExistingId() {
1831 $this->assertNull( WikiPage::newFromID( 2147483647 ) );
1834 public function provideTestInsertProtectNullRevision() {
1835 // phpcs:disable Generic.Files.LineLength
1838 [ 'edit' => 'sysop' ],
1839 [ 'edit' => '20200101040404' ],
1843 '(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)))'
1847 [ 'edit' => 'sysop', 'move' => 'something' ],
1848 [ 'edit' => '20200101040404', 'move' => '20210101050505' ],
1852 '(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)))'
1870 public function testInsertProtectNullRevision(
1879 $this->setContentLang( 'qqx' );
1881 $page = $this->createPage( __METHOD__, 'Goat' );
1883 $user = $user === null ? $user : $this->getTestSysop()->getUser();
1885 $result = $page->insertProtectNullRevision(
1894 $this->assertTrue( $result instanceof Revision );
1895 $this->assertSame( $expectedComment, $result->getComment( Revision::RAW ) );
1901 public function testUpdateRevisionOn_existingPage() {
1902 $user = $this->getTestSysop()->getUser();
1903 $page = $this->createPage( __METHOD__, 'StartText' );
1905 $revision = new Revision(
1908 'page' => $page->getId(),
1909 'title' => $page->getTitle(),
1910 'comment' => __METHOD__,
1911 'minor_edit' => true,
1912 'text' => __METHOD__ . '-text',
1913 'len' => strlen( __METHOD__ . '-text' ),
1914 'user' => $user->getId(),
1915 'user_text' => $user->getName(),
1916 'timestamp' => '20170707040404',
1917 'content_model' => CONTENT_MODEL_WIKITEXT,
1918 'content_format' => CONTENT_FORMAT_WIKITEXT,
1922 $result = $page->updateRevisionOn( $this->db, $revision );
1923 $this->assertTrue( $result );
1924 $this->assertSame( 9989, $page->getLatest() );
1925 $this->assertEquals( $revision, $page->getRevision() );
1931 public function testUpdateRevisionOn_NonExistingPage() {
1932 $user = $this->getTestSysop()->getUser();
1933 $page = $this->createPage( __METHOD__, 'StartText' );
1934 $page->doDeleteArticle( 'reason' );
1936 $revision = new Revision(
1939 'page' => $page->getId(),
1940 'title' => $page->getTitle(),
1941 'comment' => __METHOD__,
1942 'minor_edit' => true,
1943 'text' => __METHOD__ . '-text',
1944 'len' => strlen( __METHOD__ . '-text' ),
1945 'user' => $user->getId(),
1946 'user_text' => $user->getName(),
1947 'timestamp' => '20170707040404',
1948 'content_model' => CONTENT_MODEL_WIKITEXT,
1949 'content_format' => CONTENT_FORMAT_WIKITEXT,
1953 $result = $page->updateRevisionOn( $this->db, $revision );
1954 $this->assertFalse( $result );
1960 public function testUpdateIfNewerOn_olderRevision() {
1961 $user = $this->getTestSysop()->getUser();
1962 $page = $this->createPage( __METHOD__, 'StartText' );
1963 $initialRevision = $page->getRevision();
1965 $olderTimeStamp = wfTimestamp(
1967 wfTimestamp( TS_UNIX, $initialRevision->getTimestamp() ) - 1
1970 $olderRevision = new Revision(
1973 'page' => $page->getId(),
1974 'title' => $page->getTitle(),
1975 'comment' => __METHOD__,
1976 'minor_edit' => true,
1977 'text' => __METHOD__ . '-text',
1978 'len' => strlen( __METHOD__ . '-text' ),
1979 'user' => $user->getId(),
1980 'user_text' => $user->getName(),
1981 'timestamp' => $olderTimeStamp,
1982 'content_model' => CONTENT_MODEL_WIKITEXT,
1983 'content_format' => CONTENT_FORMAT_WIKITEXT,
1987 $result = $page->updateIfNewerOn( $this->db, $olderRevision );
1988 $this->assertFalse( $result );
1994 public function testUpdateIfNewerOn_newerRevision() {
1995 $user = $this->getTestSysop()->getUser();
1996 $page = $this->createPage( __METHOD__, 'StartText' );
1997 $initialRevision = $page->getRevision();
1999 $newerTimeStamp = wfTimestamp(
2001 wfTimestamp( TS_UNIX, $initialRevision->getTimestamp() ) + 1
2004 $newerRevision = new Revision(
2007 'page' => $page->getId(),
2008 'title' => $page->getTitle(),
2009 'comment' => __METHOD__,
2010 'minor_edit' => true,
2011 'text' => __METHOD__ . '-text',
2012 'len' => strlen( __METHOD__ . '-text' ),
2013 'user' => $user->getId(),
2014 'user_text' => $user->getName(),
2015 'timestamp' => $newerTimeStamp,
2016 'content_model' => CONTENT_MODEL_WIKITEXT,
2017 'content_format' => CONTENT_FORMAT_WIKITEXT,
2020 $result = $page->updateIfNewerOn( $this->db, $newerRevision );
2021 $this->assertTrue( $result );
2027 public function testInsertOn() {
2028 $title = Title::newFromText( __METHOD__ );
2029 $page = new WikiPage( $title );
2031 $startTimeStamp = wfTimestampNow();
2032 $result = $page->insertOn( $this->db );
2033 $endTimeStamp = wfTimestampNow();
2035 $this->assertInternalType( 'int', $result );
2036 $this->assertTrue( $result > 0 );
2038 $condition = [ 'page_id' => $result ];
2040 // Check the default fields have been filled
2041 $this->assertSelect(
2046 'page_restrictions',
2064 // Check the page_random field has been filled
2065 $pageRandom = $this->db->selectField( 'page', 'page_random', $condition );
2066 $this->assertTrue( (float)$pageRandom < 1 && (float)$pageRandom > 0 );
2068 // Assert the touched timestamp in the DB is roughly when we inserted the page
2069 $pageTouched = $this->db->selectField( 'page', 'page_touched', $condition );
2071 wfTimestamp( TS_UNIX, $startTimeStamp )
2072 <= wfTimestamp( TS_UNIX, $pageTouched )
2075 wfTimestamp( TS_UNIX, $endTimeStamp )
2076 >= wfTimestamp( TS_UNIX, $pageTouched )
2079 // Try inserting the same page again and checking the result is false (no change)
2080 $result = $page->insertOn( $this->db );
2081 $this->assertFalse( $result );
2087 public function testInsertOn_idSpecified() {
2088 $title = Title::newFromText( __METHOD__ );
2089 $page = new WikiPage( $title );
2092 $result = $page->insertOn( $this->db, $id );
2094 $this->assertSame( $id, $result );
2096 $condition = [ 'page_id' => $result ];
2098 // Check there is actually a row in the db
2099 $this->assertSelect(
2107 public function provideTestDoUpdateRestrictions_setBasicRestrictions() {
2108 // Note: Once the current dates passes the date in these tests they will fail.
2109 yield 'move something' => [
2111 [ 'move' => 'something' ],
2113 [ 'edit' => [], 'move' => [ 'something' ] ],
2116 yield 'move something, edit blank' => [
2118 [ 'move' => 'something', 'edit' => '' ],
2120 [ 'edit' => [], 'move' => [ 'something' ] ],
2123 yield 'edit sysop, with expiry' => [
2125 [ 'edit' => 'sysop' ],
2126 [ 'edit' => '21330101020202' ],
2127 [ 'edit' => [ 'sysop' ], 'move' => [] ],
2128 [ 'edit' => '21330101020202' ],
2130 yield 'move and edit, move with expiry' => [
2132 [ 'move' => 'something', 'edit' => 'another' ],
2133 [ 'move' => '22220202010101' ],
2134 [ 'edit' => [ 'another' ], 'move' => [ 'something' ] ],
2135 [ 'move' => '22220202010101' ],
2137 yield 'move and edit, edit with infinity expiry' => [
2139 [ 'move' => 'something', 'edit' => 'another' ],
2140 [ 'edit' => 'infinity' ],
2141 [ 'edit' => [ 'another' ], 'move' => [ 'something' ] ],
2142 [ 'edit' => 'infinity' ],
2144 yield 'non existing, create something' => [
2146 [ 'create' => 'something' ],
2148 [ 'create' => [ 'something' ] ],
2151 yield 'non existing, create something with expiry' => [
2153 [ 'create' => 'something' ],
2154 [ 'create' => '23451212112233' ],
2155 [ 'create' => [ 'something' ] ],
2156 [ 'create' => '23451212112233' ],
2164 public function testDoUpdateRestrictions_setBasicRestrictions(
2168 array $expectedRestrictions,
2169 array $expectedRestrictionExpiries
2171 if ( $pageExists ) {
2172 $page = $this->createPage( __METHOD__, 'ABC' );
2174 $page = new WikiPage( Title::newFromText( __METHOD__ . '-nonexist' ) );
2176 $user = $this->getTestSysop()->getUser();
2179 $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, 'aReason', $user, [] );
2181 $logId = $status->getValue();
2182 $allRestrictions = $page->getTitle()->getAllRestrictions();
2184 $this->assertTrue( $status->isGood() );
2185 $this->assertInternalType( 'int', $logId );
2186 $this->assertSame( $expectedRestrictions, $allRestrictions );
2187 foreach ( $expectedRestrictionExpiries as $key => $value ) {
2188 $this->assertSame( $value, $page->getTitle()->getRestrictionExpiry( $key ) );
2191 // Make sure the log entry looks good
2192 // log_params is not checked here
2193 $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
2194 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'log_comment' );
2195 $this->assertSelect(
2196 [ 'logging' ] + $actorQuery['tables'] + $commentQuery['tables'],
2198 'log_comment' => $commentQuery['fields']['log_comment_text'],
2199 'log_user' => $actorQuery['fields']['log_user'],
2200 'log_user_text' => $actorQuery['fields']['log_user_text'],
2204 [ 'log_id' => $logId ],
2207 (string)$user->getId(),
2209 (string)$page->getTitle()->getNamespace(),
2210 $page->getTitle()->getDBkey(),
2213 $actorQuery['joins'] + $commentQuery['joins']
2220 public function testDoUpdateRestrictions_failsOnReadOnly() {
2221 $page = $this->createPage( __METHOD__, 'ABC' );
2222 $user = $this->getTestSysop()->getUser();
2226 $readOnly = $this->getMockBuilder( ReadOnlyMode::class )
2227 ->disableOriginalConstructor()
2228 ->setMethods( [ 'isReadOnly', 'getReason' ] )
2230 $readOnly->expects( $this->once() )
2231 ->method( 'isReadOnly' )
2232 ->will( $this->returnValue( true ) );
2233 $readOnly->expects( $this->once() )
2234 ->method( 'getReason' )
2235 ->will( $this->returnValue( 'Some Read Only Reason' ) );
2236 $this->setService( 'ReadOnlyMode', $readOnly );
2238 $status = $page->doUpdateRestrictions( [], [], $cascade, 'aReason', $user, [] );
2239 $this->assertFalse( $status->isOK() );
2240 $this->assertSame( 'readonlytext', $status->getMessage()->getKey() );
2246 public function testDoUpdateRestrictions_returnsGoodIfNothingChanged() {
2247 $page = $this->createPage( __METHOD__, 'ABC' );
2248 $user = $this->getTestSysop()->getUser();
2250 $limit = [ 'edit' => 'sysop' ];
2252 $status = $page->doUpdateRestrictions(
2261 // The first entry should have a logId as it did something
2262 $this->assertTrue( $status->isGood() );
2263 $this->assertInternalType( 'int', $status->getValue() );
2265 $status = $page->doUpdateRestrictions(
2274 // The second entry should not have a logId as nothing changed
2275 $this->assertTrue( $status->isGood() );
2276 $this->assertNull( $status->getValue() );
2282 public function testDoUpdateRestrictions_logEntryTypeAndAction() {
2283 $page = $this->createPage( __METHOD__, 'ABC' );
2284 $user = $this->getTestSysop()->getUser();
2288 $status = $page->doUpdateRestrictions(
2289 [ 'edit' => 'sysop' ],
2296 $this->assertTrue( $status->isGood() );
2297 $this->assertInternalType( 'int', $status->getValue() );
2298 $this->assertSelect(
2300 [ 'log_type', 'log_action' ],
2301 [ 'log_id' => $status->getValue() ],
2302 [ [ 'protect', 'protect' ] ]
2305 // Modify the protection
2306 $status = $page->doUpdateRestrictions(
2307 [ 'edit' => 'somethingElse' ],
2314 $this->assertTrue( $status->isGood() );
2315 $this->assertInternalType( 'int', $status->getValue() );
2316 $this->assertSelect(
2318 [ 'log_type', 'log_action' ],
2319 [ 'log_id' => $status->getValue() ],
2320 [ [ 'protect', 'modify' ] ]
2323 // Remove the protection
2324 $status = $page->doUpdateRestrictions(
2332 $this->assertTrue( $status->isGood() );
2333 $this->assertInternalType( 'int', $status->getValue() );
2334 $this->assertSelect(
2336 [ 'log_type', 'log_action' ],
2337 [ 'log_id' => $status->getValue() ],
2338 [ [ 'protect', 'unprotect' ] ]
2346 public function testNewPageUpdater() {
2347 $user = $this->getTestUser()->getUser();
2348 $page = $this->newPage( __METHOD__, __METHOD__ );
2351 $content = $this->getMockBuilder( WikitextContent::class )
2352 ->setConstructorArgs( [ 'Hello World' ] )
2353 ->setMethods( [ 'getParserOutput' ] )
2355 $content->expects( $this->once() )
2356 ->method( 'getParserOutput' )
2357 ->willReturn( new ParserOutput( 'HTML' ) );
2359 $preparedEditBefore = $page->prepareContentForEdit( $content, null, $user );
2361 // provide context, so the cache can be kept in place
2362 $slotsUpdate = new revisionSlotsUpdate();
2363 $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
2365 $updater = $page->newPageUpdater( $user, $slotsUpdate );
2366 $updater->setContent( SlotRecord::MAIN, $content );
2367 $revision = $updater->saveRevision(
2368 CommentStoreComment::newUnsavedComment( 'test' ),
2372 $preparedEditAfter = $page->prepareContentForEdit( $content, $revision, $user );
2374 $this->assertSame( $revision->getId(), $page->getLatest() );
2376 // Parsed output must remain cached throughout.
2377 $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
2384 public function testGetDerivedDataUpdater() {
2385 $admin = $this->getTestSysop()->getUser();
2388 $page = $this->createPage( __METHOD__, __METHOD__ );
2389 $page = TestingAccessWrapper::newFromObject( $page );
2391 $revision = $page->getRevision()->getRevisionRecord();
2392 $user = $revision->getUser();
2394 $slotsUpdate = new RevisionSlotsUpdate();
2395 $slotsUpdate->modifyContent( SlotRecord::MAIN, new WikitextContent( 'Hello World' ) );
2397 // get a virgin updater
2398 $updater1 = $page->getDerivedDataUpdater( $user );
2399 $this->assertFalse( $updater1->isUpdatePrepared() );
2401 $updater1->prepareUpdate( $revision );
2403 // Re-use updater with same revision or content, even if base changed
2404 $this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, $revision ) );
2406 $slotsUpdate = RevisionSlotsUpdate::newFromContent(
2407 [ SlotRecord::MAIN => $revision->getContent( SlotRecord::MAIN ) ]
2409 $this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, null, $slotsUpdate ) );
2411 // Don't re-use for edit if base revision ID changed
2412 $this->assertNotSame(
2414 $page->getDerivedDataUpdater( $user, null, $slotsUpdate, true )
2417 // Don't re-use with different user
2418 $updater2a = $page->getDerivedDataUpdater( $admin, null, $slotsUpdate );
2419 $updater2a->prepareContent( $admin, $slotsUpdate, false );
2421 $updater2b = $page->getDerivedDataUpdater( $user, null, $slotsUpdate );
2422 $updater2b->prepareContent( $user, $slotsUpdate, false );
2423 $this->assertNotSame( $updater2a, $updater2b );
2425 // Don't re-use with different content
2426 $updater3 = $page->getDerivedDataUpdater( $admin, null, $slotsUpdate );
2427 $updater3->prepareUpdate( $revision );
2428 $this->assertNotSame( $updater2b, $updater3 );
2430 // Don't re-use if no context given
2431 $updater4 = $page->getDerivedDataUpdater( $admin );
2432 $updater4->prepareUpdate( $revision );
2433 $this->assertNotSame( $updater3, $updater4 );
2435 // Don't re-use if AGAIN no context given
2436 $updater5 = $page->getDerivedDataUpdater( $admin );
2437 $this->assertNotSame( $updater4, $updater5 );
2439 // Don't re-use cached "virgin
" unprepared updater
2440 $updater6 = $page->getDerivedDataUpdater( $admin, $revision );
2441 $this->assertNotSame( $updater5, $updater6 );
2444 protected function assertPreparedEditEquals(
2445 PreparedEdit $edit, PreparedEdit $edit2, $message = ''
2447 // suppress differences caused by a clock tick between generating the two PreparedEdits
2448 if ( abs( $edit->timestamp - $edit2->timestamp ) < 3 ) {
2449 $edit2 = clone $edit2;
2450 $edit2->timestamp = $edit->timestamp;
2452 $this->assertEquals( $edit, $edit2, $message );
2455 protected function assertPreparedEditNotEquals(
2456 PreparedEdit $edit, PreparedEdit $edit2, $message = ''
2458 if ( abs( $edit->timestamp - $edit2->timestamp ) < 3 ) {
2459 $edit2 = clone $edit2;
2460 $edit2->timestamp = $edit->timestamp;
2462 $this->assertNotEquals( $edit, $edit2, $message );