26 require_once __DIR__ .
'/Maintenance.php';
45 parent::__construct();
50 "Only rebuild rows in requested time range (in YYYYMMDDHHMMSS format)",
56 "Only rebuild rows in requested time range (in YYYYMMDDHHMMSS format)",
68 $this->
fatalError(
"Both 'from' and 'to' must be given, or neither" );
71 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
72 $this->rebuildRecentChangesTablePass1( $lbFactory );
73 $this->rebuildRecentChangesTablePass2( $lbFactory );
74 $this->rebuildRecentChangesTablePass3( $lbFactory );
75 $this->rebuildRecentChangesTablePass4( $lbFactory );
76 $this->rebuildRecentChangesTablePass5( $lbFactory );
80 $this->
output(
"Done.\n" );
88 private function rebuildRecentChangesTablePass1(
ILBFactory $lbFactory ) {
90 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
96 $sec = $this->cutoffTo - $this->cutoffFrom;
97 $days = $sec / 24 / 3600;
98 $this->
output(
"Rebuilding range of $sec seconds ($days days)\n" );
103 $this->
output(
"Rebuilding \$wgRCMaxAge=$wgRCMaxAge seconds ($days days)\n" );
106 $this->cutoffTo = time();
109 $this->
output(
"Clearing recentchanges table for time range...\n" );
110 $rcids = $dbw->selectFieldValues(
114 'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
115 'rc_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
119 foreach ( array_chunk( $rcids, $this->
getBatchSize() ) as $rcidBatch ) {
120 $dbw->delete(
'recentchanges', [
'rc_id' => $rcidBatch ], __METHOD__ );
124 $this->
output(
"Loading from page and revision tables...\n" );
126 $commentQuery = $commentStore->getJoin(
'rev_comment' );
127 $actorQuery = ActorMigration::newMigration()->getJoin(
'rev_user' );
129 [
'revision',
'page' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
139 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
141 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
142 'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
145 [
'ORDER BY' =>
'rev_timestamp DESC' ],
147 'page' => [
'JOIN',
'rev_page=page_id' ],
148 ] + $commentQuery[
'joins'] + $actorQuery[
'joins']
151 $this->
output(
"Inserting from page and revision tables...\n" );
153 foreach (
$res as $row ) {
154 $comment = $commentStore->getComment(
'rev_comment', $row );
158 'rc_timestamp' => $row->rev_timestamp,
159 'rc_actor' => $row->rev_actor,
160 'rc_namespace' => $row->page_namespace,
161 'rc_title' => $row->page_title,
162 'rc_minor' => $row->rev_minor_edit,
164 'rc_new' => $row->page_is_new,
165 'rc_cur_id' => $row->page_id,
166 'rc_this_oldid' => $row->rev_id,
167 'rc_last_oldid' => 0,
170 'rc_deleted' => $row->rev_deleted
171 ] + $commentStore->insert( $dbw,
'rc_comment', $comment ),
175 $rcid = $dbw->insertId();
178 [
'ct_rc_id' => $rcid ],
179 [
'ct_rev_id' => $row->rev_id ],
195 private function rebuildRecentChangesTablePass2(
ILBFactory $lbFactory ) {
198 $this->
output(
"Updating links and size differences...\n" );
200 # Fill in the rc_last_oldid field, which points to the previous edit
203 [
'rc_cur_id',
'rc_this_oldid',
'rc_timestamp' ],
205 "rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
206 "rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
209 [
'ORDER BY' => [
'rc_cur_id',
'rc_timestamp' ] ]
216 foreach (
$res as $row ) {
219 if ( $row->rc_cur_id != $lastCurId ) {
220 # Switch! Look up the previous last edit, if any
221 $lastCurId = intval( $row->rc_cur_id );
222 $emit = $row->rc_timestamp;
224 $revRow = $dbw->selectRow(
226 [
'rev_id',
'rev_len' ],
227 [
'rev_page' => $lastCurId,
"rev_timestamp < " . $dbw->addQuotes( $emit ) ],
229 [
'ORDER BY' =>
'rev_timestamp DESC' ]
232 $lastOldId = intval( $revRow->rev_id );
233 # Grab the last text size if available
234 $lastSize = $revRow->rev_len !==
null ? intval( $revRow->rev_len ) : null;
243 if ( $lastCurId == 0 ) {
244 $this->
output(
"Uhhh, something wrong? No curid\n" );
246 # Grab the entry's text size
247 $size = (int)$dbw->selectField(
250 [
'rev_id' => $row->rc_this_oldid ],
257 'rc_last_oldid' => $lastOldId,
261 'rc_old_len' => $lastSize,
262 'rc_new_len' => $size,
265 'rc_cur_id' => $lastCurId,
266 'rc_this_oldid' => $row->rc_this_oldid,
267 'rc_timestamp' => $row->rc_timestamp
272 $lastOldId = intval( $row->rc_this_oldid );
287 private function rebuildRecentChangesTablePass3(
ILBFactory $lbFactory ) {
291 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
296 $this->
output(
"Loading from user and logging tables...\n" );
298 $commentQuery = $commentStore->getJoin(
'log_comment' );
300 [
'logging' ] + $commentQuery[
'tables'],
312 ] + $commentQuery[
'fields'],
314 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
315 'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
320 [
'ORDER BY' => [
'log_timestamp DESC',
'log_id DESC' ] ],
321 $commentQuery[
'joins']
324 $field = $dbw->fieldInfo(
'recentchanges',
'rc_cur_id' );
327 foreach (
$res as $row ) {
328 $comment = $commentStore->getComment(
'log_comment', $row );
332 'rc_timestamp' => $row->log_timestamp,
333 'rc_actor' => $row->log_actor,
334 'rc_namespace' => $row->log_namespace,
335 'rc_title' => $row->log_title,
338 'rc_patrolled' => $row->log_type ==
'upload' ? 0 : 2,
340 'rc_this_oldid' => 0,
341 'rc_last_oldid' => 0,
344 'rc_cur_id' => $field->isNullable()
346 : (
int)$row->log_page,
347 'rc_log_type' => $row->log_type,
348 'rc_log_action' => $row->log_action,
349 'rc_logid' => $row->log_id,
350 'rc_params' => $row->log_params,
351 'rc_deleted' => $row->log_deleted
352 ] + $commentStore->insert( $dbw,
'rc_comment', $comment ),
356 $rcid = $dbw->insertId();
359 [
'ct_rc_id' => $rcid ],
360 [
'ct_log_id' => $row->log_id ],
378 private function findRcIdsWithGroups( $db, $groups, $conds = [] ) {
379 if ( !count( $groups ) ) {
382 return $db->selectFieldValues(
383 [
'recentchanges',
'actor',
'user_groups' ],
386 "rc_timestamp > " . $db->addQuotes( $db->timestamp( $this->cutoffFrom ) ),
387 "rc_timestamp < " . $db->addQuotes( $db->timestamp( $this->cutoffTo ) ),
388 'ug_group' => $groups
393 'actor' => [
'JOIN',
'actor_id=rc_actor' ],
394 'user_groups' => [
'JOIN',
'ug_user=actor_user' ]
404 private function rebuildRecentChangesTablePass4(
ILBFactory $lbFactory ) {
409 # @FIXME: recognize other bot account groups (not the same as users with 'bot' rights)
410 # @NOTE: users with 'bot' rights choose when edits are bot edits or not. That information
411 # may be lost at this point (aside from joining on the patrol log table entries).
412 $botgroups = [
'bot' ];
414 MediaWikiServices::getInstance()->getGroupPermissionsLookup()
415 ->getGroupsWithPermission(
'autopatrol' ) : [];
417 # Flag our recent bot edits
420 $this->
output(
"Flagging bot account edits...\n" );
422 # Fill in the rc_bot field
423 $rcids = $this->findRcIdsWithGroups( $dbw, $botgroups );
425 foreach ( array_chunk( $rcids, $this->
getBatchSize() ) as $rcidBatch ) {
429 [
'rc_id' => $rcidBatch ],
436 # Flag our recent autopatrolled edits
437 if ( !$wgMiserMode && $autopatrolgroups ) {
438 $this->
output(
"Flagging auto-patrolled edits...\n" );
440 $conds = [
'rc_patrolled' => 0 ];
447 $subConds[] =
'rc_log_type = ' . $dbw->addQuotes(
'upload' );
452 $rcids = $this->findRcIdsWithGroups( $dbw, $autopatrolgroups, $conds );
453 foreach ( array_chunk( $rcids, $this->
getBatchSize() ) as $rcidBatch ) {
456 [
'rc_patrolled' => 2 ],
457 [
'rc_id' => $rcidBatch ],
471 private function rebuildRecentChangesTablePass5(
ILBFactory $lbFactory ) {
474 $this->
output(
"Removing duplicate revision and logging entries...\n" );
477 [
'logging',
'log_search' ],
478 [
'ls_value',
'ls_log_id' ],
480 'ls_log_id = log_id',
481 'ls_field' =>
'associated_rev_id',
482 'log_type != ' . $dbw->addQuotes(
'create' ),
483 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
484 'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
490 foreach (
$res as $row ) {
491 $rev_id = $row->ls_value;
492 $log_id = $row->ls_log_id;
497 [
'rc_this_oldid' => $rev_id ],
498 [
'rc_logid' => $log_id ],
505 [
'rc_this_oldid' => $rev_id,
'rc_logid' => 0 ],
518 private function purgeFeeds() {
521 $this->
output(
"Deleting feed timestamps.\n" );
523 $wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
525 $wanCache->delete( $wanCache->makeKey(
'rcfeed', $feed,
'timestamp' ) ); # Good enough
for now.
531 require_once RUN_MAINTENANCE_IF_MAIN;
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static validTypes()
Get the list of valid log types.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
output( $out, $channel=null)
Throw some output to the user.
waitForReplication()
Wait for replica DBs to catch up.
hasOption( $name)
Checks to see if a particular option was set.
getBatchSize()
Returns batch size.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
Maintenance script that rebuilds recent changes from scratch.
execute()
Do the actual work.
__construct()
Default constructor.
$wgUseFilePatrol
Config variable stub for the UseFilePatrol setting, for use by phpdoc and IDEs.
$wgLogRestrictions
Config variable stub for the LogRestrictions setting, for use by phpdoc and IDEs.
$wgUseRCPatrol
Config variable stub for the UseRCPatrol setting, for use by phpdoc and IDEs.
$wgUseNPPatrol
Config variable stub for the UseNPPatrol setting, for use by phpdoc and IDEs.
$wgRCMaxAge
Config variable stub for the RCMaxAge setting, for use by phpdoc and IDEs.
$wgFeedClasses
Config variable stub for the FeedClasses setting, for use by phpdoc and IDEs.
$wgFilterLogTypes
Config variable stub for the FilterLogTypes setting, for use by phpdoc and IDEs.
$wgMiserMode
Config variable stub for the MiserMode setting, for use by phpdoc and IDEs.