getServiceContainer(); return new SpecialNuke( $services->getJobQueueGroup(), $services->getDBLoadBalancerFactory(), $services->getPermissionManager(), $services->getRepoGroup(), $services->getUserFactory(), $services->getUserOptionsLookup(), $services->getUserNamePrefixSearch(), $services->getUserNameUtils(), $services->getNamespaceInfo(), $services->getContentLanguage(), $services->getService( 'NukeIPLookup' ) ); } /** * Ensure that the prompt prevents a user blocked from deleting * pages from accessing the form. * * @return void */ public function testBlocked() { $user = $this->getTestSysop()->getUser(); $performer = new UltimateAuthority( $user ); // Self-blocks should still prevent the form from being shown $this->getServiceContainer() ->getBlockUserFactory() ->newBlockUser( $user, $performer, 'infinity', 'SpecialNukeTest::testBlocked' ) ->placeBlockUnsafe(); $this->expectException( UserBlockedError::class ); $this->executeSpecialPage( '', null, 'qqx', $performer ); $this->getServiceContainer() ->getUnblockUserFactory() ->newUnblockUser( $user, $performer, 'SpecialNukeTest::testBlocked' ) ->unblockUnsafe(); } public function testProtectedPage() { $pages = []; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ]; $pages[] = $this->insertPage( 'Page456', 'Test', NS_MAIN )[ 'title' ]; $pages[] = $this->insertPage( 'Page789', 'Test', NS_MAIN )[ 'title' ]; $this->overrideConfigValues( [ "GroupPermissions" => [ "testgroup" => [ "nuke" => true, "delete" => true ] ] ] ); $services = $this->getServiceContainer(); $page = $services->getWikiPageFactory() ->newFromTitle( $pages[2] ); $restrictions = []; foreach ( $services->getRestrictionStore()->listApplicableRestrictionTypes( $page ) as $type ) { $restrictions[$type] = "sysop"; } $cascade = false; $page->doUpdateRestrictions( $restrictions, [], $cascade, "test", $this->getTestSysop()->getUser() ); $testUser = $this->getTestUser( [ "testgroup" ] ); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'other', 'wpReason' => 'Reason', 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $testUser->getUser()->getEditToken(), ], true ); $this->expectException( PermissionsError::class ); $this->executeSpecialPage( '', $request, 'qqx', $testUser->getAuthority() ); foreach ( $pages as $checkedPage ) { $this->assertTrue( $checkedPage->exists() ); } } public function testPrompt() { $admin = $this->getTestSysop()->getUser(); $this->disableAutoCreateTempUser(); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', null, 'qqx', $performer ); $this->assertStringContainsString( '(nuke-summary)', $html ); $this->assertStringContainsString( '(nuke-tools)', $html ); } /** * Ensure that the prompt prevents a nuke user without the checkuser-temporary-account permission * from performing CheckUser IP lookups * * @return void */ public function testPromptCheckUserNoPermission() { $this->expectException( PermissionsError::class ); $this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' ); $this->enableAutoCreateTempUser(); $ip = '1.2.3.4'; $adminUser = $this->getTestSysop()->getUser(); $permissionManager = $this->getServiceContainer()->getPermissionManager(); $permissions = $permissionManager->getUserPermissions( $adminUser ); $permissions = array_diff( $permissions, [ 'checkuser-temporary-account' ] ); $permissionManager->overrideUserRightsForTesting( $adminUser, $permissions ); $performer = new UltimateAuthority( $adminUser ); $request = new FauxRequest( [ 'target' => $ip, 'action' => 'submit', ], true ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); } /** * Ensure that the prompt prevents a nuke user who hasn't accepted the agreement * from performing CheckUser IP lookups * * @return void */ public function testPromptCheckUserNoPreference() { $this->expectException( ErrorPageError::class ); $this->expectExceptionMessage( 'To view temporary account contributions for an IP, please accept' . ' the agreement in [[Special:Preferences|your preferences]].' ); $this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' ); $this->enableAutoCreateTempUser(); $ip = '1.2.3.4'; $this->overrideConfigValues( [ 'GroupPermissions' => [ 'testgroup' => [ 'nuke' => true, 'checkuser-temporary-account' => true ] ] ] ); $adminUser = $this->getTestUser( [ 'testgroup' ] )->getUser(); $request = new FauxRequest( [ 'target' => $ip, 'action' => 'submit', ], true ); $adminPerformer = new UltimateAuthority( $adminUser ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer ); } /** * Ensure that the prompt displays the correct messages when * temp accounts and CheckUser are enabled * * @return void */ public function testPromptWithCheckUser() { $this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' ); $admin = $this->getTestSysop()->getUser(); $this->enableAutoCreateTempUser(); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', null, 'qqx', $performer ); $this->assertStringContainsString( '(nuke-summary)', $html ); $this->assertStringContainsString( '(nuke-tools-tempaccount)', $html ); } public function testPromptTarget() { $testUser = $this->getTestUser(); $performer = $testUser->getAuthority(); $this->editPage( 'Target1', 'test', "", NS_MAIN, $performer ); $this->editPage( 'Target2', 'test', "", NS_MAIN, $performer ); $adminUser = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'target' => $testUser->getUser()->getName() ] ); $adminPerformer = new UltimateAuthority( $adminUser ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer ); $this->assertStringContainsString( 'Target1', $html ); $this->assertStringContainsString( 'Target2', $html ); } /** * Ensure that the prompt works with anon IP searches when * temp accounts are disabled * * @return void */ public function testPromptTargetAnonUser() { $this->disableAutoCreateTempUser( [ 'known' => false ] ); $ip = '127.0.0.1'; $testUser = $this->getServiceContainer()->getUserFactory()->newAnonymous( $ip ); $performer = new UltimateAuthority( $testUser ); $this->editPage( 'Target1', 'test', "", NS_MAIN, $performer ); $this->editPage( 'Target2', 'test', "", NS_MAIN, $performer ); $adminUser = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'target' => $testUser->getUser()->getName() ] ); $adminPerformer = new UltimateAuthority( $adminUser ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer ); $this->assertStringContainsString( '(nuke-list:', $html ); $this->assertStringContainsString( 'Target1', $html ); $this->assertStringContainsString( 'Target2', $html ); $this->assertEquals( 2, substr_count( $html, '(nuke-editby: 127.0.0.1)' ) ); } /** * Ensure that the prompt returns temp accounts from IP lookups when * temp accounts and CheckUser are enabled * * @return void */ public function testPromptTargetCheckUser() { $this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' ); $this->enableAutoCreateTempUser(); $ip = '1.2.3.4'; RequestContext::getMain()->getRequest()->setIP( $ip ); $testUser = $this->getServiceContainer()->getTempUserCreator() ->create( null, new FauxRequest() )->getUser(); $this->editPage( 'Target1', 'test', "", NS_MAIN, $testUser ); $this->editPage( 'Target2', 'test', "", NS_MAIN, $testUser ); $adminUser = $this->getTestSysop()->getUser(); $permissionManager = $this->getServiceContainer()->getPermissionManager(); $permissionManager->overrideUserRightsForTesting( $adminUser, array_merge( $permissionManager->getUserPermissions( $adminUser ), [ 'checkuser-temporary-account-no-preference' ] ) ); $request = new FauxRequest( [ 'target' => $ip, 'action' => 'submit', ], true ); $adminPerformer = new UltimateAuthority( $adminUser ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer ); $this->assertEquals( 2, substr_count( $html, '(nuke-editby: ~2024-1)' ) ); $this->assertStringContainsString( '(nuke-list-tempaccount:', $html ); $this->assertStringContainsString( 'Target1', $html ); $this->assertStringContainsString( 'Target2', $html ); } /** * Ensure that the prompt returns temp accounts and IP accounts from IP lookups when * temp accounts and CheckUser are enabled and Anonymous IP accounts exist * * @return void */ public function testPromptTargetCheckUserMixed() { $this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' ); $this->disableAutoCreateTempUser( [ 'known' => false ] ); $ip = '1.2.3.4'; $testUser = $this->getServiceContainer()->getUserFactory()->newAnonymous( $ip ); $performer = new UltimateAuthority( $testUser ); // create a page as an anonymous IP user $this->editPage( 'Target1', 'test', "", NS_MAIN, $performer ); $this->enableAutoCreateTempUser(); RequestContext::getMain()->getRequest()->setIP( $ip ); $testUser = $this->getServiceContainer()->getTempUserCreator() ->create( null, new FauxRequest() )->getUser(); $performer = new UltimateAuthority( $testUser ); // create a page as a temp user $this->editPage( 'Target2', 'test', "", NS_MAIN, $performer ); $adminUser = $this->getTestSysop()->getUser(); $permissionManager = $this->getServiceContainer()->getPermissionManager(); $permissionManager->overrideUserRightsForTesting( $adminUser, array_merge( $permissionManager->getUserPermissions( $adminUser ), [ 'checkuser-temporary-account-no-preference' ] ) ); $request = new FauxRequest( [ 'target' => $ip, 'action' => 'submit', ], true ); $adminPerformer = new UltimateAuthority( $adminUser ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer ); $this->assertSame( 1, substr_count( $html, ' (nuke-editby: ~2024-1)' ) ); $this->assertSame( 1, substr_count( $html, ' (nuke-editby: 1.2.3.4)' ) ); // They should all show up together $this->assertStringContainsString( '(nuke-list-tempaccount:', $html ); $this->assertStringContainsString( 'Target1', $html ); $this->assertStringContainsString( 'Target2', $html ); } public function testListNoPagesGlobal() { $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'ThisPageShouldNotExist-' . rand() ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( '(nuke-nopages-global)', $html ); $this->assertStringNotContainsString( '(nuke-nopages)', $html ); } public function testListNoPagesUser() { $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'target' => 'ThisPageShouldNotExist-' . rand() ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringNotContainsString( '(nuke-nopages-global)', $html ); $this->assertStringContainsString( 'nuke-nopages', $html ); } public function testListNamespace() { $this->editPage( 'NukeUserPageTarget', 'test', '', NS_USER ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'NukeUserPageTarget', 'namespace' => NS_USER ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $expectedTitle = Title::makeTitle( NS_USER, 'NukeUserPageTarget' ) ->getPrefixedText(); $this->assertStringContainsString( $expectedTitle, $html ); } public function testListTalk() { $this->editPage( 'NukeTalkPageTarget', 'test', '', NS_TALK ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'NukeTalkPageTarget' ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $expectedTitle = Title::makeTitle( NS_TALK, 'NukeTalkPageTarget' ) ->getPrefixedText(); $this->assertStringContainsString( $expectedTitle, $html ); } public function testListCapitalizedNamespace() { $this->overrideConfigValues( [ 'CapitalLinks' => false, 'CapitalLinkOverrides' => [] ] ); $this->editPage( 'uncapsTarget', 'test' ); $this->editPage( 'UncapsTarget', 'test' ); $admin = $this->getTestSysop()->getUser(); $expectedTitle = Title::makeTitle( NS_MAIN, 'uncapsTarget' ) ->getPrefixedText(); $shouldMatch = [ "%ncapsTarget", "u%", "uncapsTarget" ]; $shouldNotMatch = [ "UncapsTarget" ]; foreach ( $shouldMatch as $match ) { $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'uncapsTarget' ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( $expectedTitle, $html, "match: $match" ); foreach ( $shouldNotMatch as $noMatch ) { $this->assertStringNotContainsString( $noMatch, $html ); } } } public function testListCapitalizedNamespaceOverrides() { $overriddenNamespaces = [ NS_PROJECT => true, NS_MEDIAWIKI => true, NS_USER => true, ]; $this->overrideConfigValues( [ 'CapitalLinks' => false, 'CapitalLinkOverrides' => $overriddenNamespaces ] ); $overriddenNamespaces[ NS_MAIN ] = false; foreach ( $overriddenNamespaces as $ns => $override ) { $this->editPage( "UncapsTarget" . $ns, 'test', '', $ns ); // If capital links for this ns is `true`, then the existing page should be edited. $this->editPage( "uncapsTarget" . $ns, 'test2', '', $ns ); } $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'u%' ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( "uncapsTarget" . NS_MAIN, $html ); // Check that the overridden namespaces are included in the search $this->assertStringContainsString( "UncapsTarget" . NS_PROJECT, $html ); $this->assertStringContainsString( "UncapsTarget" . NS_MEDIAWIKI, $html ); $this->assertStringContainsString( "UncapsTarget" . NS_USER, $html ); $this->assertStringNotContainsString( "UncapsTarget" . NS_MAIN, $html ); // Check that the non-overridden namespaces are not included in the search $this->assertStringNotContainsString( "uncapsTarget" . NS_PROJECT, $html ); $this->assertStringNotContainsString( "uncapsTarget" . NS_MEDIAWIKI, $html ); $this->assertStringNotContainsString( "uncapsTarget" . NS_USER, $html ); } /** * Check if Nuke still works with languages with complicated capitalization * or no capitalization. * * Since the first letter of each title should be capitalized, searching for the * lowercase version should still yield the uppercase version, or the same character * if casing does not exist for the language. * * @return void */ public function testListCapitalized() { /** @noinspection SpellCheckingInspection */ $testedLanguages = [ "ee" => [ "Ülemiste", "ülemiste" ], "zh" => "你好", "ja" => "にほんご", ]; foreach ( $testedLanguages as $lang => $testData ) { [ $created, $wanted ] = is_array( $testData ) ? $testData : [ $testData, $testData ]; $this->overrideConfigValue( 'LanguageCode', $lang ); $this->editPage( $created, 'test' ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => $wanted ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( $created, $html ); } } public function testListLimit() { $this->editPage( 'Page1', 'test' ); $this->editPage( 'Page2', 'test' ); $this->editPage( 'Page3', 'test' ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'Page%', 'limit' => 2 ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertEquals( 2, substr_count( $html, '
  • ' ) ); } public function testListLimitWithHooks() { $this->editPage( 'Page1', 'test' ); $this->editPage( 'Page2', 'test' ); $this->editPage( 'Page3', 'test' ); $this->setTemporaryHook( "NukeGetNewPages", static function ( $_1, $_2, $_3, $_4, &$pages ) { $pages[] = "Page3"; return true; } ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'Page%', 'limit' => 2 ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertEquals( 2, substr_count( $html, '
  • ' ) ); } public function testListMaxAge() { // 1 day $maxAge = 86400; $this->overrideConfigValues( [ 'NukeMaxAge' => $maxAge, 'RCMaxAge' => $maxAge ] ); // Will never show up. If it does, the max age isn't being applied at all. $page1Status = $this->editPage( 'Page1', 'test' ); // Will show up conditionally (see below). $page2Status = $this->editPage( 'Page2', 'test' ); // Will always show up. $this->editPage( 'Page3', 'test' ); // Prepare database connection and update query builder $dbw = $this->getServiceContainer()->getConnectionProvider()->getPrimaryDatabase(); $uqb = $dbw->newUpdateQueryBuilder() ->update( 'revision' ) ->caller( __METHOD__ ); // Manually change the rev_timestamp of Page1's creation in the database. ( clone $uqb ) ->set( [ 'rev_timestamp' => $dbw->timestamp( time() - ( $maxAge * 3 ) ) ] ) ->where( [ 'rev_id' => $page1Status->getNewRevision()->getId() ] )->execute(); // Manually change the rev_timestamp of Page2's creation in the database. ( clone $uqb ) ->set( [ 'rev_timestamp' => $dbw->timestamp( time() - $maxAge - 60 ) ] ) ->where( [ 'rev_id' => $page2Status->getNewRevision()->getId() ] )->execute(); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => 'Page%', 'limit' => 2 ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringNotContainsString( 'Page1', $html ); $this->assertStringNotContainsString( 'Page2', $html ); $this->assertStringContainsString( 'Page3', $html ); // Now check if resetting $wgNukeMaxAge will show Page2. // This should follow $wgRCMaxAge, which will include the page. $this->overrideConfigValues( [ 'RCMaxAge' => $maxAge * 2, 'NukeMaxAge' => 0 ] ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringNotContainsString( 'Page1', $html ); $this->assertStringContainsString( 'Page2', $html ); $this->assertStringContainsString( 'Page3', $html ); // Now check if expanding just $wgNukeMaxAge will show Page2. $this->overrideConfigValues( [ 'RCMaxAge' => $maxAge, 'NukeMaxAge' => $maxAge * 2 ] ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringNotContainsString( 'Page1', $html ); $this->assertStringContainsString( 'Page2', $html ); $this->assertStringContainsString( 'Page3', $html ); } public function testExecutePattern() { // Test that matching wildcards works, and that escaping wildcards works as documented // at https://www.mediawiki.org/wiki/Help:Extension:Nuke $this->editPage( '%PositiveNukeTest123', 'test' ); $this->editPage( 'NegativeNukeTest123', 'test' ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'pattern' => '\\%PositiveNukeTest%', 'wpFormIdentifier' => 'massdelete', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( 'PositiveNukeTest123', $html ); $this->assertStringNotContainsString( 'NegativeNukeTest123', $html ); } public function testListFiles() { $testFileName = $this->uploadTestFile()['title']->getPrefixedText(); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'namespace' => NS_FILE ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); // The title should be in the list $this->assertStringContainsString( $testFileName, $html ); // There should also be an image preview $this->assertStringContainsString( "getTestUser()->getUser(); $this->insertPage( 'Page123', 'Test', NS_MAIN, $user ); $this->insertPage( 'Paging456', 'Test', NS_MAIN, $user ); $this->insertPage( 'Should not show', 'No show' ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'target' => $user->getName(), 'wpFormIdentifier' => 'massdelete', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( 'Page123', $html ); $this->assertStringContainsString( 'Paging456', $html ); $this->assertStringNotContainsString( 'Should not show', $html ); } public function testNamespaces() { $this->insertPage( 'Page123', 'Test', NS_MAIN ); $this->insertPage( 'Paging456', 'Test', NS_MAIN ); $this->insertPage( 'Should not show', 'No show', NS_TALK ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'submit', 'namespace' => NS_MAIN, 'wpFormIdentifier' => 'massdelete', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertStringContainsString( 'Page123', $html ); $this->assertStringContainsString( 'Paging456', $html ); $this->assertStringNotContainsString( 'Should not show', $html ); } public function testDelete() { $pages = []; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ]; $pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN )[ 'title' ]; $admin = $this->getTestSysop()->getUser(); $fauxReason = "Reason " . wfRandomString(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Vandalism', 'wpReason' => $fauxReason, 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $deleteCount = count( $pages ); $this->assertStringContainsString( "(nuke-delete-summary: $deleteCount)", $html ); $this->assertStringContainsString( '(nuke-deletion-queued: Page123)', $html ); $this->assertStringContainsString( '(nuke-deletion-queued: Paging456)', $html ); // Pre-check to confirm that the page hasn't been deleted yet // "mw-tag-marker-nuke" is the CSS class for the "Nuke" tag's on Special:Log. $this->assertStringNotContainsString( 'mw-tag-marker-nuke', $this->getDeleteLogHtml() ); // Ensure all delete jobs are run $this->runJobs(); $deleteLogHtml = $this->getDeleteLogHtml(); $this->assertStringContainsString( 'Vandalism', $deleteLogHtml ); $this->assertStringContainsString( 'mw-tag-marker-nuke', $deleteLogHtml ); $this->assertStringContainsString( $fauxReason, $deleteLogHtml ); } public function testDeleteTarget() { $pages = []; $testUser = $this->getTestUser( "user" )->getUser(); $testUserName = $testUser->getName(); $pages[] = $this->uploadTestFile( $testUser )[ 'title' ]; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN, $testUser )[ 'title' ]; $pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN, $testUser )[ 'title' ]; $admin = $this->getTestSysop()->getUser(); $fauxReason = "Reason " . wfRandomString(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Vandalism', 'wpReason' => $fauxReason, 'target' => $testUserName, 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $deleteCount = count( $pages ); $this->assertStringContainsString( "(nuke-delete-summary-user: $deleteCount, $testUserName)", $html ); } public function testDeleteTargetAnon() { $pages = []; $testUser = $this->getTestUser( "user" )->getUser(); $testUserName = $testUser->getName(); $pages[] = $this->uploadTestFile( $testUser )[ 'title' ]; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN, $testUser )[ 'title' ]; $pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN, $testUser )[ 'title' ]; $admin = $this->getTestSysop()->getUser(); $fauxReason = "Reason " . wfRandomString(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Vandalism', 'wpReason' => $fauxReason, 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $deleteCount = count( $pages ); $this->assertStringContainsString( "(nuke-delete-summary: $deleteCount)", $html ); } public function testDeleteSkipped() { $pages = []; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ]; $pages[] = $this->insertPage( 'Paging 456', 'Test', NS_MAIN )[ 'title' ]; $skippedPage = $this->insertPage( 'Page 789', 'Test', NS_MAIN )['title']; $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Vandalism', 'originalPageList' => implode( '|', $pages ), 'pages' => [ $skippedPage->getPrefixedDBkey() ], 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $skippedCount = count( $pages ); $this->assertStringContainsString( '(nuke-delete-summary: 1)', $html ); $this->assertStringContainsString( "(nuke-skipped-summary: $skippedCount)", $html ); // Ensure all delete jobs are run $this->runJobs(); // Make sure that those pages are not in the deletion log. $deleteLogHtml = $this->getDeleteLogHtml(); foreach ( $pages as $title ) { $this->assertStringNotContainsString( $title->getPrefixedText(), $deleteLogHtml ); } $this->assertStringContainsString( $skippedPage->getPrefixedText(), $deleteLogHtml ); } public function testDeleteDropdownReason() { $pages = []; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ]; $pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN )[ 'title' ]; $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Vandalism', 'wpReason' => "", 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); $this->executeSpecialPage( '', $request, 'qqx', $performer ); // Ensure all jobs are run $this->runJobs(); // Check logs $this->assertStringContainsString( "Vandalism", $this->getDeleteLogHtml() ); } public function testDeleteCustomReason() { $pages = []; $pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ]; $pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN )[ 'title' ]; $admin = $this->getTestSysop()->getUser(); $fauxReason = "Reason " . wfRandomString(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'other', 'wpReason' => $fauxReason, 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); $this->executeSpecialPage( '', $request, 'qqx', $performer ); // Ensure all jobs are run $this->runJobs(); // Check logs $this->assertStringContainsString( $fauxReason, $this->getDeleteLogHtml() ); } public function testDeleteFiles() { $testFileName = $this->uploadTestFile()['title']->getPrefixedText(); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Reason', 'wpReason' => 'Reason', 'pages' => [ $testFileName ], 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getEditToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); // Files are deleted instantly $this->assertStringContainsString( "nuke-deleted", $html ); $this->assertStringContainsString( $testFileName, $html ); } public function testDeleteHook() { $wasCalled = false; $pages = []; $pages[] = $this->insertPage( 'Bad article', 'test', NS_MAIN )['title']; $pages[] = $this->insertPage( 'DO NOT DELETE', 'test', NS_MAIN )['title']; $pages[] = $this->insertPage( 'Bad article 2', 'test', NS_MAIN )['title']; $this->setTemporaryHook( "NukeDeletePage", static function ( Title $title, $reason, &$deletionResult ) use ( &$wasCalled ) { $wasCalled = true; $deletionResult = $title->getPrefixedText() !== "DO NOT DELETE"; return $title->getPrefixedText() === "Bad article 2"; } ); $admin = $this->getTestSysop()->getUser(); $request = new FauxRequest( [ 'action' => 'delete', 'wpDeleteReasonList' => 'Reason', 'wpReason' => 'Reason', 'pages' => $pages, 'wpFormIdentifier' => 'nukelist', 'wpEditToken' => $admin->getRequest()->getSession()->getToken(), ], true ); $performer = new UltimateAuthority( $admin ); [ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer ); $this->assertTrue( $wasCalled ); $this->assertStringContainsString( '(nuke-deleted: Bad article)', $html ); $this->assertStringContainsString( '(nuke-not-deleted: DO NOT DELETE)', $html ); $this->assertStringContainsString( '(nuke-deletion-queued: Bad article 2)', $html ); } public function testSearchSubpages() { $user = $this->getTestUser()->getUser(); $username = $user->getName(); $truncatedUsername = substr( $username, 0, 2 ); $searchResults1 = $this->newSpecialPage() ->prefixSearchSubpages( $truncatedUsername, 10, 0 ); $this->assertArrayContains( [ $username ], $searchResults1 ); $searchResults2 = $this->newSpecialPage() ->prefixSearchSubpages( "", 10, 0 ); $this->assertCount( 0, $searchResults2 ); } /** * Upload a test file. * * @param ?User|null $user * @return array Title object and page id */ private function uploadTestFile( ?User $user = null ): array { $exampleFilePath = realpath( __DIR__ . "/../../assets/Example.png" ); $tempFilePath = $this->getNewTempFile(); copy( $exampleFilePath, $tempFilePath ); $title = Title::makeTitle( NS_FILE, "Example " . rand() . ".png" ); $request = new FauxRequest( [], true ); $request->setUpload( 'wpUploadFile', [ 'name' => $title->getText(), 'type' => 'image/png', 'tmp_name' => $tempFilePath, 'size' => filesize( $tempFilePath ), 'error' => UPLOAD_ERR_OK ] ); $upload = UploadFromFile::createFromRequest( $request ); $uploadStatus = $upload->performUpload( "test", false, false, $user ?? $this->getTestUser( "user" )->getUser() ); $this->assertTrue( $uploadStatus->isOK() ); $this->getServiceContainer()->getJobRunner()->run( [] ); return [ 'title' => $title, 'id' => $title->getId() ]; } private function getDeleteLogHtml(): string { $services = $this->getServiceContainer(); // TODO: Make this use qqx so tests can be checked against system message keys. $specialLog = $services->getSpecialPageFactory()->getPage( 'Log' ); $specialLog->execute( "delete" ); return $specialLog->getOutput()->getHTML(); } }