Completed
Branch master (939199)
by
unknown
39:35
created

includes/changes/EnhancedChangesList.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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;
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 = [ 'mw-enhanced-rc' ];
362
		$type = $rcObj->mAttribs['rc_type'];
363
		$data = [];
364
		$lineParams = [];
365
366
		if ( $rcObj->watched
367
			&& $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
368
		) {
369
			$classes = [ 'mw-enhanced-watched' ];
370
		}
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, &$classes ] );
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
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
		$lineParams['classes'] = array_values( $classes );
465
466
		// everything else: makes it easier for extensions to add or remove data
467
		$lineParams['data'] = array_values( $data );
468
469
		return $lineParams;
470
	}
471
472
	/**
473
	 * Generates amount of changes (linking to diff ) & link to history.
474
	 *
475
	 * @param array $block
476
	 * @param array $queryParams
477
	 * @param bool $allLogs
478
	 * @param bool $isnew
479
	 * @param bool $namehidden
480
	 * @return string
481
	 */
482
	protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
483
		if ( empty( $block ) ) {
484
			return '';
485
		}
486
487
		# Changes message
488
		static $nchanges = [];
489
		static $sinceLastVisitMsg = [];
490
491
		$n = count( $block );
492
		if ( !isset( $nchanges[$n] ) ) {
493
			$nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
494
		}
495
496
		$sinceLast = 0;
497
		$unvisitedOldid = null;
498
		/** @var $rcObj RCCacheEntry */
499
		foreach ( $block as $rcObj ) {
500
			// Same logic as below inside main foreach
501
			if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
502
				$sinceLast++;
503
				$unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
504
			}
505
		}
506
		if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
507
			$sinceLastVisitMsg[$sinceLast] =
508
				$this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
509
		}
510
511
		$currentRevision = 0;
512
		foreach ( $block as $rcObj ) {
513
			if ( !$currentRevision ) {
514
				$currentRevision = $rcObj->mAttribs['rc_this_oldid'];
515
			}
516
		}
517
518
		# Total change link
519
		$links = [];
520
		/** @var $block0 RecentChange */
521
		$block0 = $block[0];
522
		$last = $block[count( $block ) - 1];
523
		if ( !$allLogs ) {
524
			if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ||
525
				$isnew ||
526
				$rcObj->mAttribs['rc_type'] == RC_CATEGORIZE
527
			) {
528
				$links['total-changes'] = $nchanges[$n];
529
			} else {
530
				$links['total-changes'] = $this->linkRenderer->makeKnownLink(
531
					$block0->getTitle(),
532
					new HtmlArmor( $nchanges[$n] ),
533
					[],
534
					$queryParams + [
535
						'diff' => $currentRevision,
536
						'oldid' => $last->mAttribs['rc_last_oldid'],
537
					]
538
				);
539
				if ( $sinceLast > 0 && $sinceLast < $n ) {
540
					$links['total-changes-since-last'] = $this->linkRenderer->makeKnownLink(
541
							$block0->getTitle(),
542
							new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
543
							[],
544
							$queryParams + [
545
								'diff' => $currentRevision,
546
								'oldid' => $unvisitedOldid,
547
							]
548
						);
549
				}
550
			}
551
		}
552
553
		# History
554
		if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
555
			// don't show history link for logs
556
		} elseif ( $namehidden || !$block0->getTitle()->exists() ) {
557
			$links['history'] = $this->message['enhancedrc-history'];
558
		} else {
559
			$params = $queryParams;
560
			$params['action'] = 'history';
561
562
			$links['history'] = $this->linkRenderer->makeKnownLink(
563
					$block0->getTitle(),
564
					new HtmlArmor( $this->message['enhancedrc-history'] ),
565
					[],
566
					$params
567
				);
568
		}
569
570
		# Allow others to alter, remove or add to these links
571
		Hooks::run( 'EnhancedChangesList::getLogText',
572
			[ $this, &$links, $block ] );
573
574
		if ( !$links ) {
575
			return '';
576
		}
577
578
		$logtext = implode( $this->message['pipe-separator'], $links );
579
		$logtext = $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
580
		return ' ' . $logtext;
581
	}
582
583
	/**
584
	 * Enhanced RC ungrouped line.
585
	 *
586
	 * @param RecentChange|RCCacheEntry $rcObj
587
	 * @return string A HTML formatted line (generated using $r)
588
	 */
589
	protected function recentChangesBlockLine( $rcObj ) {
590
		$data = [];
591
592
		$query['curid'] = $rcObj->mAttribs['rc_cur_id'];
593
594
		$type = $rcObj->mAttribs['rc_type'];
595
		$logType = $rcObj->mAttribs['rc_log_type'];
596
		$classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
597
		$classes[] = 'mw-enhanced-rc';
598
599 View Code Duplication
		if ( $logType ) {
600
			# Log entry
601
			$classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
602
		} else {
603
			$classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
604
				$rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
605
		}
606
607
		# Flag and Timestamp
608
		$data['recentChangesFlags'] = [
609
			'newpage' => $type == RC_NEW,
610
			'minor' => $rcObj->mAttribs['rc_minor'],
611
			'unpatrolled' => $rcObj->unpatrolled,
612
			'bot' => $rcObj->mAttribs['rc_bot'],
613
		];
614
		// timestamp is not really a link here, but is called timestampLink
615
		// for consistency with EnhancedChangesListModifyLineData
616
		$data['timestampLink'] = $rcObj->timestamp;
617
618
		# Article or log link
619
		if ( $logType ) {
620
			$logPage = new LogPage( $logType );
621
			$logTitle = SpecialPage::getTitleFor( 'Log', $logType );
622
			$logName = $logPage->getName()->text();
623
			$data['logLink'] = $this->msg( 'parentheses' )
624
				->rawParams(
625
					$this->linkRenderer->makeKnownLink( $logTitle, $logName )
626
				)->escaped();
627
		} else {
628
			$data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
629
		}
630
631
		# Diff and hist links
632
		if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
633
			$query['action'] = 'history';
634
			$data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
635
		}
636
		$data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator">. .</span> ';
637
638
		# Character diff
639
		if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
640
			$cd = $this->formatCharacterDifference( $rcObj );
641
			if ( $cd !== '' ) {
642
				$data['characterDiff'] = $cd;
643
				$data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator">. .</span> ';
644
			}
645
		}
646
647
		if ( $type == RC_LOG ) {
648
			$data['logEntry'] = $this->insertLogEntry( $rcObj );
649
		} elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
650
			$data['comment'] = $this->insertComment( $rcObj );
651 View Code Duplication
		} else {
652
			$data['userLink'] = $rcObj->userlink;
653
			$data['userTalkLink'] = $rcObj->usertalklink;
654
			$data['comment'] = $this->insertComment( $rcObj );
655
			if ( $type == RC_CATEGORIZE ) {
656
				$data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
657
			}
658
			$data['rollback'] = $this->getRollback( $rcObj );
659
		}
660
661
		# Tags
662
		$data['tags'] = $this->getTags( $rcObj, $classes );
663
664
		# Show how many people are watching this if enabled
665
		$data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
666
667
		// give the hook a chance to modify the data
668
		$success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
669
			[ $this, &$data, $rcObj ] );
670
		if ( !$success ) {
671
			// skip entry if hook aborted it
672
			return '';
673
		}
674
675
		$line = Html::openElement( 'table', [ 'class' => $classes ] ) .
676
			Html::openElement( 'tr' );
677
		$line .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>';
678
679
		if ( isset( $data['recentChangesFlags'] ) ) {
680
			$line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
681
			unset( $data['recentChangesFlags'] );
682
		}
683
684
		if ( isset( $data['timestampLink'] ) ) {
685
			$line .= '&#160;' . $data['timestampLink'];
686
			unset( $data['timestampLink'] );
687
		}
688
		$line .= '&#160;</td><td>';
689
690
		// everything else: makes it easier for extensions to add or remove data
691
		$line .= implode( '', $data );
692
693
		$line .= "</td></tr></table>\n";
694
695
		return $line;
696
	}
697
698
	/**
699
	 * Returns value to be used in 'historyLink' element of $data param in
700
	 * EnhancedChangesListModifyBlockLineData hook.
701
	 *
702
	 * @since 1.27
703
	 *
704
	 * @param RCCacheEntry $rc
705
	 * @param array $query array of key/value pairs to append as a query string
706
	 * @return string HTML
707
	 */
708
	public function getDiffHistLinks( RCCacheEntry $rc, array $query ) {
709
		$pageTitle = $rc->getTitle();
710
		if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
711
			// For categorizations we must swap the category title with the page title!
712
			$pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
713
		}
714
715
		$retVal = ' ' . $this->msg( 'parentheses' )
716
				->rawParams( $rc->difflink . $this->message['pipe-separator']
717
					. $this->linkRenderer->makeKnownLink(
718
						$pageTitle,
719
						new HtmlArmor( $this->message['hist'] ),
720
						[],
721
						$query
722
					) )->escaped();
723
		return $retVal;
724
	}
725
726
	/**
727
	 * If enhanced RC is in use, this function takes the previously cached
728
	 * RC lines, arranges them, and outputs the HTML
729
	 *
730
	 * @return string
731
	 */
732
	protected function recentChangesBlock() {
733
		if ( count( $this->rc_cache ) == 0 ) {
734
			return '';
735
		}
736
737
		$blockOut = '';
738
		foreach ( $this->rc_cache as $block ) {
739
			if ( count( $block ) < 2 ) {
740
				$blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
741
			} else {
742
				$blockOut .= $this->recentChangesBlockGroup( $block );
743
			}
744
		}
745
746
		return '<div>' . $blockOut . '</div>';
747
	}
748
749
	/**
750
	 * Returns text for the end of RC
751
	 * If enhanced RC is in use, returns pretty much all the text
752
	 * @return string
753
	 */
754
	public function endRecentChangesList() {
755
		return $this->recentChangesBlock() . '</div>';
756
	}
757
}
758