getServiceContainer()->getSpecialPageFactory()->getPage( 'Newpages' ); } /** * Asserts that the form fields for the Special:NewPages page are present. * * @param string $html The HTML returned by the special page * @param bool $canAnonUsersCreatePages Whether anonymous users should be able to create pages */ private function verifyFormFieldsArePresent( string $html, bool $canAnonUsersCreatePages ) { // Verify that the form labels are present. This is a good way to check that the form fields are present, // since the form labels should be generated by the form field definitions. $this->assertStringContainsString( '(namespace)', $html, 'Namespace filter not added to form' ); $this->assertStringContainsString( '(newpages-username)', $html, 'Username filter not added to form' ); $this->assertStringContainsString( '(namespace_association)', $html, 'Associated namespace filter not added to form' ); $this->assertStringContainsString( '(tag-filter)', $html, 'Tag filter not added to form' ); $this->assertStringContainsString( '(invert)', $html, 'Invert checkbox not added to form' ); $this->assertStringContainsString( '(minimum-size)', $html, 'Size filter not added to form' ); // Verify that the filter links are present in the form if ( $canAnonUsersCreatePages ) { $this->assertStringContainsString( '(newpages-showhide-registered', $html, 'Registered filter should be present' ); } else { $this->assertStringNotContainsString( '(newpages-showhide-registered', $html, 'Registered filter should not be present' ); } $this->assertStringContainsString( '(newpages-showhide-bots', $html, 'Missing bots filter' ); $this->assertStringContainsString( '(newpages-showhide-redirect', $html, 'Missing redirect filter' ); $this->assertStringContainsString( '(newpages-submit)', $html, 'Submit button text not as expected' ); } /** * @param string $group The group to allow or disallow creating pages * @param bool $state Whether to allow or disallow the given $group from creating pages */ private function setGroupHasRightsToCreatePages( string $group, bool $state ) { // Remove the 'createtalk' and 'createpage' rights from the '*' group if they are present for the test. $groupPermissionsValue = $this->getServiceContainer()->getMainConfig() ->get( MainConfigNames::GroupPermissions ); if ( $state ) { $groupPermissionsValue[$group]['createtalk'] = true; $groupPermissionsValue[$group]['createpage'] = true; } else { unset( $groupPermissionsValue[$group]['createtalk'] ); unset( $groupPermissionsValue[$group]['createpage'] ); } $this->overrideConfigValue( MainConfigNames::GroupPermissions, $groupPermissionsValue ); } /** * Helper method used to expect that one element matches the given selector inside the given parent element. * * @param DOMElement|Document $document The element to search through * @param string $selector The CSS selector which should match only one element * @return DOMElement|Element The matched element */ private function getAndExpectSingleMatchingElement( $document, string $selector ) { $matchingClass = DOMCompat::querySelectorAll( $document, $selector ); $this->assertCount( 1, $matchingClass, "One element was expected to match $selector" ); return $matchingClass[0]; } /** * Verifies that the given new pages line has the expected elements. * * @param DOMElement|Element $line The line element to verify * @param RevisionRecord $firstRevision The first revision of the page */ private function verifyLineHasExpectedElements( $line, RevisionRecord $firstRevision ) { // Verify the timestamp element is present $this->getAndExpectSingleMatchingElement( $line, ".mw-newpages-time" ); // Verify that the page name is as expected. $pageNameElement = $this->getAndExpectSingleMatchingElement( $line, ".mw-newpages-pagename" ); $this->assertSame( $this->getServiceContainer()->getTitleFormatter()->getPrefixedText( $firstRevision->getPage() ), $pageNameElement->textContent ); // Verify that the edit page and page history links are there $editLinkElement = $this->getAndExpectSingleMatchingElement( $line, ".mw-newpages-edit" ); $this->assertSame( '(editlink)', $editLinkElement->textContent ); $pageHistoryLinkElement = $this->getAndExpectSingleMatchingElement( $line, ".mw-newpages-history" ); $this->assertSame( '(hist)', $pageHistoryLinkElement->textContent ); // Verify that the user link is present and correct, including that the username is hidden if the current // authority cannot see it. $authority = RequestContext::getMain()->getAuthority(); $userNameElement = $this->getAndExpectSingleMatchingElement( $line, ".mw-userlink" ); if ( $firstRevision->userCan( RevisionRecord::DELETED_USER, $authority ) ) { $expectedUserText = $firstRevision->getUser( RevisionRecord::RAW )->getName(); } else { $expectedUserText = '(rev-deleted-user)'; } $this->assertSame( $expectedUserText, $userNameElement->textContent ); // Verify that the comment is present if visible or hidden if not $commentElement = $this->getAndExpectSingleMatchingElement( $line, ".comment" ); if ( $firstRevision->userCan( RevisionRecord::DELETED_COMMENT, $authority ) ) { $this->assertStringContainsString( $firstRevision->getComment()->text, $commentElement->textContent ); } else { $this->assertStringContainsString( '(rev-deleted-comment)', $commentElement->textContent ); } } /** * Perform testing steps that are common to all of the tests in this file. * * @param Title[] $expectedPages A list of Title objects for pages that should appear in the results * @param Title[] $expectedPagesNotShown A list of Title objects for pages that should not appear in the results * @param ?FauxRequest $fauxRequest A fake request to use for the test, null just uses the main request * @param bool $canAnonUsersCreatePages Whether IP addresses can create pages * @param ?bool $canTempUsersCreatePages Null if temporary accounts are disabled and not known about. * A boolean if temporary accounts are enabled, and the boolean is whether temporary accounts can create pages. * @return string */ private function testLoadPage( array $expectedPages, array $expectedPagesNotShown, ?FauxRequest $fauxRequest = null, bool $canAnonUsersCreatePages = false, ?bool $canTempUsersCreatePages = false ): string { $this->setGroupHasRightsToCreatePages( '*', $canAnonUsersCreatePages ); if ( $canTempUsersCreatePages !== null ) { // If the $canTempUsersCreatePages is set to a boolean, then enable temp users as temporary users are // being used in the test. $this->enableAutoCreateTempUser(); $this->setGroupHasRightsToCreatePages( 'temp', $canTempUsersCreatePages ); } $this->overrideConfigValues( [ MainConfigNames::UseNPPatrol => true, MainConfigNames::UseRCPatrol => true, ] ); // This is explicitly needed because the HTMLSizeFilterField uses the user's language and not the language // set by ::executeSpecialPage. $this->setUserLang( 'qqx' ); // Call the special page and verify that the form fields are as expected. [ $html ] = $this->executeSpecialPage( '', $fauxRequest ); $this->verifyFormFieldsArePresent( $html, $canAnonUsersCreatePages ); // Verify that the pages which should be there are present in the page. $contributionsList = $this->getAndExpectSingleMatchingElement( DOMUtils::parseHTML( $html ), '.mw-contributions-list' ); foreach ( $expectedPages as $page ) { // Find the line with the matching revision ID $firstRevision = $this->getServiceContainer()->getRevisionStore()->getFirstRevision( $page ); $matchingLine = $this->getAndExpectSingleMatchingElement( $contributionsList, "li[data-mw-revid=\"{$firstRevision->getId()}\"]" ); // Check that this matching line has the expected structure. $this->verifyLineHasExpectedElements( $matchingLine, $firstRevision ); } // Check that the pages which shouldn't be there are not added to the page. foreach ( $expectedPagesNotShown as $page ) { $firstRevId = $this->getServiceContainer()->getRevisionStore()->getFirstRevision( $page )->getId(); $matchingLines = DOMCompat::querySelectorAll( $contributionsList, "[data-mw-revid=\"$firstRevId\"]" ); $this->assertCount( 0, $matchingLines, "New page entry for revision $firstRevId was not expected" ); } // Verify that the edit is never shown $matchingLines = DOMCompat::querySelectorAll( $contributionsList, '[data-mw-revid="' . self::$editRevId . '"]' ); $this->assertCount( 0, $matchingLines, 'A revision ID which is not associated with a new page creation is present in Special:NewPages.' ); // Return the HTML to allow further custom testing by the methods which called this method. return $html; } public function testLoadWithNoOptionsSpecified() { // Expect that by default all new main space page creations are shown, but no other pages. $expectedPages = []; $expectedPagesNotShown = []; foreach ( self::$allPages as $page ) { if ( $page->getNamespace() === NS_MAIN ) { $expectedPages[] = $page; } else { $expectedPagesNotShown[] = $page; } } $this->testLoadPage( $expectedPages, $expectedPagesNotShown ); } public function testWhenFilteredToJustTestUser1Pages() { // Filter for all page creations by the first test user. $this->testLoadPage( self::$testUser1Pages, array_diff( self::$allPages, self::$testUser1Pages ), new FauxRequest( [ 'username' => self::$testUser1->getName(), 'namespace' => 'all' ] ) ); } public function testWhenFilteredToJustAnonCreations() { // Filter for all page creations by anon users in any namespace. $fauxRequest = new FauxRequest( [ 'hideliu' => true, 'namespace' => '' ] ); $this->testLoadPage( array_diff( self::$allPages, self::$testUser1Pages ), self::$testUser1Pages, $fauxRequest, true, true ); } public function testWhenFilteredToJustAnonCreationsWhenTemporaryAccountsAreDisabled() { // Filter for all page creations by anon users in any namespace. $fauxRequest = new FauxRequest( [ 'hideliu' => true, 'namespace' => '' ] ); // The expected pages should only be creations where the author is not an IP address. $expectedPages = array_filter( self::$allPages, function ( $page ) { $firstRev = $this->getServiceContainer()->getRevisionStore()->getFirstRevision( $page ); return !$firstRev->getUser()->isRegistered(); } ); $this->disableAutoCreateTempUser(); $this->testLoadPage( $expectedPages, array_diff( self::$allPages, $expectedPages ), $fauxRequest, true, null ); } public function addDBDataOnce() { // Create some pages so that there will be some entries in Special:NewPages. $testUser1 = $this->getMutableTestUser()->getUser(); // Get the first test user to create a page and it's associated talk page in mainspace. $firstPage = $this->insertPage( 'SpecialNewPagesTest1', 'test', NS_MAIN, $testUser1 ); $secondPage = $this->insertPage( 'SpecialNewPagesTest1', 'talk', NS_TALK, $testUser1 ); // Get the first test user to create it's userpage $thirdPage = $this->insertPage( $testUser1->getName(), 'userpage', NS_USER, $testUser1 ); // Get an anon user to create a page in the template namespace. $this->disableAutoCreateTempUser(); $fourthPage = $this->insertPage( 'SpecialNewPagesTest2', 'test', NS_TEMPLATE, $this->getServiceContainer()->getUserFactory()->newFromName( '127.0.0.1', UserFactory::RIGOR_NONE ) ); // Get a temporary account to create a page in the project namespace. $this->enableAutoCreateTempUser(); $testTempUser = $this->getServiceContainer()->getTempUserCreator() ->create( null, RequestContext::getMain()->getRequest() ); $this->assertStatusGood( $testTempUser ); $fifthPage = $this->insertPage( 'SpecialNewPagesTest3', 'test', NS_PROJECT, $testTempUser->getUser() ); // Get the sysop test user to make an edit, to test it won't appear in Special:NewPages. $editStatus = $this->editPage( $firstPage['title'], 'testing1234', 'test edit' ); $this->assertStatusGood( $editStatus ); self::$testUser1 = $testUser1; self::$testUser1Pages = [ $firstPage['title'], $secondPage['title'], $thirdPage['title'], ]; self::$allPages = array_merge( self::$testUser1Pages, [ $fourthPage['title'], $fifthPage['title'] ] ); self::$editRevId = $editStatus->getNewRevision()->getId(); } }