Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F585012
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
18 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jul 5, 5:31 AM (8 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
227469
Default Alt Text
(18 KB)
Attached To
Mode
rMW mediawiki
Attached
Detach File
Event Timeline
Log In to Comment