Page MenuHomePhorge

No OneTemporary

Size
18 KB
Referenced Files
None
Subscribers
None
diff --git a/maintenance/cleanupCaps.php b/maintenance/cleanupCaps.php
index e3b8ae1a4dc..0031d7a5971 100644
--- a/maintenance/cleanupCaps.php
+++ b/maintenance/cleanupCaps.php
@@ -64,10 +64,10 @@ class CleanupCaps extends TableCleanup {
$this->getServiceContainer()->getNamespaceInfo()->
isCapitalized( $this->namespace )
) {
- $this->output( "Will be moving pages to first letter capitalized titles" );
+ $this->output( "Will be moving pages to first letter capitalized titles\n" );
$callback = 'processRowToUppercase';
} else {
- $this->output( "Will be moving pages to first letter lowercase titles" );
+ $this->output( "Will be moving pages to first letter lowercase titles\n" );
$callback = 'processRowToLowercase';
}
@@ -82,6 +82,10 @@ class CleanupCaps extends TableCleanup {
protected function processRowToUppercase( $row ) {
$current = Title::makeTitle( $row->page_namespace, $row->page_title );
+ // Set the ID of the page because Title::exists will return false
+ // unless the Article ID is already known, because Title::canExist will be false
+ // when $wgCapitalLinks is true and the old title is in lower case.
+ $current->mArticleID = $row->page_id;
$display = $current->getPrefixedText();
$lower = $row->page_title;
$upper = $this->getServiceContainer()->getContentLanguage()->ucfirst( $row->page_title );
@@ -104,19 +108,31 @@ class CleanupCaps extends TableCleanup {
'Converting page title to first-letter uppercase',
false
);
- if ( $ok ) {
- $this->progress( 1 );
- if ( $row->page_namespace == $this->namespace ) {
- $talk = $target->getTalkPage();
- $row->page_namespace = $talk->getNamespace();
- if ( $talk->exists() ) {
+ if ( !$ok ) {
+ $this->progress( 0 );
+ return;
+ }
+
+ $this->progress( 1 );
+ if ( $row->page_namespace == $this->namespace ) {
+ // We need to fetch the existence of the talk page from the DB directly because
+ // Title::exists will return false if Title::canExist returns false. Title::canExist will
+ // return false if $wgCapitalLinks is true and the old title is in lowercase form.
+ $namespaceInfo = $this->getServiceContainer()->getNamespaceInfo();
+ if ( $namespaceInfo->canHaveTalkPage( $current ) ) {
+ $talkNamespace = $namespaceInfo->getTalk( $row->page_namespace );
+ $talkPageExists = $this->getReplicaDB()->newSelectQueryBuilder()
+ ->select( '1' )
+ ->from( 'page' )
+ ->where( [ 'page_title' => $row->page_title, 'page_namespace' => $talkNamespace ] )
+ ->caller( __METHOD__ )
+ ->fetchField();
+ if ( $talkPageExists ) {
+ $row->page_namespace = $talkNamespace;
$this->processRowToUppercase( $row );
- return;
}
}
}
-
- $this->progress( 0 );
}
protected function processRowToLowercase( $row ) {
@@ -141,19 +157,18 @@ class CleanupCaps extends TableCleanup {
}
$ok = $this->movePage( $current, $target, 'Converting page titles to lowercase', true );
- if ( $ok === true ) {
- $this->progress( 1 );
- if ( $row->page_namespace == $this->namespace ) {
- $talk = $target->getTalkPage();
+ if ( $ok !== true ) {
+ $this->progress( 0 );
+ }
+
+ $this->progress( 1 );
+ if ( $row->page_namespace == $this->namespace ) {
+ $talk = $current->getTalkPage();
+ if ( $talk->exists() ) {
$row->page_namespace = $talk->getNamespace();
- if ( $talk->exists() ) {
- $this->processRowToLowercase( $row );
- return;
- }
+ $this->processRowToLowercase( $row );
}
}
-
- $this->progress( 0 );
}
/**
diff --git a/tests/phpunit/maintenance/CleanupCapsTest.php b/tests/phpunit/maintenance/CleanupCapsTest.php
new file mode 100644
index 00000000000..694f11ddebc
--- /dev/null
+++ b/tests/phpunit/maintenance/CleanupCapsTest.php
@@ -0,0 +1,388 @@
+<?php
+
+namespace MediaWiki\Tests\Maintenance;
+
+use CleanupCaps;
+use MediaWiki\MainConfigNames;
+use MediaWiki\Title\Title;
+use StatusValue;
+
+/**
+ * @covers \CleanupCaps
+ * @group Database
+ * @author Dreamy Jazz
+ */
+class CleanupCapsTest extends MaintenanceBaseTestCase {
+
+ protected function getMaintenanceClass() {
+ return CleanupCaps::class;
+ }
+
+ /** @dataProvider provideCapitalLinksValues */
+ public function testWhenNamespaceEmpty( bool $capitalLinks, string $stringToExpectInOutput ) {
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks );
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*0 of 0 rows updated/' );
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput );
+ }
+
+ public static function provideCapitalLinksValues() {
+ return [
+ '$wgCapitalLinks is false' => [
+ false, 'Will be moving pages to first letter lowercase titles',
+ ],
+ '$wgCapitalLinks is true' => [
+ true, 'Will be moving pages to first letter capitalized titles',
+ ],
+ ];
+ }
+
+ /** @dataProvider provideCapitalLinksValues */
+ public function testWhenPagesExistButDoNotNeedMoving( bool $capitalLinks, string $stringToExpectInOutput ) {
+ // Generate a test page which does not need moving
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks );
+ $titleBeforeCall = 'test';
+ if ( $capitalLinks ) {
+ $titleBeforeCall = ucfirst( $titleBeforeCall );
+ }
+ $this->getExistingTestPage( Title::newFromText( $titleBeforeCall ) );
+
+ // Verify that the DB has been set up correctly for this test, in case Title::newFromText does
+ // normalisation that causes the title to be in a different form to what we want.
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( $titleBeforeCall );
+
+ // Run the maintenance script
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ // Assert on the output, checking that the script says the page are already fixed.
+ $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*0 of.*rows updated/' );
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput );
+ if ( $capitalLinks ) {
+ $expectedMoveLineOutput = '"Test" already uppercase';
+ } else {
+ $expectedMoveLineOutput = '"test" already lowercase';
+ }
+ $this->assertStringContainsString( $expectedMoveLineOutput, $actualOutput );
+
+ // Expect that no moves have occurred, as the page were already in the correct form
+ $this->newSelectQueryBuilder()
+ ->select( 'COUNT(*)' )
+ ->from( 'logging' )
+ ->join( 'actor', null, 'actor_id=log_actor' )
+ ->where( [ 'actor_name' => 'Conversion script' ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( 0 );
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( $titleBeforeCall );
+ }
+
+ /** @dataProvider provideCapitalLinksValues */
+ public function testWhenPagesExistAndNeedToBeMoved( bool $capitalLinks, string $stringToExpectInOutput ) {
+ // Generate three testing titles when $wgCapitalLinks is reversed from the value
+ // provided in $capitalLinks (so we can test that the script does convert).
+ $titles = [ 'a', 'test', 'test_abc' ];
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks );
+
+ if ( $capitalLinks ) {
+ $titlesBeforeCall = array_map( 'lcfirst', $titles );
+ } else {
+ $titlesBeforeCall = array_map( 'ucfirst', $titles );
+ }
+ foreach ( $titlesBeforeCall as $title ) {
+ $this->getExistingTestPage( Title::newFromText( $title ) );
+ }
+
+ // Check that the titles in the DB have the correct form (either uppercase or lowercase first character),
+ // as Title::newFromText could have modified this.
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( $titlesBeforeCall );
+
+ // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks );
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ // Assert that the script outputs it has renamed all of the pages
+ $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*3 of.*rows updated/' );
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput );
+
+ foreach ( $titles as $title ) {
+ $title = str_replace( '_', ' ', $title );
+ if ( $capitalLinks ) {
+ $originTitle = lcfirst( $title );
+ $destTitle = ucfirst( $title );
+ } else {
+ $originTitle = ucfirst( $title );
+ $destTitle = lcfirst( $title );
+ }
+ $this->assertStringContainsString( "\"$originTitle\" -> \"$destTitle\": OK", $actualOutput );
+ }
+
+ // Assert that the pages have actually been moved in the DB
+ $this->newSelectQueryBuilder()
+ ->select( 'COUNT(*)' )
+ ->from( 'logging' )
+ ->join( 'actor', null, 'actor_id=log_actor' )
+ ->where( [ 'actor_name' => 'Conversion script' ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( 3 );
+
+ if ( $capitalLinks ) {
+ $expectedTitles = array_map( 'ucfirst', $titles );
+ } else {
+ // When $wgCapitalLinks is false, redirects are left from the old name
+ // as they are still valid titles.
+ $expectedTitles = array_merge(
+ array_map( 'ucfirst', $titles ),
+ array_map( 'lcfirst', $titles )
+ );
+ }
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( $expectedTitles );
+ }
+
+ /** @dataProvider provideCapitalLinksValues */
+ public function testWhenPageExistsWithTalkPageThatNeedsMove( bool $capitalLinks, string $stringToExpectInOutput ) {
+ // Generate a page and an associated talk page that will need to be moved.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks );
+ $originTitle = 'abc';
+ if ( !$capitalLinks ) {
+ $originTitle = ucfirst( $originTitle );
+ }
+ $this->getExistingTestPage( Title::newFromText( $originTitle ) );
+ $this->getExistingTestPage( Title::newFromText( $originTitle, NS_TALK ) );
+
+ // Verify that the page_title column is set up correctly for the test.
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => [ 0, 1 ] ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ $originTitle, $originTitle ] );
+
+ // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks );
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ // Assert that the script says both the mainspace page and it's associated talk page have moved.
+ $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*2 of.*rows updated/' );
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput );
+
+ $destTitle = $capitalLinks ? ucfirst( $originTitle ) : lcfirst( $originTitle );
+ $this->assertStringContainsString( "\"$originTitle\" -> \"$destTitle\": OK", $actualOutput );
+ $this->assertStringContainsString( "\"Talk:$originTitle\" -> \"Talk:$destTitle\": OK", $actualOutput );
+
+ // Assert that what the script says it has done has been applied in the DB
+ $this->newSelectQueryBuilder()
+ ->select( 'COUNT(*)' )
+ ->from( 'logging' )
+ ->join( 'actor', null, 'actor_id=log_actor' )
+ ->where( [ 'actor_name' => 'Conversion script' ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( 2 );
+
+ if ( $capitalLinks ) {
+ $expectedTitles = [ $destTitle, $destTitle ];
+ } else {
+ // When $wgCapitalLinks is false, redirects are left from the old name
+ // as they are still valid titles.
+ $expectedTitles = [ $originTitle, $originTitle, $destTitle, $destTitle ];
+ }
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => [ 0, 1 ] ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( $expectedTitles );
+ }
+
+ /** @dataProvider provideCapitalLinksValues */
+ public function testWhenPagesExistForDryRun( bool $capitalLinks, string $stringToExpectInOutput ) {
+ // Generate a page that needs moving
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks );
+ $originTitle = 'abc';
+ if ( !$capitalLinks ) {
+ $originTitle = ucfirst( $originTitle );
+ }
+ $this->getExistingTestPage( Title::newFromText( $originTitle ) );
+ $this->getExistingTestPage( Title::newFromText( $originTitle, NS_TALK ) );
+
+ // Verify the page_title column before running the script as we want to assert no change by the
+ // script, so need to know the form before calling the script.
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => [ 0, 1 ] ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ $originTitle, $originTitle ] );
+
+ // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks );
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->setOption( 'dry-run', 1 );
+ $this->maintenance->execute();
+
+ // Expect that the script processes the pages, but does not move them as it's a dry run.
+ $this->expectOutputRegex( '/Processing page[\s\S]*Finished page.*2 of.*rows updated/' );
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput );
+
+ $destTitle = $capitalLinks ? ucfirst( $originTitle ) : lcfirst( $originTitle );
+ $this->assertStringContainsString(
+ "\"$originTitle\" -> \"$destTitle\": DRY RUN, NOT MOVED", $actualOutput
+ );
+ $this->assertStringContainsString(
+ "\"Talk:$originTitle\" -> \"Talk:$destTitle\": DRY RUN, NOT MOVED", $actualOutput
+ );
+
+ // Expect that the DB is untouched as this was a dry-run.
+ $this->newSelectQueryBuilder()
+ ->select( 'COUNT(*)' )
+ ->from( 'logging' )
+ ->join( 'actor', null, 'actor_id=log_actor' )
+ ->where( [ 'actor_name' => 'Conversion script' ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( 0 );
+
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => [ 0, 1 ] ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ $originTitle, $originTitle ] );
+ }
+
+ /** @dataProvider provideCapitalLinksValues */
+ public function testWhenMovePageIsValidMoveHookDeniesMove( bool $capitalLinks, string $stringToExpectInOutput ) {
+ // Generate a page that needs moving
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, !$capitalLinks );
+ $originTitle = 'abc';
+ if ( !$capitalLinks ) {
+ $originTitle = ucfirst( $originTitle );
+ }
+ $this->getExistingTestPage( Title::newFromText( $originTitle ) );
+
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => [ 0, 1 ] ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( $originTitle );
+
+ // Add a hook for MovePageIsValidMove that denies all attempts to move pages
+ $this->setTemporaryHook(
+ 'MovePageIsValidMove',
+ static function ( $unusedOne, $unusedTwo, StatusValue $status ) {
+ $status->fatal( 'test-status' );
+ }
+ );
+
+ // Make the change in $wgCapitalLinks and run the maintenance script to do the conversion.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, $capitalLinks );
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ // Assert that the output indicates the move failed
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString( $stringToExpectInOutput, $actualOutput );
+ $destTitle = $capitalLinks ? ucfirst( $originTitle ) : lcfirst( $originTitle );
+ $this->assertStringContainsString(
+ "\"$originTitle\" -> \"$destTitle\": FAILED", $actualOutput
+ );
+
+ // Expect that the titles have not been moved because the move was disallowed
+ $this->newSelectQueryBuilder()
+ ->select( 'COUNT(*)' )
+ ->from( 'logging' )
+ ->join( 'actor', null, 'actor_id=log_actor' )
+ ->where( [ 'actor_name' => 'Conversion script' ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( 0 );
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => [ 0, 1 ] ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValue( $originTitle );
+ }
+
+ public function testWhenToLowercaseFindsNewTitleAlreadyExists() {
+ // Get two pages which have the same name except for the capitalisation of the first character.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, false );
+ $this->getExistingTestPage( Title::newFromText( 'test' ) );
+ $this->getExistingTestPage( Title::newFromText( 'Test' ) );
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ 'Test', 'test' ] );
+
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ // Check that the maintenance script skipped the uppercase page because the lowercase version already exists.
+ $this->expectOutputRegex( '/"Test" skipped; "test" already exists/' );
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ 'Test', 'test' ] );
+ }
+
+ public function testWhenToUppercaseFindsNewTitleAlreadyExists() {
+ // Get two pages which have the same name except for the capitalisation of the first character.
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, false );
+ $this->getExistingTestPage( Title::newFromText( 'test' ) );
+ $this->getExistingTestPage( Title::newFromText( 'Test' ) );
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ 'Test', 'test' ] );
+
+ $this->overrideConfigValue( MainConfigNames::CapitalLinks, true );
+ $this->maintenance->setOption( 'namespace', 0 );
+ $this->maintenance->execute();
+
+ // Check that the lowercase version is renamed to 'CleanupCaps/test' because the uppercase version already
+ // exists. This is done to ensure that the title is not considered invalid by the Title class
+ $actualOutput = $this->getActualOutputForAssertion();
+ $this->assertStringContainsString(
+ "\"test\" -> \"CapsCleanup/test\": OK", $actualOutput
+ );
+ $this->newSelectQueryBuilder()
+ ->select( 'page_title' )
+ ->from( 'page' )
+ ->where( [ 'page_namespace' => 0 ] )
+ ->caller( __METHOD__ )
+ ->assertFieldValues( [ 'CapsCleanup/test', 'Test' ] );
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jul 5, 5:31 AM (14 h, 50 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
227469
Default Alt Text
(18 KB)

Event Timeline