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 $this->rebuildRecentChangesTablePass1();
70 $this->rebuildRecentChangesTablePass2();
71 $this->rebuildRecentChangesTablePass3();
72 $this->rebuildRecentChangesTablePass4();
73 $this->rebuildRecentChangesTablePass5();
77 $this->
output(
"Done.\n" );
83 private function rebuildRecentChangesTablePass1() {
91 $sec = $this->cutoffTo - $this->cutoffFrom;
92 $days = $sec / 24 / 3600;
93 $this->
output(
"Rebuilding range of $sec seconds ($days days)\n" );
98 $this->
output(
"Rebuilding \$wgRCMaxAge=$wgRCMaxAge seconds ($days days)\n" );
101 $this->cutoffTo = time();
104 $this->
output(
"Clearing recentchanges table for time range...\n" );
105 $rcids = $dbw->newSelectQueryBuilder()
107 ->from(
'recentchanges' )
108 ->where( [
'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ) ] )
109 ->andWhere( [
'rc_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ) ] )
110 ->caller( __METHOD__ )->fetchFieldValues();
111 foreach ( array_chunk( $rcids, $this->
getBatchSize() ) as $rcidBatch ) {
112 $dbw->delete(
'recentchanges', [
'rc_id' => $rcidBatch ], __METHOD__ );
116 $this->
output(
"Loading from page and revision tables...\n" );
118 $commentQuery = $commentStore->getJoin(
'rev_comment' );
119 $actorQuery = ActorMigration::newMigration()->getJoin(
'rev_user' );
121 [
'revision',
'page' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
131 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
133 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
134 'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
137 [
'ORDER BY' =>
'rev_timestamp DESC' ],
139 'page' => [
'JOIN',
'rev_page=page_id' ],
140 ] + $commentQuery[
'joins'] + $actorQuery[
'joins']
143 $this->
output(
"Inserting from page and revision tables...\n" );
145 foreach ( $res as $row ) {
146 $comment = $commentStore->getComment(
'rev_comment', $row );
150 'rc_timestamp' => $row->rev_timestamp,
151 'rc_actor' => $row->rev_actor,
152 'rc_namespace' => $row->page_namespace,
153 'rc_title' => $row->page_title,
154 'rc_minor' => $row->rev_minor_edit,
156 'rc_new' => $row->page_is_new,
157 'rc_cur_id' => $row->page_id,
158 'rc_this_oldid' => $row->rev_id,
159 'rc_last_oldid' => 0,
162 'rc_deleted' => $row->rev_deleted
163 ] + $commentStore->insert( $dbw,
'rc_comment', $comment ),
167 $rcid = $dbw->insertId();
170 [
'ct_rc_id' => $rcid ],
171 [
'ct_rev_id' => $row->rev_id ],
185 private function rebuildRecentChangesTablePass2() {
188 $this->
output(
"Updating links and size differences...\n" );
190 # Fill in the rc_last_oldid field, which points to the previous edit
191 $res = $dbw->newSelectQueryBuilder()
192 ->select( [
'rc_cur_id',
'rc_this_oldid',
'rc_timestamp' ] )
193 ->from(
'recentchanges' )
194 ->where( [
"rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ) ] )
195 ->andWhere( [
"rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ) ] )
196 ->orderBy( [
'rc_cur_id',
'rc_timestamp' ] )
197 ->caller( __METHOD__ )->fetchResultSet();
203 foreach ( $res as $row ) {
206 if ( $row->rc_cur_id != $lastCurId ) {
207 # Switch! Look up the previous last edit, if any
208 $lastCurId = intval( $row->rc_cur_id );
209 $emit = $row->rc_timestamp;
211 $revRow = $dbw->newSelectQueryBuilder()
212 ->select( [
'rev_id',
'rev_len' ] )
214 ->where( [
'rev_page' => $lastCurId,
"rev_timestamp < " . $dbw->addQuotes( $emit ) ] )
215 ->orderBy(
'rev_timestamp DESC' )
216 ->caller( __METHOD__ )->fetchRow();
218 $lastOldId = intval( $revRow->rev_id );
219 # Grab the last text size if available
220 $lastSize = $revRow->rev_len !==
null ? intval( $revRow->rev_len ) : null;
229 if ( $lastCurId == 0 ) {
230 $this->
output(
"Uhhh, something wrong? No curid\n" );
232 # Grab the entry's text size
233 $size = (int)$dbw->newSelectQueryBuilder()
234 ->select(
'rev_len' )
236 ->where( [
'rev_id' => $row->rc_this_oldid ] )
237 ->caller( __METHOD__ )->fetchField();
242 'rc_last_oldid' => $lastOldId,
246 'rc_old_len' => $lastSize,
247 'rc_new_len' => $size,
250 'rc_cur_id' => $lastCurId,
251 'rc_this_oldid' => $row->rc_this_oldid,
252 'rc_timestamp' => $row->rc_timestamp
257 $lastOldId = intval( $row->rc_this_oldid );
270 private function rebuildRecentChangesTablePass3() {
279 $this->
output(
"Loading from user and logging tables...\n" );
281 $commentQuery = $commentStore->getJoin(
'log_comment' );
283 [
'logging' ] + $commentQuery[
'tables'],
295 ] + $commentQuery[
'fields'],
297 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
298 'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
303 [
'ORDER BY' => [
'log_timestamp DESC',
'log_id DESC' ] ],
304 $commentQuery[
'joins']
307 $field = $dbw->fieldInfo(
'recentchanges',
'rc_cur_id' );
310 foreach ( $res as $row ) {
311 $comment = $commentStore->getComment(
'log_comment', $row );
315 'rc_timestamp' => $row->log_timestamp,
316 'rc_actor' => $row->log_actor,
317 'rc_namespace' => $row->log_namespace,
318 'rc_title' => $row->log_title,
321 'rc_patrolled' => $row->log_type ==
'upload' ? 0 : 2,
323 'rc_this_oldid' => 0,
324 'rc_last_oldid' => 0,
327 'rc_cur_id' => $field->isNullable()
329 : (
int)$row->log_page,
330 'rc_log_type' => $row->log_type,
331 'rc_log_action' => $row->log_action,
332 'rc_logid' => $row->log_id,
333 'rc_params' => $row->log_params,
334 'rc_deleted' => $row->log_deleted
335 ] + $commentStore->insert( $dbw,
'rc_comment', $comment ),
339 $rcid = $dbw->insertId();
342 [
'ct_rc_id' => $rcid ],
343 [
'ct_log_id' => $row->log_id ],
361 private function findRcIdsWithGroups( $db, $groups, $conds = [] ) {
362 if ( !count( $groups ) ) {
365 return $db->newSelectQueryBuilder()
368 ->from(
'recentchanges' )
369 ->join(
'actor',
null,
'actor_id=rc_actor' )
370 ->join(
'user_groups',
null,
'ug_user=actor_user' )
373 "rc_timestamp > " . $db->addQuotes( $db->timestamp( $this->cutoffFrom ) ),
374 "rc_timestamp < " . $db->addQuotes( $db->timestamp( $this->cutoffTo ) ),
375 'ug_group' => $groups
377 ->caller( __METHOD__ )->fetchFieldValues();
383 private function rebuildRecentChangesTablePass4() {
388 # @FIXME: recognize other bot account groups (not the same as users with 'bot' rights)
389 # @NOTE: users with 'bot' rights choose when edits are bot edits or not. That information
390 # may be lost at this point (aside from joining on the patrol log table entries).
391 $botgroups = [
'bot' ];
394 ->getGroupsWithPermission(
'autopatrol' ) : [];
396 # Flag our recent bot edits
399 $this->
output(
"Flagging bot account edits...\n" );
401 # Fill in the rc_bot field
402 $rcids = $this->findRcIdsWithGroups( $dbw, $botgroups );
404 foreach ( array_chunk( $rcids, $this->
getBatchSize() ) as $rcidBatch ) {
408 [
'rc_id' => $rcidBatch ],
415 # Flag our recent autopatrolled edits
416 if ( !$wgMiserMode && $autopatrolgroups ) {
417 $this->
output(
"Flagging auto-patrolled edits...\n" );
419 $conds = [
'rc_patrolled' => 0 ];
426 $subConds[] =
'rc_log_type = ' . $dbw->addQuotes(
'upload' );
431 $rcids = $this->findRcIdsWithGroups( $dbw, $autopatrolgroups, $conds );
432 foreach ( array_chunk( $rcids, $this->
getBatchSize() ) as $rcidBatch ) {
435 [
'rc_patrolled' => 2 ],
436 [
'rc_id' => $rcidBatch ],
448 private function rebuildRecentChangesTablePass5() {
451 $this->
output(
"Removing duplicate revision and logging entries...\n" );
453 $res = $dbw->newSelectQueryBuilder()
454 ->select( [
'ls_value',
'ls_log_id' ] )
456 ->join(
'log_search',
null,
'ls_log_id = log_id' )
458 'ls_field' =>
'associated_rev_id',
459 'log_type != ' . $dbw->addQuotes(
'create' ),
460 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
461 'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
463 ->caller( __METHOD__ )->fetchResultSet();
466 foreach ( $res as $row ) {
467 $rev_id = $row->ls_value;
468 $log_id = $row->ls_log_id;
473 [
'rc_this_oldid' => $rev_id ],
474 [
'rc_logid' => $log_id ],
481 [
'rc_this_oldid' => $rev_id,
'rc_logid' => 0 ],
494 private function purgeFeeds() {
497 $this->
output(
"Deleting feed timestamps.\n" );
500 foreach ( $wgFeedClasses as $feed => $className ) {
501 $wanCache->delete( $wanCache->makeKey(
'rcfeed', $feed,
'timestamp' ) ); # Good enough
for now.
507 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.
getServiceContainer()
Returns the main service container.
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.