getServiceContainer(); $this->hookContainer = $services->getHookContainer(); $this->linkRenderer = $services->getLinkRenderer(); $this->dbProvider = $services->getConnectionProvider(); $this->revisionStore = $services->getRevisionStore(); $this->namespaceInfo = $services->getNamespaceInfo(); $this->commentFormatter = $services->getCommentFormatter(); $this->linkBatchFactory = $services->getLinkBatchFactory(); $this->userFactory = $services->getUserFactory(); $this->pager = $this->getDeletedContribsPager(); } private function getDeletedContribsPager( $target = 'Some test user', $namespace = 0 ) { $target = UserIdentityValue::newAnonymous( $target ); return new DeletedContribsPager( $this->hookContainer, $this->linkRenderer, $this->dbProvider, $this->revisionStore, $this->namespaceInfo, $this->commentFormatter, $this->linkBatchFactory, $this->userFactory, RequestContext::getMain(), [ 'namespace' => $namespace ], $target ); } /** * Flow uses DeletedContribsPager::reallyDoQuery hook to provide something other then * stdClass as a row, and then manually formats its own row in ContributionsLineEnding. * Emulate this behaviour and check that it works. */ public function testDeletedContribProvidedByHook() { $this->setTemporaryHook( 'DeletedContribsPager::reallyDoQuery', static function ( &$data ) { $data = [ [ new class() { public $ar_timestamp = 12345; public $testing = 'TESTING'; public $ar_namespace = NS_MAIN; public $ar_title = 'Test'; public $ar_rev_id = null; } ] ]; } ); $this->setTemporaryHook( 'DeletedContributionsLineEnding', function ( $pager, &$ret, $row ) { $this->assertSame( 'TESTING', $row->testing ); $ret .= 'FROM_HOOK!'; } ); $pager = $this->getDeletedContribsPager(); $this->assertStringContainsString( 'FROM_HOOK!', $pager->getBody() ); } public static function provideEmptyResultIntegration() { $cases = [ [ 'target' => '', 'namespace' => '' ], [ 'target' => '127.0.0.1', 'namespace' => '' ], [ 'target' => 'UserWithNoEdits', 'namespace' => 1 ], ]; foreach ( $cases as $case ) { yield [ $case ]; } } /** * Confirm that the query is valid for various filter options. * * @dataProvider provideEmptyResultIntegration */ public function testEmptyResultIntegration( $options ) { $pager = $this->getDeletedContribsPager( $options['target'], $options['namespace'], ); $this->assertIsString( $pager->getBody() ); $this->assertSame( 0, $pager->getNumRows() ); } public function testPopulatedIntegrationNoPermissions() { $pager = $this->getDeletedContribsPager( self::$user->getName() ); $this->assertIsString( $pager->getBody() ); $this->assertSame( 1, $pager->getNumRows() ); } public function testPopulatedIntegrationWithPermissions() { $this->setGroupPermissions( [ '*' => [ 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, ] ] ); $pager = $this->getDeletedContribsPager( self::$user->getName() ); $this->assertIsString( $pager->getBody() ); $this->assertSame( 2, $pager->getNumRows() ); $this->assertStringContainsString( '>+9<', $pager->getBody() ); } public function testParentRevisionSizePreloading() { $this->setGroupPermissions( [ '*' => [ 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, ] ] ); $pager = $this->getDeletedContribsPager( self::$user->getName() ); // Make sure the query leaves (at least) one row unselected // so that we can test loading from parent revision ids TestingAccessWrapper::newFromObject( $pager )->deletedOnly = true; $this->assertIsString( $pager->getBody() ); $this->assertSame( 1, $pager->getNumRows() ); $this->assertStringContainsString( '>+9<', $pager->getBody() ); } public function addDBDataOnce() { self::$user = $this->getTestUser()->getUser(); $title = Title::makeTitle( NS_MAIN, 'DeletedContribsPagerTest' ); // Make two edits (one will be revdel'd) $this->editPage( $title, 'Test', '', NS_MAIN, self::$user ); $status = $this->editPage( $title, 'Test content.', '', NS_MAIN, self::$user ); // Delete the page where the edits were made $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title ); $this->deletePage( $page ); // Suppress the second edit $this->getDb()->newUpdateQueryBuilder() ->update( 'archive' ) ->set( [ 'ar_deleted' => RevisionRecord::DELETED_USER | RevisionRecord::DELETED_TEXT, // This is to ensure the minor edits path doesn't encounter an error 'ar_minor_edit' => 1, ] ) ->where( [ 'ar_rev_id' => $status->getNewRevision()->getId() ] ) ->execute(); } }