26 require_once __DIR__ .
'/Maintenance.php';
43 parent::__construct();
48 "Only rebuild rows in requested time range (in YYYYMMDDHHMMSS format)",
54 "Only rebuild rows in requested time range (in YYYYMMDDHHMMSS format)",
66 $this->
fatalError(
"Both 'from' and 'to' must be given, or neither" );
69 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
78 $this->
output(
"Done.\n" );
93 $days = $sec / 24 / 3600;
94 $this->
output(
"Rebuilding range of $sec seconds ($days days)\n" );
99 $this->
output(
"Rebuilding \$wgRCMaxAge=$wgRCMaxAge seconds ($days days)\n" );
102 $this->cutoffTo = time();
105 $this->
output(
"Clearing recentchanges table for time range...\n" );
106 $rcids = $dbw->selectFieldValues(
110 'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
111 'rc_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
114 foreach ( array_chunk( $rcids, $this->
getBatchSize() )
as $rcidBatch ) {
115 $dbw->delete(
'recentchanges', [
'rc_id' => $rcidBatch ], __METHOD__ );
119 $this->
output(
"Loading from page and revision tables...\n" );
121 $commentQuery = $commentStore->getJoin(
'rev_comment' );
124 [
'revision',
'page' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
134 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
136 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
137 'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
140 [
'ORDER BY' =>
'rev_timestamp DESC' ],
142 'page' => [
'JOIN',
'rev_page=page_id' ],
143 ] + $commentQuery[
'joins'] + $actorQuery[
'joins']
146 $this->
output(
"Inserting from page and revision tables...\n" );
149 foreach (
$res as $row ) {
150 $comment = $commentStore->getComment(
'rev_comment', $row );
155 'rc_timestamp' => $row->rev_timestamp,
156 'rc_namespace' => $row->page_namespace,
157 'rc_title' => $row->page_title,
158 'rc_minor' => $row->rev_minor_edit,
160 'rc_new' => $row->page_is_new,
161 'rc_cur_id' => $row->page_id,
162 'rc_this_oldid' => $row->rev_id,
163 'rc_last_oldid' => 0,
166 'rc_deleted' => $row->rev_deleted
167 ] + $commentStore->insert( $dbw,
'rc_comment', $comment )
168 + $actorMigration->getInsertValues( $dbw,
'rc_user',
$user ),
172 $rcid = $dbw->insertId();
175 [
'ct_rc_id' => $rcid ],
176 [
'ct_rev_id' => $row->rev_id ],
193 $this->
output(
"Updating links and size differences...\n" );
195 # Fill in the rc_last_oldid field, which points to the previous edit
198 [
'rc_cur_id',
'rc_this_oldid',
'rc_timestamp' ],
200 "rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
201 "rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
204 [
'ORDER BY' =>
'rc_cur_id,rc_timestamp' ]
211 foreach (
$res as $obj ) {
214 if ( $obj->rc_cur_id != $lastCurId ) {
215 # Switch! Look up the previous last edit, if any
216 $lastCurId = intval( $obj->rc_cur_id );
217 $emit = $obj->rc_timestamp;
219 $row = $dbw->selectRow(
221 [
'rev_id',
'rev_len' ],
222 [
'rev_page' => $lastCurId,
"rev_timestamp < " . $dbw->addQuotes( $emit ) ],
224 [
'ORDER BY' =>
'rev_timestamp DESC' ]
227 $lastOldId = intval( $row->rev_id );
228 # Grab the last text size if available
229 $lastSize = !is_null( $row->rev_len ) ? intval( $row->rev_len ) :
null;
238 if ( $lastCurId == 0 ) {
239 $this->
output(
"Uhhh, something wrong? No curid\n" );
241 # Grab the entry's text size
242 $size = (int)$dbw->selectField(
245 [
'rev_id' => $obj->rc_this_oldid ],
252 'rc_last_oldid' => $lastOldId,
256 'rc_old_len' => $lastSize,
257 'rc_new_len' => $size,
260 'rc_cur_id' => $lastCurId,
261 'rc_this_oldid' => $obj->rc_this_oldid,
262 'rc_timestamp' => $obj->rc_timestamp
267 $lastOldId = intval( $obj->rc_this_oldid );
286 $this->
output(
"Loading from user, page, and logging tables...\n" );
288 $commentQuery = $commentStore->getJoin(
'log_comment' );
291 [
'logging',
'page' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
302 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
304 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
305 'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
311 [
'ORDER BY' =>
'log_timestamp DESC' ],
314 [
'LEFT JOIN', [
'log_namespace=page_namespace',
'log_title=page_title' ] ]
315 ] + $commentQuery[
'joins'] + $actorQuery[
'joins']
318 $field = $dbw->fieldInfo(
'recentchanges',
'rc_cur_id' );
322 foreach (
$res as $row ) {
323 $comment = $commentStore->getComment(
'log_comment', $row );
328 'rc_timestamp' => $row->log_timestamp,
329 'rc_namespace' => $row->log_namespace,
330 'rc_title' => $row->log_title,
333 'rc_patrolled' => $row->log_type ==
'upload' ? 0 : 2,
335 'rc_this_oldid' => 0,
336 'rc_last_oldid' => 0,
339 'rc_cur_id' => $field->isNullable()
341 : (
int)$row->page_id,
342 'rc_log_type' => $row->log_type,
343 'rc_log_action' => $row->log_action,
344 'rc_logid' => $row->log_id,
345 'rc_params' => $row->log_params,
346 'rc_deleted' => $row->log_deleted
347 ] + $commentStore->insert( $dbw,
'rc_comment', $comment )
348 + $actorMigration->getInsertValues( $dbw,
'rc_user',
$user ),
352 $rcid = $dbw->insertId();
355 [
'ct_rc_id' => $rcid ],
356 [
'ct_log_id' => $row->log_id ],
376 # @FIXME: recognize other bot account groups (not the same as users with 'bot' rights)
377 # @NOTE: users with 'bot' rights choose when edits are bot edits or not. That information
378 # may be lost at this point (aside from joining on the patrol log table entries).
379 $botgroups = [
'bot' ];
382 # Flag our recent bot edits
384 $this->
output(
"Flagging bot account edits...\n" );
386 # Find all users that are bots
388 array_merge( [
'user_groups' ], $userQuery[
'tables'] ),
389 $userQuery[
'fields'],
390 [
'ug_group' => $botgroups ],
393 [
'user_groups' => [
'JOIN',
'user_id = ug_user' ] ] + $userQuery[
'joins']
397 foreach (
$res as $obj ) {
401 # Fill in the rc_bot field
405 foreach ( $actorQuery[
'orconds']
as $cond ) {
406 $rcids = array_merge( $rcids, $dbw->selectFieldValues(
407 [
'recentchanges' ] + $actorQuery[
'tables'],
410 "rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
411 "rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
419 $rcids = array_values( array_unique( $rcids ) );
421 foreach ( array_chunk( $rcids, $this->
getBatchSize() )
as $rcidBatch ) {
425 [
'rc_id' => $rcidBatch ],
433 # Flag our recent autopatrolled edits
437 $this->
output(
"Flagging auto-patrolled edits...\n" );
439 # Find all users in RC with autopatrol rights
441 array_merge( [
'user_groups' ], $userQuery[
'tables'] ),
442 $userQuery[
'fields'],
443 [
'ug_group' => $autopatrolgroups ],
446 [
'user_groups' => [
'JOIN',
'user_id = ug_user' ] ] + $userQuery[
'joins']
449 foreach (
$res as $obj ) {
453 # Fill in the rc_patrolled field
454 if ( $patrolusers ) {
456 foreach ( $actorQuery[
'orconds']
as $cond ) {
459 [
'rc_patrolled' => 2 ],
462 'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
463 'rc_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
481 $this->
output(
"Removing duplicate revision and logging entries...\n" );
484 [
'logging',
'log_search' ],
485 [
'ls_value',
'ls_log_id' ],
487 'ls_log_id = log_id',
488 'ls_field' =>
'associated_rev_id',
489 'log_type' =>
'upload',
490 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
491 'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
497 foreach (
$res as $obj ) {
498 $rev_id = $obj->ls_value;
499 $log_id = $obj->ls_log_id;
504 [
'rc_this_oldid' => $rev_id ],
505 [
'rc_logid' => $log_id ],
512 [
'rc_this_oldid' => $rev_id,
'rc_logid' => 0 ],
528 $this->
output(
"Deleting feed timestamps.\n" );
530 $wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
532 $wanCache->delete( $wanCache->makeKey(
'rcfeed', $feed,
'timestamp' ) ); # Good enough
for now.