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'] );
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 );
0 ignored issues
show
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...
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