Completed
Branch master (62f6c6)
by
unknown
21:31
created

EnhancedChangesList::recentChangesBlockGroup()   F

Complexity

Conditions 34
Paths > 20000

Size

Total Lines 185
Code Lines 118

Duplication

Lines 24
Ratio 12.97 %

Importance

Changes 0
Metric Value
cc 34
eloc 118
nc 107108
nop 1
dl 24
loc 185
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
2
/**
3
 * Generates a list of changes using an Enhanced system (uses javascript).
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
class EnhancedChangesList extends ChangesList {
24
25
	/**
26
	 * @var RCCacheEntryFactory
27
	 */
28
	protected $cacheEntryFactory;
29
30
	/**
31
	 * @var array Array of array of RCCacheEntry
32
	 */
33
	protected $rc_cache;
34
35
	/**
36
	 * @param IContextSource|Skin $obj
37
	 * @throws MWException
38
	 */
39
	public function __construct( $obj ) {
40
		if ( $obj instanceof Skin ) {
41
			// @todo: deprecate constructing with Skin
42
			$context = $obj->getContext();
43
		} else {
44
			if ( !$obj instanceof IContextSource ) {
45
				throw new MWException( 'EnhancedChangesList must be constructed with a '
46
					. 'context source or skin.' );
47
			}
48
49
			$context = $obj;
50
		}
51
52
		parent::__construct( $context );
53
54
		// message is set by the parent ChangesList class
55
		$this->cacheEntryFactory = new RCCacheEntryFactory(
56
			$context,
57
			$this->message,
58
			$this->linkRenderer
59
		);
60
	}
61
62
	/**
63
	 * Add the JavaScript file for enhanced changeslist
64
	 * @return string
65
	 */
66
	public function beginRecentChangesList() {
67
		$this->rc_cache = [];
68
		$this->rcMoveIndex = 0;
69
		$this->rcCacheIndex = 0;
70
		$this->lastdate = '';
71
		$this->rclistOpen = false;
72
		$this->getOutput()->addModuleStyles( [
73
			'mediawiki.special.changeslist',
74
			'mediawiki.special.changeslist.enhanced',
75
		] );
76
		$this->getOutput()->addModules( [
77
			'jquery.makeCollapsible',
78
			'mediawiki.icon',
79
		] );
80
81
		return '<div class="mw-changeslist">';
82
	}
83
84
	/**
85
	 * Format a line for enhanced recentchange (aka with javascript and block of lines).
86
	 *
87
	 * @param RecentChange $rc
88
	 * @param bool $watched
89
	 * @param int $linenumber (default null)
90
	 *
91
	 * @return string
92
	 */
93
	public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
94
95
		$date = $this->getLanguage()->userDate(
96
			$rc->mAttribs['rc_timestamp'],
97
			$this->getUser()
98
		);
99
100
		$ret = '';
101
102
		# If it's a new day, add the headline and flush the cache
103
		if ( $date != $this->lastdate ) {
104
			# Process current cache
105
			$ret = $this->recentChangesBlock();
106
			$this->rc_cache = [];
107
			$ret .= Xml::element( 'h4', null, $date ) . "\n";
108
			$this->lastdate = $date;
109
		}
110
111
		$cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched );
112
		$this->addCacheEntry( $cacheEntry );
113
114
		return $ret;
115
	}
116
117
	/**
118
	 * Put accumulated information into the cache, for later display.
119
	 * Page moves go on their own line.
120
	 *
121
	 * @param RCCacheEntry $cacheEntry
122
	 */
123
	protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
124
		$cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
125
126
		if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) {
127
			$this->rc_cache[$cacheGroupingKey] = [];
128
		}
129
130
		array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry );
131
	}
132
133
	/**
134
	 * @todo use rc_source to group, if set; fallback to rc_type
135
	 *
136
	 * @param RCCacheEntry $cacheEntry
137
	 *
138
	 * @return string
139
	 */
140
	protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
141
		$title = $cacheEntry->getTitle();
142
		$cacheGroupingKey = $title->getPrefixedDBkey();
143
144
		$type = $cacheEntry->mAttribs['rc_type'];
145
146
		if ( $type == RC_LOG ) {
147
			// Group by log type
148
			$cacheGroupingKey = SpecialPage::getTitleFor(
149
				'Log',
150
				$cacheEntry->mAttribs['rc_log_type']
151
			)->getPrefixedDBkey();
152
		}
153
154
		return $cacheGroupingKey;
155
	}
156
157
	/**
158
	 * Enhanced RC group
159
	 * @param RCCacheEntry[] $block
160
	 * @return string
161
	 * @throws DomainException
162
	 */
163
	protected function recentChangesBlockGroup( $block ) {
164
		$recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' );
165
166
		# Add the namespace and title of the block as part of the class
167
		$tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' ];
168
		if ( $block[0]->mAttribs['rc_log_type'] ) {
169
			# Log entry
170
			$tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
171
				. $block[0]->mAttribs['rc_log_type'] );
172
		} else {
173
			$tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
174
				. $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
175
		}
176
		if ( $block[0]->watched
177
			&& $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
178
		) {
179
			$tableClasses[] = 'mw-changeslist-line-watched';
180
		} else {
181
			$tableClasses[] = 'mw-changeslist-line-not-watched';
182
		}
183
184
		# Collate list of users
185
		$userlinks = [];
186
		# Other properties
187
		$curId = 0;
188
		# Some catalyst variables...
189
		$namehidden = true;
190
		$allLogs = true;
191
		$RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
192
193
		# Default values for RC flags
194
		$collectedRcFlags = [];
195
		foreach ( $recentChangesFlags as $key => $value ) {
196
			$flagGrouping = ( isset( $recentChangesFlags[$key]['grouping'] ) ?
197
					$recentChangesFlags[$key]['grouping'] : 'any' );
198 View Code Duplication
			switch ( $flagGrouping ) {
199
				case 'all':
200
					$collectedRcFlags[$key] = true;
201
					break;
202
				case 'any':
203
					$collectedRcFlags[$key] = false;
204
					break;
205
				default:
206
					throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
207
			}
208
		}
209
		foreach ( $block as $rcObj ) {
210
			// If all log actions to this page were hidden, then don't
211
			// give the name of the affected page for this block!
212
			if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
213
				$namehidden = false;
214
			}
215
			$u = $rcObj->userlink;
216
			if ( !isset( $userlinks[$u] ) ) {
217
				$userlinks[$u] = 0;
218
			}
219
			if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
220
				$allLogs = false;
221
			}
222
			# Get the latest entry with a page_id and oldid
223
			# since logs may not have these.
224
			if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
225
				$curId = $rcObj->mAttribs['rc_cur_id'];
226
			}
227
228
			$userlinks[$u]++;
229
		}
230
231
		# Sort the list and convert to text
232
		krsort( $userlinks );
233
		asort( $userlinks );
234
		$users = [];
235
		foreach ( $userlinks as $userlink => $count ) {
236
			$text = $userlink;
237
			$text .= $this->getLanguage()->getDirMark();
238
			if ( $count > 1 ) {
239
				// @todo FIXME: Hardcoded '×'. Should be a message.
240
				$formattedCount = $this->getLanguage()->formatNum( $count ) . '×';
241
				$text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
242
			}
243
			array_push( $users, $text );
244
		}
245
246
		# Article link
247
		$articleLink = '';
248
		$revDeletedMsg = false;
249
		if ( $namehidden ) {
250
			$revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped();
251
		} elseif ( $allLogs ) {
252
			$articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
253
		} else {
254
			$articleLink = $this->getArticleLink( $block[0], $block[0]->unpatrolled, $block[0]->watched );
255
		}
256
257
		$queryParams['curid'] = $curId;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$queryParams was never initialized. Although not strictly required by PHP, it is generally a good practice to add $queryParams = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
258
259
		# Sub-entries
260
		$lines = [];
261
		foreach ( $block as $i => $rcObj ) {
262
			$line = $this->getLineData( $block, $rcObj, $queryParams );
263
			if ( !$line ) {
264
				// completely ignore this RC entry if we don't want to render it
265
				unset( $block[$i] );
266
				continue;
267
			}
268
269
			// Roll up flags
270
			foreach ( $line['recentChangesFlagsRaw'] as $key => $value ) {
271
				$flagGrouping = ( isset( $recentChangesFlags[$key]['grouping'] ) ?
272
					$recentChangesFlags[$key]['grouping'] : 'any' );
273 View Code Duplication
				switch ( $flagGrouping ) {
274
					case 'all':
275
						if ( !$value ) {
276
							$collectedRcFlags[$key] = false;
277
						}
278
						break;
279
					case 'any':
280
						if ( $value ) {
281
							$collectedRcFlags[$key] = true;
282
						}
283
						break;
284
					default:
285
						throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
286
				}
287
			}
288
289
			$lines[] = $line;
290
		}
291
292
		// Further down are some assumptions that $block is a 0-indexed array
293
		// with (count-1) as last key. Let's make sure it is.
294
		$block = array_values( $block );
295
296
		if ( empty( $block ) || !$lines ) {
297
			// if we can't show anything, don't display this block altogether
298
			return '';
299
		}
300
301
		$logText = $this->getLogText( $block, $queryParams, $allLogs,
302
			$collectedRcFlags['newpage'], $namehidden
303
		);
304
305
		# Character difference (does not apply if only log items)
306
		$charDifference = false;
307
		if ( $RCShowChangedSize && !$allLogs ) {
308
			$last = 0;
309
			$first = count( $block ) - 1;
310
			# Some events (like logs and category changes) have an "empty" size, so we need to skip those...
311
			while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
312
				$last++;
313
			}
314
			while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) {
315
				$first--;
316
			}
317
			# Get net change
318
			$charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] );
319
		}
320
321
		$numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
322
		$usersList = $this->msg( 'brackets' )->rawParams(
323
			implode( $this->message['semicolon-separator'], $users )
324
		)->escaped();
325
326
		$templateParams = [
327
			'articleLink' => $articleLink,
328
			'charDifference' => $charDifference,
329
			'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ),
330
			'languageDirMark' => $this->getLanguage()->getDirMark(),
331
			'lines' => $lines,
332
			'logText' => $logText,
333
			'numberofWatchingusers' => $numberofWatchingusers,
334
			'rev-deleted-event' => $revDeletedMsg,
335
			'tableClasses' => $tableClasses,
336
			'timestamp' => $block[0]->timestamp,
337
			'users' => $usersList,
338
		];
339
340
		$this->rcCacheIndex++;
341
342
		$templateParser = new TemplateParser();
343
		return $templateParser->processTemplate(
344
			'EnhancedChangesListGroup',
345
			$templateParams
346
		);
347
	}
348
349
	/**
350
	 * @param RCCacheEntry[] $block
351
	 * @param RCCacheEntry $rcObj
352
	 * @param array $queryParams
353
	 * @return array
354
	 * @throws Exception
355
	 * @throws FatalError
356
	 * @throws MWException
357
	 */
358
	protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
359
		$RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
360
361
		# Classes to apply -- TODO implement
362
		$classes = [];
363
		$type = $rcObj->mAttribs['rc_type'];
364
		$data = [];
365
		$lineParams = [];
366
367
		if ( $rcObj->watched
368
			&& $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
369
		) {
370
			$lineParams['classes'] = [ 'mw-enhanced-watched' ];
371
		}
372
		$separator = ' <span class="mw-changeslist-separator">. .</span> ';
373
374
		$data['recentChangesFlags'] = [
375
			'newpage' => $type == RC_NEW,
376
			'minor' => $rcObj->mAttribs['rc_minor'],
377
			'unpatrolled' => $rcObj->unpatrolled,
378
			'bot' => $rcObj->mAttribs['rc_bot'],
379
		];
380
381
		$params = $queryParams;
382
383
		if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
384
			$params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
385
		}
386
387
		# Log timestamp
388
		if ( $type == RC_LOG ) {
389
			$link = $rcObj->timestamp;
390
			# Revision link
391
		} elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
392
			$link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> ';
393
		} else {
394
			$link = $this->linkRenderer->makeKnownLink(
395
				$rcObj->getTitle(),
396
				new HtmlArmor( $rcObj->timestamp ),
397
				[],
398
				$params
399
			);
400
			if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
401
				$link = '<span class="history-deleted">' . $link . '</span> ';
402
			}
403
		}
404
		$data['timestampLink'] = $link;
405
406
		$currentAndLastLinks = '';
407
		if ( !$type == RC_LOG || $type == RC_NEW ) {
408
			$currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
409
					$rcObj->curlink .
410
					$this->message['pipe-separator'] .
411
					$rcObj->lastlink
412
				)->escaped();
413
		}
414
		$data['currentAndLastLinks'] = $currentAndLastLinks;
415
		$data['separatorAfterCurrentAndLastLinks'] = $separator;
416
417
		# Character diff
418
		if ( $RCShowChangedSize ) {
419
			$cd = $this->formatCharacterDifference( $rcObj );
420
			if ( $cd !== '' ) {
421
				$data['characterDiff'] = $cd;
422
				$data['separatorAfterCharacterDiff'] = $separator;
423
			}
424
		}
425
426 View Code Duplication
		if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
427
			$data['logEntry'] = $this->insertLogEntry( $rcObj );
428
		} elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
429
			$data['comment'] = $this->insertComment( $rcObj );
430
		} else {
431
			# User links
432
			$data['userLink'] = $rcObj->userlink;
433
			$data['userTalkLink'] = $rcObj->usertalklink;
434
			$data['comment'] = $this->insertComment( $rcObj );
435
		}
436
437
		# Rollback
438
		$data['rollback'] = $this->getRollback( $rcObj );
439
440
		# Tags
441
		$data['tags'] = $this->getTags( $rcObj, $classes );
442
443
		// give the hook a chance to modify the data
444
		$success = Hooks::run( 'EnhancedChangesListModifyLineData',
445
			[ $this, &$data, $block, $rcObj ] );
446
		if ( !$success ) {
447
			// skip entry if hook aborted it
448
			return [];
449
		}
450
451
		$lineParams['recentChangesFlagsRaw'] = [];
452
		if ( isset( $data['recentChangesFlags'] ) ) {
453
			$lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
0 ignored issues
show
Bug introduced by
It seems like $data['recentChangesFlags'] can also be of type string; however, ChangesList::recentChangesFlags() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
454
			# FIXME: This is used by logic, don't return it in the template params.
455
			$lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
456
			unset( $data['recentChangesFlags'] );
457
		}
458
459
		if ( isset( $data['timestampLink'] ) ) {
460
			$lineParams['timestampLink'] = $data['timestampLink'];
461
			unset( $data['timestampLink'] );
462
		}
463
464
		// everything else: makes it easier for extensions to add or remove data
465
		$lineParams['data'] = array_values( $data );
466
467
		return $lineParams;
468
	}
469
470
	/**
471
	 * Generates amount of changes (linking to diff ) & link to history.
472
	 *
473
	 * @param array $block
474
	 * @param array $queryParams
475
	 * @param bool $allLogs
476
	 * @param bool $isnew
477
	 * @param bool $namehidden
478
	 * @return string
479
	 */
480
	protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
481
		if ( empty( $block ) ) {
482
			return '';
483
		}
484
485
		# Changes message
486
		static $nchanges = [];
487
		static $sinceLastVisitMsg = [];
488
489
		$n = count( $block );
490
		if ( !isset( $nchanges[$n] ) ) {
491
			$nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
492
		}
493
494
		$sinceLast = 0;
495
		$unvisitedOldid = null;
496
		/** @var $rcObj RCCacheEntry */
497
		foreach ( $block as $rcObj ) {
498
			// Same logic as below inside main foreach
499
			if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
500
				$sinceLast++;
501
				$unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
502
			}
503
		}
504
		if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
505
			$sinceLastVisitMsg[$sinceLast] =
506
				$this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
507
		}
508
509
		$currentRevision = 0;
510
		foreach ( $block as $rcObj ) {
511
			if ( !$currentRevision ) {
512
				$currentRevision = $rcObj->mAttribs['rc_this_oldid'];
513
			}
514
		}
515
516
		# Total change link
517
		$links = [];
518
		/** @var $block0 RecentChange */
519
		$block0 = $block[0];
520
		$last = $block[count( $block ) - 1];
521
		if ( !$allLogs ) {
522
			if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ||
0 ignored issues
show
Bug introduced by
The variable $rcObj does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
523
				$isnew ||
524
				$rcObj->mAttribs['rc_type'] == RC_CATEGORIZE
525
			) {
526
				$links['total-changes'] = $nchanges[$n];
527
			} else {
528
				$links['total-changes'] = $this->linkRenderer->makeKnownLink(
529
					$block0->getTitle(),
530
					new HtmlArmor( $nchanges[$n] ),
531
					[],
532
					$queryParams + [
533
						'diff' => $currentRevision,
534
						'oldid' => $last->mAttribs['rc_last_oldid'],
535
					]
536
				);
537
				if ( $sinceLast > 0 && $sinceLast < $n ) {
538
					$links['total-changes-since-last'] = $this->linkRenderer->makeKnownLink(
539
							$block0->getTitle(),
540
							new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
541
							[],
542
							$queryParams + [
543
								'diff' => $currentRevision,
544
								'oldid' => $unvisitedOldid,
545
							]
546
						);
547
				}
548
			}
549
		}
550
551
		# History
552
		if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
553
			// don't show history link for logs
554
		} elseif ( $namehidden || !$block0->getTitle()->exists() ) {
555
			$links['history'] = $this->message['enhancedrc-history'];
556
		} else {
557
			$params = $queryParams;
558
			$params['action'] = 'history';
559
560
			$links['history'] = $this->linkRenderer->makeKnownLink(
561
					$block0->getTitle(),
562
					new HtmlArmor( $this->message['enhancedrc-history'] ),
563
					[],
564
					$params
565
				);
566
		}
567
568
		# Allow others to alter, remove or add to these links
569
		Hooks::run( 'EnhancedChangesList::getLogText',
570
			[ $this, &$links, $block ] );
571
572
		if ( !$links ) {
573
			return '';
574
		}
575
576
		$logtext = implode( $this->message['pipe-separator'], $links );
577
		$logtext = $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
578
		return ' ' . $logtext;
579
	}
580
581
	/**
582
	 * Enhanced RC ungrouped line.
583
	 *
584
	 * @param RecentChange|RCCacheEntry $rcObj
585
	 * @return string A HTML formatted line (generated using $r)
586
	 */
587
	protected function recentChangesBlockLine( $rcObj ) {
588
		$data = [];
589
590
		$query['curid'] = $rcObj->mAttribs['rc_cur_id'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
591
592
		$type = $rcObj->mAttribs['rc_type'];
593
		$logType = $rcObj->mAttribs['rc_log_type'];
594
		$classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
0 ignored issues
show
Bug introduced by
The property watched does not seem to exist in RecentChange.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
595
		$classes[] = 'mw-enhanced-rc';
596
597 View Code Duplication
		if ( $logType ) {
598
			# Log entry
599
			$classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
600
		} else {
601
			$classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
602
				$rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
603
		}
604
605
		# Flag and Timestamp
606
		$data['recentChangesFlags'] = [
607
			'newpage' => $type == RC_NEW,
608
			'minor' => $rcObj->mAttribs['rc_minor'],
609
			'unpatrolled' => $rcObj->unpatrolled,
0 ignored issues
show
Bug introduced by
The property unpatrolled does not seem to exist in RecentChange.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
610
			'bot' => $rcObj->mAttribs['rc_bot'],
611
		];
612
		// timestamp is not really a link here, but is called timestampLink
613
		// for consistency with EnhancedChangesListModifyLineData
614
		$data['timestampLink'] = $rcObj->timestamp;
0 ignored issues
show
Bug introduced by
The property timestamp does not seem to exist. Did you mean notificationtimestamp?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
615
616
		# Article or log link
617
		if ( $logType ) {
618
			$logPage = new LogPage( $logType );
619
			$logTitle = SpecialPage::getTitleFor( 'Log', $logType );
620
			$logName = $logPage->getName()->text();
621
			$data['logLink'] = $this->msg( 'parentheses' )
622
				->rawParams(
623
					$this->linkRenderer->makeKnownLink( $logTitle, $logName )
624
				)->escaped();
625
		} else {
626
			$data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
627
		}
628
629
		# Diff and hist links
630
		if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
631
			$query['action'] = 'history';
632
			$data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
0 ignored issues
show
Compatibility introduced by
$rcObj of type object<RecentChange> is not a sub-type of object<RCCacheEntry>. It seems like you assume a child class of the class RecentChange to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
633
		}
634
		$data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator">. .</span> ';
635
636
		# Character diff
637
		if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
638
			$cd = $this->formatCharacterDifference( $rcObj );
639
			if ( $cd !== '' ) {
640
				$data['characterDiff'] = $cd;
641
				$data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator">. .</span> ';
642
			}
643
		}
644
645
		if ( $type == RC_LOG ) {
646
			$data['logEntry'] = $this->insertLogEntry( $rcObj );
647
		} elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
648
			$data['comment'] = $this->insertComment( $rcObj );
649 View Code Duplication
		} else {
650
			$data['userLink'] = $rcObj->userlink;
0 ignored issues
show
Bug introduced by
The property userlink does not seem to exist in RecentChange.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
651
			$data['userTalkLink'] = $rcObj->usertalklink;
0 ignored issues
show
Bug introduced by
The property usertalklink does not seem to exist in RecentChange.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
652
			$data['comment'] = $this->insertComment( $rcObj );
653
			if ( $type == RC_CATEGORIZE ) {
654
				$data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
0 ignored issues
show
Compatibility introduced by
$rcObj of type object<RecentChange> is not a sub-type of object<RCCacheEntry>. It seems like you assume a child class of the class RecentChange to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
655
			}
656
			$data['rollback'] = $this->getRollback( $rcObj );
657
		}
658
659
		# Tags
660
		$data['tags'] = $this->getTags( $rcObj, $classes );
661
662
		# Show how many people are watching this if enabled
663
		$data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
664
665
		// give the hook a chance to modify the data
666
		$success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
667
			[ $this, &$data, $rcObj ] );
668
		if ( !$success ) {
669
			// skip entry if hook aborted it
670
			return '';
671
		}
672
673
		$line = Html::openElement( 'table', [ 'class' => $classes ] ) .
674
			Html::openElement( 'tr' );
675
		$line .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>';
676
677
		if ( isset( $data['recentChangesFlags'] ) ) {
678
			$line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
679
			unset( $data['recentChangesFlags'] );
680
		}
681
682
		if ( isset( $data['timestampLink'] ) ) {
683
			$line .= '&#160;' . $data['timestampLink'];
684
			unset( $data['timestampLink'] );
685
		}
686
		$line .= '&#160;</td><td>';
687
688
		// everything else: makes it easier for extensions to add or remove data
689
		$line .= implode( '', $data );
690
691
		$line .= "</td></tr></table>\n";
692
693
		return $line;
694
	}
695
696
	/**
697
	 * Returns value to be used in 'historyLink' element of $data param in
698
	 * EnhancedChangesListModifyBlockLineData hook.
699
	 *
700
	 * @since 1.27
701
	 *
702
	 * @param RCCacheEntry $rc
703
	 * @param array $query array of key/value pairs to append as a query string
704
	 * @return string HTML
705
	 */
706
	public function getDiffHistLinks( RCCacheEntry $rc, array $query ) {
707
		$pageTitle = $rc->getTitle();
708
		if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
709
			// For categorizations we must swap the category title with the page title!
710
			$pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
711
		}
712
713
		$retVal = ' ' . $this->msg( 'parentheses' )
714
				->rawParams( $rc->difflink . $this->message['pipe-separator']
715
					. $this->linkRenderer->makeKnownLink(
716
						$pageTitle,
0 ignored issues
show
Bug introduced by
It seems like $pageTitle defined by \Title::newFromID($rc->getAttribute('rc_cur_id')) on line 710 can be null; however, MediaWiki\Linker\LinkRenderer::makeKnownLink() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
717
						new HtmlArmor( $this->message['hist'] ),
718
						[],
719
						$query
720
					) )->escaped();
721
		return $retVal;
722
	}
723
724
	/**
725
	 * If enhanced RC is in use, this function takes the previously cached
726
	 * RC lines, arranges them, and outputs the HTML
727
	 *
728
	 * @return string
729
	 */
730
	protected function recentChangesBlock() {
731
		if ( count( $this->rc_cache ) == 0 ) {
732
			return '';
733
		}
734
735
		$blockOut = '';
736
		foreach ( $this->rc_cache as $block ) {
737
			if ( count( $block ) < 2 ) {
738
				$blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
739
			} else {
740
				$blockOut .= $this->recentChangesBlockGroup( $block );
741
			}
742
		}
743
744
		return '<div>' . $blockOut . '</div>';
745
	}
746
747
	/**
748
	 * Returns text for the end of RC
749
	 * If enhanced RC is in use, returns pretty much all the text
750
	 * @return string
751
	 */
752
	public function endRecentChangesList() {
753
		return $this->recentChangesBlock() . '</div>';
754
	}
755
}
756