Completed
Branch master (efd8f8)
by
unknown
29:11
created

rebuildRecentChangesTablePass1()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 91
Code Lines 67

Duplication

Lines 4
Ratio 4.4 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 4
loc 91
rs 5.574
cc 8
eloc 67
nc 12
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 33 and the first side effect is on line 26.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Rebuild recent changes from scratch.  This takes several hours,
4
 * depending on the database size and server configuration.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along
17
 * with this program; if not, write to the Free Software Foundation, Inc.,
18
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 * http://www.gnu.org/copyleft/gpl.html
20
 *
21
 * @file
22
 * @ingroup Maintenance
23
 * @todo Document
24
 */
25
26
require_once __DIR__ . '/Maintenance.php';
27
28
/**
29
 * Maintenance script that rebuilds recent changes from scratch.
30
 *
31
 * @ingroup Maintenance
32
 */
33
class RebuildRecentchanges extends Maintenance {
34
	/** @var integer UNIX timestamp */
35
	private $cutoffFrom;
36
	/** @var integer UNIX timestamp */
37
	private $cutoffTo;
38
39 View Code Duplication
	public function __construct() {
40
		parent::__construct();
41
		$this->addDescription( 'Rebuild recent changes' );
42
43
		$this->addOption(
44
			'from',
45
			"Only rebuild rows in requested time range (in YYYYMMDDHHMMSS format)",
46
			false,
47
			true
48
		);
49
		$this->addOption(
50
			'to',
51
			"Only rebuild rows in requested time range (in YYYYMMDDHHMMSS format)",
52
			false,
53
			true
54
		);
55
		$this->setBatchSize( 200 );
56
	}
57
58
	public function execute() {
59
		if (
60
			( $this->hasOption( 'from' ) && !$this->hasOption( 'to' ) ) ||
61
			( !$this->hasOption( 'from' ) && $this->hasOption( 'to' ) )
62
		) {
63
			$this->error( "Both 'from' and 'to' must be given, or neither", 1 );
64
		}
65
66
		$this->rebuildRecentChangesTablePass1();
67
		$this->rebuildRecentChangesTablePass2();
68
		$this->rebuildRecentChangesTablePass3();
69
		$this->rebuildRecentChangesTablePass4();
70
		$this->rebuildRecentChangesTablePass5();
71
		if ( !( $this->hasOption( 'from' ) && $this->hasOption( 'to' ) ) ) {
72
			$this->purgeFeeds();
73
		}
74
		$this->output( "Done.\n" );
75
	}
76
77
	/**
78
	 * Rebuild pass 1: Insert `recentchanges` entries for page revisions.
79
	 */
80
	private function rebuildRecentChangesTablePass1() {
81
		$dbw = $this->getDB( DB_MASTER );
82
83
		if ( $this->hasOption( 'from' ) && $this->hasOption( 'to' ) ) {
84
			$this->cutoffFrom = wfTimestamp( TS_UNIX, $this->getOption( 'from' ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like wfTimestamp(TS_UNIX, $this->getOption('from')) of type string or false is incompatible with the declared type integer of property $cutoffFrom.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
85
			$this->cutoffTo = wfTimestamp( TS_UNIX, $this->getOption( 'to' ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like wfTimestamp(TS_UNIX, $this->getOption('to')) of type string or false is incompatible with the declared type integer of property $cutoffTo.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
86
87
			$sec = $this->cutoffTo - $this->cutoffFrom;
88
			$days = $sec / 24 / 3600;
89
			$this->output( "Rebuilding range of $sec seconds ($days days)\n" );
90
		} else {
91
			global $wgRCMaxAge;
92
93
			$days = $wgRCMaxAge / 24 / 3600;
94
			$this->output( "Rebuilding \$wgRCMaxAge=$wgRCMaxAge seconds ($days days)\n" );
95
96
			$this->cutoffFrom = time() - $wgRCMaxAge;
0 ignored issues
show
Documentation Bug introduced by
It seems like time() - $wgRCMaxAge can also be of type double. However, the property $cutoffFrom is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
97
			$this->cutoffTo = time();
98
		}
99
100
		$this->output( "Clearing recentchanges table for time range...\n" );
101
		$rcids = $dbw->selectFieldValues(
102
			'recentchanges',
103
			'rc_id',
104
			[
105
				'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
106
				'rc_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
107
			]
108
		);
109 View Code Duplication
		foreach ( array_chunk( $rcids, $this->mBatchSize ) as $rcidBatch ) {
110
			$dbw->delete( 'recentchanges', [ 'rc_id' => $rcidBatch ], __METHOD__ );
111
			wfGetLBFactory()->waitForReplication();
112
		}
113
114
		$this->output( "Loading from page and revision tables...\n" );
115
		$res = $dbw->select(
116
			[ 'page', 'revision' ],
117
			[
118
				'rev_timestamp',
119
				'rev_user',
120
				'rev_user_text',
121
				'rev_comment',
122
				'rev_minor_edit',
123
				'rev_id',
124
				'rev_deleted',
125
				'page_namespace',
126
				'page_title',
127
				'page_is_new',
128
				'page_id'
129
			],
130
			[
131
				'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
132
				'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
133
				'rev_page=page_id'
134
			],
135
			__METHOD__,
136
			[ 'ORDER BY' => 'rev_timestamp DESC' ]
137
		);
138
139
		$this->output( "Inserting from page and revision tables...\n" );
140
		$inserted = 0;
141
		foreach ( $res as $row ) {
0 ignored issues
show
Bug introduced by
The expression $res of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
142
			$dbw->insert(
143
				'recentchanges',
144
				[
145
					'rc_timestamp' => $row->rev_timestamp,
146
					'rc_user' => $row->rev_user,
147
					'rc_user_text' => $row->rev_user_text,
148
					'rc_namespace' => $row->page_namespace,
149
					'rc_title' => $row->page_title,
150
					'rc_comment' => $row->rev_comment,
151
					'rc_minor' => $row->rev_minor_edit,
152
					'rc_bot' => 0,
153
					'rc_new' => $row->page_is_new,
154
					'rc_cur_id' => $row->page_id,
155
					'rc_this_oldid' => $row->rev_id,
156
					'rc_last_oldid' => 0, // is this ok?
157
					'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT,
158
					'rc_source' => $row->page_is_new
159
						? $dbw->addQuotes( RecentChange::SRC_NEW )
160
						: $dbw->addQuotes( RecentChange::SRC_EDIT )
161
					,
162
					'rc_deleted' => $row->rev_deleted
163
				],
164
				__METHOD__
165
			);
166
			if ( ( ++$inserted % $this->mBatchSize ) == 0 ) {
167
				wfGetLBFactory()->waitForReplication();
168
			}
169
		}
170
	}
171
172
	/**
173
	 * Rebuild pass 2: Enhance entries for page revisions with references to the previous revision
174
	 * (rc_last_oldid, rc_new etc.) and size differences (rc_old_len, rc_new_len).
175
	 */
176
	private function rebuildRecentChangesTablePass2() {
177
		$dbw = $this->getDB( DB_MASTER );
178
179
		$this->output( "Updating links and size differences...\n" );
180
181
		# Fill in the rc_last_oldid field, which points to the previous edit
182
		$res = $dbw->select(
183
			'recentchanges',
184
			[ 'rc_cur_id', 'rc_this_oldid', 'rc_timestamp' ],
185
			[
186
				"rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
187
				"rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
188
			],
189
			__METHOD__,
190
			[ 'ORDER BY' => 'rc_cur_id,rc_timestamp' ]
191
		);
192
193
		$lastCurId = 0;
194
		$lastOldId = 0;
195
		$lastSize = null;
196
		$updated = 0;
197
		foreach ( $res as $obj ) {
0 ignored issues
show
Bug introduced by
The expression $res of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
198
			$new = 0;
199
200
			if ( $obj->rc_cur_id != $lastCurId ) {
201
				# Switch! Look up the previous last edit, if any
202
				$lastCurId = intval( $obj->rc_cur_id );
203
				$emit = $obj->rc_timestamp;
204
205
				$row = $dbw->selectRow(
206
					'revision',
207
					[ 'rev_id', 'rev_len' ],
208
					[ 'rev_page' => $lastCurId, "rev_timestamp < " . $dbw->addQuotes( $emit ) ],
209
					__METHOD__,
210
					[ 'ORDER BY' => 'rev_timestamp DESC' ]
211
				);
212
				if ( $row ) {
213
					$lastOldId = intval( $row->rev_id );
214
					# Grab the last text size if available
215
					$lastSize = !is_null( $row->rev_len ) ? intval( $row->rev_len ) : null;
216
				} else {
217
					# No previous edit
218
					$lastOldId = 0;
219
					$lastSize = null;
220
					$new = 1; // probably true
221
				}
222
			}
223
224
			if ( $lastCurId == 0 ) {
225
				$this->output( "Uhhh, something wrong? No curid\n" );
226
			} else {
227
				# Grab the entry's text size
228
				$size = (int)$dbw->selectField(
229
					'revision',
230
					'rev_len',
231
					[ 'rev_id' => $obj->rc_this_oldid ],
232
					__METHOD__
233
				);
234
235
				$dbw->update(
236
					'recentchanges',
237
					[
238
						'rc_last_oldid' => $lastOldId,
239
						'rc_new' => $new,
240
						'rc_type' => $new ? RC_NEW : RC_EDIT,
241
						'rc_source' => $new === 1
242
							? $dbw->addQuotes( RecentChange::SRC_NEW )
243
							: $dbw->addQuotes( RecentChange::SRC_EDIT ),
244
						'rc_old_len' => $lastSize,
245
						'rc_new_len' => $size,
246
					],
247
					[
248
						'rc_cur_id' => $lastCurId,
249
						'rc_this_oldid' => $obj->rc_this_oldid,
250
						'rc_timestamp' => $obj->rc_timestamp // index usage
251
					],
252
					__METHOD__
253
				);
254
255
				$lastOldId = intval( $obj->rc_this_oldid );
256
				$lastSize = $size;
257
258
				if ( ( ++$updated % $this->mBatchSize ) == 0 ) {
259
					wfGetLBFactory()->waitForReplication();
260
				}
261
			}
262
		}
263
	}
264
265
	/**
266
	 * Rebuild pass 3: Insert `recentchanges` entries for action logs.
267
	 */
268
	private function rebuildRecentChangesTablePass3() {
269
		global $wgLogTypes, $wgLogRestrictions;
270
271
		$dbw = $this->getDB( DB_MASTER );
272
273
		$this->output( "Loading from user, page, and logging tables...\n" );
274
275
		$res = $dbw->select(
276
			[ 'user', 'logging', 'page' ],
277
			[
278
				'log_timestamp',
279
				'log_user',
280
				'user_name',
281
				'log_namespace',
282
				'log_title',
283
				'log_comment',
284
				'page_id',
285
				'log_type',
286
				'log_action',
287
				'log_id',
288
				'log_params',
289
				'log_deleted'
290
			],
291
			[
292
				'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
293
				'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
294
				'log_user=user_id',
295
				// Some logs don't go in RC since they are private.
296
				// @FIXME: core/extensions also have spammy logs that don't go in RC.
297
				'log_type' => array_diff( $wgLogTypes, array_keys( $wgLogRestrictions ) ),
298
			],
299
			__METHOD__,
300
			[ 'ORDER BY' => 'log_timestamp DESC' ],
301
			[
302
				'page' =>
303
					[ 'LEFT JOIN', [ 'log_namespace=page_namespace', 'log_title=page_title' ] ]
304
			]
305
		);
306
307
		$inserted = 0;
308
		foreach ( $res as $row ) {
0 ignored issues
show
Bug introduced by
The expression $res of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
309
			$dbw->insert(
310
				'recentchanges',
311
				[
312
					'rc_timestamp' => $row->log_timestamp,
313
					'rc_user' => $row->log_user,
314
					'rc_user_text' => $row->user_name,
315
					'rc_namespace' => $row->log_namespace,
316
					'rc_title' => $row->log_title,
317
					'rc_comment' => $row->log_comment,
318
					'rc_minor' => 0,
319
					'rc_bot' => 0,
320
					'rc_patrolled' => 1,
321
					'rc_new' => 0,
322
					'rc_this_oldid' => 0,
323
					'rc_last_oldid' => 0,
324
					'rc_type' => RC_LOG,
325
					'rc_source' => $dbw->addQuotes( RecentChange::SRC_LOG ),
326
					'rc_cur_id' => $dbw->cascadingDeletes()
0 ignored issues
show
Bug introduced by
The method cascadingDeletes() does not exist on IDatabase. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
327
						? $row->page_id
328
						: (int)$row->page_id, // NULL => 0,
329
					'rc_log_type' => $row->log_type,
330
					'rc_log_action' => $row->log_action,
331
					'rc_logid' => $row->log_id,
332
					'rc_params' => $row->log_params,
333
					'rc_deleted' => $row->log_deleted
334
				],
335
				__METHOD__
336
			);
337
338
			if ( ( ++$inserted % $this->mBatchSize ) == 0 ) {
339
				wfGetLBFactory()->waitForReplication();
340
			}
341
		}
342
	}
343
344
	/**
345
	 * Rebuild pass 4: Mark bot and autopatrolled entries.
346
	 */
347
	private function rebuildRecentChangesTablePass4() {
348
		global $wgUseRCPatrol, $wgMiserMode;
349
350
		$dbw = $this->getDB( DB_MASTER );
351
352
		list( $recentchanges, $usergroups, $user ) =
353
			$dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface IDatabase as the method tableNamesN() does only exist in the following implementations of said interface: Database, DatabaseBase, DatabaseMssql, DatabaseMysql, DatabaseMysqlBase, DatabaseMysqli, DatabaseOracle, DatabasePostgres, DatabaseSqlite.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
354
355
		# @FIXME: recognize other bot account groups (not the same as users with 'bot' rights)
356
		# @NOTE: users with 'bot' rights choose when edits are bot edits or not. That information
357
		# may be lost at this point (aside from joining on the patrol log table entries).
358
		$botgroups = [ 'bot' ];
359
		$autopatrolgroups = $wgUseRCPatrol ? User::getGroupsWithPermission( 'autopatrol' ) : [];
360
361
		# Flag our recent bot edits
362
		if ( $botgroups ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $botgroups of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
363
			$botwhere = $dbw->makeList( $botgroups );
364
365
			$this->output( "Flagging bot account edits...\n" );
366
367
			# Find all users that are bots
368
			$sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
369
				"WHERE ug_group IN($botwhere) AND user_id = ug_user";
370
			$res = $dbw->query( $sql, __METHOD__ );
371
372
			$botusers = [];
373
			foreach ( $res as $obj ) {
0 ignored issues
show
Bug introduced by
The expression $res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
374
				$botusers[] = $obj->user_name;
375
			}
376
377
			# Fill in the rc_bot field
378
			if ( $botusers ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $botusers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
379
				$rcids = $dbw->selectFieldValues(
380
					'recentchanges',
381
					'rc_id',
382
					[
383
						'rc_user_text' => $botusers,
384
						"rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
385
						"rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
386
					],
387
					__METHOD__
388
				);
389
390 View Code Duplication
				foreach ( array_chunk( $rcids, $this->mBatchSize ) as $rcidBatch ) {
391
					$dbw->update(
392
						'recentchanges',
393
						[ 'rc_bot' => 1 ],
394
						[ 'rc_id' => $rcidBatch ],
395
						__METHOD__
396
					);
397
					wfGetLBFactory()->waitForReplication();
398
				}
399
			}
400
		}
401
402
		# Flag our recent autopatrolled edits
403
		if ( !$wgMiserMode && $autopatrolgroups ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $autopatrolgroups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
404
			$patrolwhere = $dbw->makeList( $autopatrolgroups );
405
			$patrolusers = [];
406
407
			$this->output( "Flagging auto-patrolled edits...\n" );
408
409
			# Find all users in RC with autopatrol rights
410
			$sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
411
				"WHERE ug_group IN($patrolwhere) AND user_id = ug_user";
412
			$res = $dbw->query( $sql, __METHOD__ );
413
414
			foreach ( $res as $obj ) {
0 ignored issues
show
Bug introduced by
The expression $res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
415
				$patrolusers[] = $dbw->addQuotes( $obj->user_name );
416
			}
417
418
			# Fill in the rc_patrolled field
419
			if ( $patrolusers ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $patrolusers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
420
				$patrolwhere = implode( ',', $patrolusers );
421
				$sql2 = "UPDATE $recentchanges SET rc_patrolled=1 " .
422
					"WHERE rc_user_text IN($patrolwhere) " .
423
					"AND rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ) . ' ' .
424
					"AND rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) );
425
				$dbw->query( $sql2 );
426
			}
427
		}
428
	}
429
430
	/**
431
	 * Rebuild pass 5: Delete duplicate entries where we generate both a page revision and a log entry
432
	 * for a single action (upload only, at the moment, but potentially also move, protect, ...).
433
	 */
434
	private function rebuildRecentChangesTablePass5() {
435
		$dbw = wfGetDB( DB_MASTER );
436
437
		$this->output( "Removing duplicate revision and logging entries...\n" );
438
439
		$res = $dbw->select(
440
			[ 'logging', 'log_search' ],
441
			[ 'ls_value', 'ls_log_id' ],
442
			[
443
				'ls_log_id = log_id',
444
				'ls_field' => 'associated_rev_id',
445
				'log_type' => 'upload',
446
				'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
0 ignored issues
show
Security Bug introduced by
It seems like $dbw->timestamp($this->cutoffFrom) targeting DatabaseBase::timestamp() can also be of type false; however, DatabaseBase::addQuotes() does only seem to accept string|object<Blob>, did you maybe forget to handle an error condition?
Loading history...
447
				'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
0 ignored issues
show
Security Bug introduced by
It seems like $dbw->timestamp($this->cutoffTo) targeting DatabaseBase::timestamp() can also be of type false; however, DatabaseBase::addQuotes() does only seem to accept string|object<Blob>, did you maybe forget to handle an error condition?
Loading history...
448
			],
449
			__METHOD__
450
		);
451
452
		$updates = 0;
453
		foreach ( $res as $obj ) {
0 ignored issues
show
Bug introduced by
The expression $res of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
454
			$rev_id = $obj->ls_value;
455
			$log_id = $obj->ls_log_id;
456
457
			// Mark the logging row as having an associated rev id
458
			$dbw->update(
459
				'recentchanges',
460
				/*SET*/ [ 'rc_this_oldid' => $rev_id ],
461
				/*WHERE*/ [ 'rc_logid' => $log_id ],
462
				__METHOD__
463
			);
464
465
			// Delete the revision row
466
			$dbw->delete(
467
				'recentchanges',
468
				/*WHERE*/ [ 'rc_this_oldid' => $rev_id, 'rc_logid' => 0 ],
469
				__METHOD__
470
			);
471
472
			if ( ( ++$updates % $this->mBatchSize ) == 0 ) {
473
				wfGetLBFactory()->waitForReplication();
474
			}
475
		}
476
	}
477
478
	/**
479
	 * Purge cached feeds in $messageMemc
480
	 */
481
	private function purgeFeeds() {
482
		global $wgFeedClasses, $messageMemc;
483
484
		$this->output( "Deleting feed timestamps.\n" );
485
486
		foreach ( $wgFeedClasses as $feed => $className ) {
487
			$messageMemc->delete( wfMemcKey( 'rcfeed', $feed, 'timestamp' ) ); # Good enough for now.
488
		}
489
	}
490
}
491
492
$maintClass = "RebuildRecentchanges";
493
require_once RUN_MAINTENANCE_IF_MAIN;
494