49 $sourceWiki = $this->
getOption(
'remote-wiki' );
50 $cutoffTimestamp = ConvertibleTimestamp::convert( TS::MW, $this->
getArg( 0 ) );
52 $currentWiki = WikiMap::getCurrentWikiId();
53 if ( $sourceWiki === $currentWiki ) {
54 $this->
output(
"Source wiki must be different from the current wiki.\n" );
59 $this->interwikiDelimiter = $this->
getConfig()->get( MainConfigNames::UserrightsInterwikiDelimiter );
60 $titlePattern =
new LikeValue( $sourceDb->anyString(), $this->interwikiDelimiter . $currentWiki );
63 $this->
output(
"DRY RUN: No changes will be made\n" );
66 $minTimestamp = $sourceDb->newSelectQueryBuilder()
67 ->select(
'log_timestamp' )
70 'log_type' =>
'rights',
71 'log_action' =>
'rights',
73 ->orderBy(
'log_timestamp', SelectQueryBuilder::SORT_ASC )
74 ->caller( __METHOD__ )
77 if ( $minTimestamp ===
false ) {
78 $this->
output(
"No source data found, exiting\n" );
83 $lastTimestamp = $minTimestamp;
86 $minInsertedId =
null;
87 $maxInsertedId =
null;
89 $rows = DatabaseLogEntry::newSelectQueryBuilder( $sourceDb )
91 'log_type' =>
'rights',
92 'log_action' =>
'rights',
93 $sourceDb->expr(
'log_title', IExpression::LIKE, $titlePattern ),
94 $sourceDb->expr(
'log_timestamp',
'<', $sourceDb->timestamp( $cutoffTimestamp ) ),
97 $sourceDb->buildComparison(
'>', [
98 'log_timestamp' => $sourceDb->timestamp( $lastTimestamp ),
99 'log_id' => $lastLogId,
102 ->orderBy( [
'log_timestamp',
'log_id' ], SelectQueryBuilder::SORT_ASC )
104 ->caller( __METHOD__ )
107 if ( $rows->numRows() === 0 ) {
110 $this->
output(
"Processing batch of {$rows->numRows()} log entries...\n" );
114 $originalEntries = [];
115 $targetUserNames = [];
116 foreach ( $rows as $row ) {
117 $entry = DatabaseLogEntry::newFromRow( $row );
118 $originalEntries[] = $entry;
119 $targetUserNames[] = $this->getTargetUserName( $entry );
122 $renames = $this->getRenames( $targetUserNames );
126 $timestampsPresent = [];
127 foreach ( $originalEntries as $originalEntry ) {
128 $lastLogId = $originalEntry->getId();
129 $lastTimestamp = $originalEntry->getTimestamp();
131 $targetName = $this->getTargetUserName( $originalEntry );
132 $targetNewName = $this->getUpToDateUserName( $targetName, $originalEntry->getTimestamp(), $renames );
133 if ( $targetNewName !== $targetName ) {
134 $this->
output(
"Renaming $targetName to $targetNewName in entry $lastLogId\n" );
136 $targetName = $targetNewName;
137 $localTarget = Title::newFromText( $targetName, $originalEntry->getTarget()->getNamespace() );
139 $params = $originalEntry->getParameters();
140 if ( $originalEntry->isLegacy() ) {
143 $legacyParams = $originalEntry->getParameters();
144 if ( count( $legacyParams ) > 1 ) {
145 $oldGroups = array_map(
'trim', explode(
',', $legacyParams[0] ) );
146 $newGroups = array_map(
'trim', explode(
',', $legacyParams[1] ) );
148 '4::oldgroups' => $oldGroups,
149 '5::newgroups' => $newGroups,
154 $performerName = $originalEntry->getPerformerIdentity()->getName();
155 $performer = UserIdentityValue::newExternal( $sourceWiki, $performerName );
158 $logEntry->setTimestamp( $originalEntry->getTimestamp() );
159 $logEntry->setPerformer( $performer );
160 $logEntry->setTarget( $localTarget );
161 $logEntry->setComment( $originalEntry->getComment() );
162 $logEntry->setParameters( $params );
163 $logEntry->setDeleted( $originalEntry->getDeleted() );
164 $logsToInsert[] = $logEntry;
165 $timestampsPresent[] = $logEntry->getTimestamp();
168 $existingRows = DatabaseLogEntry::newSelectQueryBuilder( $this->
getReplicaDB() )
170 'log_type' =>
'rights',
171 'log_action' =>
'rights',
172 'log_timestamp' => array_map(
177 ->caller( __METHOD__ )
181 $existingChanges = [];
182 foreach ( $existingRows as $row ) {
183 $entry = DatabaseLogEntry::newFromRow( $row );
184 $existingChanges[ $entry->getTimestamp() ][] = $entry->getTarget()->getText();
187 foreach ( $logsToInsert as $logEntry ) {
192 isset( $existingChanges[ $logEntry->getTimestamp() ] )
193 && in_array( $logEntry->getTarget()->getText(), $existingChanges[ $logEntry->getTimestamp() ] )
200 $id = $logEntry->insert();
202 if ( $minInsertedId ===
null ) {
203 $minInsertedId = $id;
205 $maxInsertedId = $id;
213 $this->
output(
"Skipped $skipped log entries.\n" );
215 $this->
output(
"Would insert $count log entries.\n" );
217 LoggerFactory::getInstance(
'logentry' )->info(
218 'Backfilled {count} interwiki rights log entries from {sourceWiki}.',
221 'sourceWiki' => $sourceWiki,
222 'minInsertedId' => $minInsertedId,
223 'maxInsertedId' => $maxInsertedId,
227 $minInsertedId ??=
'(null)';
228 $maxInsertedId ??=
'(null)';
229 $this->
output(
"Inserted $count log entries, with ids between $minInsertedId and $maxInsertedId.\n" );