MediaWiki master
EnhancedChangesList.php
Go to the documentation of this file.
1<?php
8
9use DomainException;
21
28
33
37 protected $rc_cache;
38
42 protected $templateParser;
43
48 public function __construct( $context, ?ChangesListFilterGroupContainer $filterGroups = null ) {
49 parent::__construct( $context, $filterGroups );
50
51 // message is set by the parent ChangesList class
52 $this->cacheEntryFactory = new RCCacheEntryFactory(
53 $context,
54 $this->message,
55 $this->linkRenderer,
56 $this->userLinkRenderer
57 );
58 $this->templateParser = new TemplateParser();
59 }
60
65 public function beginRecentChangesList() {
66 $this->getOutput()->addModuleStyles( [
67 'mediawiki.special.changeslist.enhanced',
68 ] );
69
70 parent::beginRecentChangesList();
71 return '<div class="mw-changeslist" aria-live="polite">';
72 }
73
83 public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
84 $date = $this->getLanguage()->userDate(
85 $rc->mAttribs['rc_timestamp'],
86 $this->getUser()
87 );
88 if ( $this->lastdate === '' ) {
89 $this->lastdate = $date;
90 }
91
92 $ret = '';
93
94 # If it's a new day, flush the cache and update $this->lastdate
95 if ( $date !== $this->lastdate ) {
96 # Process current cache (uses $this->lastdate to generate a heading)
97 $ret = $this->recentChangesBlock();
98 $this->rc_cache = [];
99 $this->lastdate = $date;
100 }
101
102 $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched );
103 $this->addCacheEntry( $cacheEntry );
104
105 return $ret;
106 }
107
112 protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
113 $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
114 $this->rc_cache[$cacheGroupingKey][] = $cacheEntry;
115 }
116
122 protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
123 $title = $cacheEntry->getTitle();
124 $cacheGroupingKey = $title->getPrefixedDBkey();
125
126 $source = $cacheEntry->mAttribs['rc_source'];
127
129 // Group by log type
130 $cacheGroupingKey = SpecialPage::getTitleFor(
131 'Log',
132 $cacheEntry->mAttribs['rc_log_type']
133 )->getPrefixedDBkey();
134 }
135
136 return $cacheGroupingKey;
137 }
138
144 protected function recentChangesBlockGroup( $block ) {
145 $recentChangesFlags = $this->getConfig()->get( MainConfigNames::RecentChangesFlags );
146
147 # Add the namespace and title of the block as part of the class
148 $tableClasses = [ 'mw-enhanced-rc', 'mw-changeslist-line' ];
149 if ( $block[0]->mAttribs['rc_log_type'] ) {
150 # Log entry
151 $tableClasses[] = 'mw-changeslist-log';
152 $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
153 . $block[0]->mAttribs['rc_log_type'] );
154 } else {
155 $tableClasses[] = 'mw-changeslist-edit';
156 $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
157 . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
158 }
159 if ( $block[0]->watched ) {
160 $tableClasses[] = 'mw-changeslist-line-watched';
161 } else {
162 $tableClasses[] = 'mw-changeslist-line-not-watched';
163 }
164
165 # Collate list of users
166 $usercounts = [];
167 $userlinks = [];
168 # Some catalyst variables...
169 $namehidden = true;
170 $allLogs = true;
171 $RCShowChangedSize = $this->getConfig()->get( MainConfigNames::RCShowChangedSize );
172
173 # Default values for RC flags
174 $collectedRcFlags = [];
175 foreach ( $recentChangesFlags as $key => $value ) {
176 $flagGrouping = $value['grouping'] ?? 'any';
177 switch ( $flagGrouping ) {
178 case 'all':
179 $collectedRcFlags[$key] = true;
180 break;
181 case 'any':
182 $collectedRcFlags[$key] = false;
183 break;
184 default:
185 throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
186 }
187 }
188 foreach ( $block as $rcObj ) {
189 // If all log actions to this page were hidden, then don't
190 // give the name of the affected page for this block!
191 if ( !static::isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
192 $namehidden = false;
193 }
194 $username = $rcObj->getPerformerIdentity()->getName();
195 $userlink = $rcObj->userlink;
196
197 // Redact the username from tabulation if it is rc_deleted
198 if ( static::isDeleted( $rcObj, LogPage::DELETED_USER ) ) {
199 $username = '';
200 }
201
202 if ( !isset( $usercounts[$username] ) ) {
203 $usercounts[$username] = 0;
204 $userlinks[$username] = $userlink;
205 }
206 if ( $rcObj->mAttribs['rc_source'] !== RecentChange::SRC_LOG ) {
207 $allLogs = false;
208 }
209
210 $usercounts[$username]++;
211 }
212
213 # Sort the list and convert to text
214 krsort( $usercounts );
215 asort( $usercounts );
216 $users = [];
217 foreach ( $usercounts as $username => $count ) {
218 $text = (string)$userlinks[$username];
219 if ( $count > 1 ) {
220 $formattedCount = $this->msg( 'ntimes' )->numParams( $count )->escaped();
221 $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
222 }
223 $users[] = Html::rawElement(
224 'span',
225 [ 'class' => 'mw-changeslist-user-in-group' ],
226 $text
227 );
228 }
229
230 # Article link
231 $articleLink = '';
232 $revDeletedMsg = false;
233 if ( $namehidden ) {
234 $revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped();
235 } elseif ( $allLogs ) {
236 $articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
237 } else {
238 $articleLink = $this->getArticleLink(
239 $block[0], $block[0]->unpatrolled, $block[0]->watched );
240 }
241
242 # Sub-entries
243 $lines = [];
244 $filterClasses = [];
245 foreach ( $block as $i => $rcObj ) {
246 $line = $this->getLineData( $block, $rcObj, [], false );
247 if ( !$line ) {
248 // completely ignore this RC entry if we don't want to render it
249 unset( $block[$i] );
250 continue;
251 }
252
253 // Roll up flags
254 foreach ( $line['recentChangesFlagsRaw'] as $key => $value ) {
255 $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
256 switch ( $flagGrouping ) {
257 case 'all':
258 if ( !$value ) {
259 $collectedRcFlags[$key] = false;
260 }
261 break;
262 case 'any':
263 if ( $value ) {
264 $collectedRcFlags[$key] = true;
265 }
266 break;
267 default:
268 throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
269 }
270 }
271
272 // Roll up filter-based CSS classes
273 $filterClasses = array_merge( $filterClasses, $this->getHTMLClassesForFilters( $rcObj ) );
274 // Add classes for change tags separately, getHTMLClassesForFilters() doesn't add them
275 $this->getTags( $rcObj, $filterClasses );
276 $filterClasses = array_unique( $filterClasses );
277
278 $lines[] = $line;
279 }
280
281 // Further down are some assumptions that $block is a 0-indexed array
282 // with (count-1) as last key. Let's make sure it is.
283 $block = array_values( $block );
284 $filterClasses = array_values( $filterClasses );
285
286 if ( !$block || !$lines ) {
287 // if we can't show anything, don't display this block altogether
288 return '';
289 }
290
291 $labels = $this->getGroupLabels( $block, $tableClasses );
292 if ( $labels === '' ) {
293 foreach ( $block as $i => $rcObj ) {
294 $lineClasses = [];
295 $lineLabels = $this->getLabels( $rcObj, $lineClasses );
296 if ( $lineLabels !== '' ) {
297 $lines[$i]['data'][] = $lineLabels;
298 }
299 }
300 }
301
302 $logText = $this->getLogText( $block, [], $allLogs,
303 $collectedRcFlags['newpage'], $namehidden
304 );
305
306 # Character difference (does not apply if only log items)
307 $charDifference = false;
308 if ( $RCShowChangedSize && !$allLogs ) {
309 $last = 0;
310 $first = count( $block ) - 1;
311 # Some events (like logs and category changes) have an "empty" size, so we need to skip those...
312 while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
313 $last++;
314 }
315 while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) {
316 $first--;
317 }
318 # Get net change
319 $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ) ?: false;
320 }
321
322 $numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
323 $usersList = $this->msg( 'brackets' )->rawParams(
324 implode( $this->message['semicolon-separator'], $users )
325 )->escaped();
326
327 $prefix = '';
328 if ( is_callable( $this->changeLinePrefixer ) ) {
329 $prefix = ( $this->changeLinePrefixer )( $block[0], $this, true );
330 }
331
332 $templateParams = [
333 'checkboxId' => 'mw-checkbox-' . base64_encode( random_bytes( 3 ) ),
334 'articleLink' => $articleLink,
335 'charDifference' => $charDifference,
336 'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ),
337 'filterClasses' => $filterClasses,
338 'labels' => $labels,
339 'lines' => $lines,
340 'logText' => $logText,
341 'numberofWatchingusers' => $numberofWatchingusers,
342 'prefix' => $prefix,
343 'rev-deleted-event' => $revDeletedMsg,
344 'tableClasses' => $tableClasses,
345 'timestamp' => $block[0]->timestamp,
346 'fullTimestamp' => $block[0]->getAttribute( 'rc_timestamp' ),
347 'users' => $usersList,
348 ];
349
350 $this->rcCacheIndex++;
351
352 return $this->templateParser->processTemplate(
353 'EnhancedChangesListGroup',
354 $templateParams
355 );
356 }
357
365 protected function getLineData(
366 array $block,
367 RCCacheEntry $rcObj,
368 array $queryParams = [],
369 bool $includeLabels = true
370 ) {
371 $RCShowChangedSize = $this->getConfig()->get( MainConfigNames::RCShowChangedSize );
372
373 $source = $rcObj->mAttribs['rc_source'];
374 $data = [];
375 $titleText = $rcObj->getTitle();
376 if ( !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
377 $titleText = $this->msg( 'rev-deleted-event' );
378 }
379 $lineParams = [ 'targetTitle' => $titleText ];
380
381 $classes = [ 'mw-enhanced-rc' ];
382 if ( $rcObj->watched ) {
383 $classes[] = 'mw-enhanced-watched';
384 }
385 $classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
386
387 $separator = ' <span class="mw-changeslist-separator"></span> ';
388
389 $data['recentChangesFlags'] = [
390 'newpage' => $source == RecentChange::SRC_NEW,
391 'minor' => $rcObj->mAttribs['rc_minor'],
392 'unpatrolled' => $rcObj->unpatrolled,
393 'bot' => $rcObj->mAttribs['rc_bot'],
394 ];
395
396 # Log timestamp
398 $link = htmlspecialchars( $rcObj->timestamp );
399 # Revision link
400 } elseif ( !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
401 $link = Html::element( 'span', [ 'class' => 'history-deleted' ], $rcObj->timestamp );
402 } else {
403 $params = [];
404 $params['curid'] = $rcObj->mAttribs['rc_cur_id'];
405 if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
406 $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
407 }
408 // FIXME: The link has incorrect "title=" when rc_source = RecentChange::SRC_CATEGORIZE.
409 // rc_cur_id refers to the page that was categorized
410 // whereas RecentChange::getTitle refers to the category.
411 $link = $this->linkRenderer->makeKnownLink(
412 $rcObj->getTitle(),
413 $rcObj->timestamp,
414 [],
415 $params + $queryParams
416 );
417 if ( static::isDeleted( $rcObj, RevisionRecord::DELETED_TEXT ) ) {
418 $link = '<span class="history-deleted">' . $link . '</span> ';
419 }
420 }
421 $data['timestampLink'] = $link;
422
423 $currentAndLastLinks = '';
425 $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
426 $rcObj->curlink .
427 $this->message['pipe-separator'] .
428 $rcObj->lastlink
429 )->escaped();
430 }
431 $data['currentAndLastLinks'] = $currentAndLastLinks;
432 $data['separatorAfterCurrentAndLastLinks'] = $separator;
433
434 # Character diff
435 if ( $RCShowChangedSize ) {
436 $cd = $this->formatCharacterDifference( $rcObj );
437 if ( $cd !== '' ) {
438 $data['characterDiff'] = $cd;
439 $data['separatorAfterCharacterDiff'] = $separator;
440 }
441 }
442
444 $data['logEntry'] = $this->insertLogEntry( $rcObj );
445 } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
446 $data['comment'] = $this->insertComment( $rcObj );
447 } else {
448 # User links
449 $data['userLink'] = $rcObj->userlink;
450 $data['userTalkLink'] = $rcObj->usertalklink;
451 $data['comment'] = $this->insertComment( $rcObj );
453 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
454 }
455 # Rollback, thanks etc...
456 $data['rollback'] = $this->getRollback( $rcObj );
457 }
458
459 # Tags
460 $data['tags'] = $this->getTags( $rcObj, $classes );
461
462 # Watchlist labels
463 $labels = $this->getLabels( $rcObj, $classes );
464 if ( $includeLabels ) {
465 $data['labels'] = $labels;
466 }
467
468 $attribs = $this->getDataAttributes( $rcObj );
469
470 // give the hook a chance to modify the data
471 $success = $this->getHookRunner()->onEnhancedChangesListModifyLineData(
472 $this, $data, $block, $rcObj, $classes, $attribs );
473 if ( !$success ) {
474 // skip entry if hook aborted it
475 return [];
476 }
477 $attribs = array_filter( $attribs,
478 Sanitizer::isReservedDataAttribute( ... ),
479 ARRAY_FILTER_USE_KEY
480 );
481
482 $lineParams['recentChangesFlagsRaw'] = [];
483 if ( isset( $data['recentChangesFlags'] ) ) {
484 $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
485 # FIXME: This is used by logic, don't return it in the template params.
486 $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
487 unset( $data['recentChangesFlags'] );
488 }
489
490 if ( isset( $data['timestampLink'] ) ) {
491 $lineParams['timestampLink'] = $data['timestampLink'];
492 unset( $data['timestampLink'] );
493 }
494
495 $lineParams['classes'] = array_values( $classes );
496 $lineParams['attribs'] = Html::expandAttributes( $attribs );
497
498 // everything else: makes it easier for extensions to add or remove data
499 $lineParams['data'] = array_values( $data );
500
501 return $lineParams;
502 }
503
509 protected function getGroupLabels( array $block, array &$classes ): string {
510 if ( !$block ) {
511 return '';
512 }
513
514 $firstLabelIds = (string)( $block[0]->mAttribs[WatchlistLabelCondition::LABEL_IDS] ?? '' );
515 if ( $firstLabelIds === '' ) {
516 return '';
517 }
518
519 foreach ( $block as $rcObj ) {
520 $labelIds = (string)( $rcObj->mAttribs[WatchlistLabelCondition::LABEL_IDS] ?? '' );
521 if ( $labelIds !== $firstLabelIds ) {
522 return '';
523 }
524 }
525
526 return $this->getLabels( $block[0], $classes );
527 }
528
539 protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
540 if ( !$block ) {
541 return '';
542 }
543
544 // Changes message
545 static $nchanges = [];
546 static $sinceLastVisitMsg = [];
547
548 $n = count( $block );
549 if ( !isset( $nchanges[$n] ) ) {
550 $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
551 }
552
553 $sinceLast = 0;
554 $unvisitedOldid = null;
555 $currentRevision = 0;
556 $previousRevision = 0;
557 $curId = 0;
558 $allCategorization = true;
560 foreach ( $block as $rcObj ) {
561 // Fields of categorization entries refer to the changed page
562 // rather than the category for which we are building the log text.
563 if ( $rcObj->mAttribs['rc_source'] == RecentChange::SRC_CATEGORIZE ) {
564 continue;
565 }
566
567 $allCategorization = false;
568 $previousRevision = $rcObj->mAttribs['rc_last_oldid'];
569 // Same logic as below inside main foreach
570 if ( $rcObj->watched ) {
571 $sinceLast++;
572 $unvisitedOldid = $previousRevision;
573 }
574 if ( !$currentRevision ) {
575 $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
576 }
577 if ( !$curId ) {
578 $curId = $rcObj->mAttribs['rc_cur_id'];
579 }
580 }
581
582 // Total change link
583 $links = [];
584 $title = $block[0]->getTitle();
585 if ( !$allLogs ) {
586 // TODO: Disable the link if the user cannot see it (rc_deleted).
587 // Beware of possibly interspersed categorization entries.
588 if ( $isnew || $allCategorization ) {
589 $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
590 } else {
591 $links['total-changes'] = Html::rawElement( 'span', [],
592 $this->linkRenderer->makeKnownLink(
593 $title,
594 new HtmlArmor( $nchanges[$n] ),
595 [ 'class' => 'mw-changeslist-groupdiff' ],
596 $queryParams + [
597 'curid' => $curId,
598 'diff' => $currentRevision,
599 'oldid' => $previousRevision,
600 ]
601 )
602 );
603 }
604
605 if (
606 !$allCategorization &&
607 $sinceLast > 0 &&
608 $sinceLast < $n
609 ) {
610 if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
611 $sinceLastVisitMsg[$sinceLast] =
612 $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
613 }
614 $links['total-changes-since-last'] = Html::rawElement( 'span', [],
615 $this->linkRenderer->makeKnownLink(
616 $title,
617 new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
618 [ 'class' => 'mw-changeslist-groupdiff' ],
619 $queryParams + [
620 'curid' => $curId,
621 'diff' => $currentRevision,
622 'oldid' => $unvisitedOldid,
623 ]
624 )
625 );
626 }
627 }
628
629 // History
630 if ( $allLogs || $allCategorization ) {
631 // don't show history link for logs
632 } elseif ( $namehidden || !$title->exists() ) {
633 $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
634 } else {
635 $links['history'] = Html::rawElement( 'span', [],
636 $this->linkRenderer->makeKnownLink(
637 $title,
638 new HtmlArmor( $this->message['enhancedrc-history'] ),
639 [ 'class' => 'mw-changeslist-history' ],
640 [
641 'curid' => $curId,
642 'action' => 'history',
643 ] + $queryParams
644 )
645 );
646 }
647
648 // Allow others to alter, remove or add to these links
649 $this->getHookRunner()->onEnhancedChangesList__getLogText( $this, $links, $block );
650
651 if ( !$links ) {
652 return '';
653 }
654
655 $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
656 implode( ' ', $links ) );
657 return ' ' . $logtext;
658 }
659
666 protected function recentChangesBlockLine( $rcObj ) {
667 $data = [];
668
669 $source = $rcObj->mAttribs['rc_source'];
670 $logType = $rcObj->mAttribs['rc_log_type'];
671 $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
672 $classes[] = 'mw-enhanced-rc';
673
674 if ( $logType ) {
675 # Log entry
676 $classes[] = 'mw-changeslist-log';
677 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
678 } else {
679 $classes[] = 'mw-changeslist-edit';
680 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
681 $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
682 }
683
684 # Flag and Timestamp
685 $data['recentChangesFlags'] = [
686 'newpage' => $source == RecentChange::SRC_NEW,
687 'minor' => $rcObj->mAttribs['rc_minor'],
688 'unpatrolled' => $rcObj->unpatrolled,
689 'bot' => $rcObj->mAttribs['rc_bot'],
690 ];
691 // timestamp is not really a link here, but is called timestampLink
692 // for consistency with EnhancedChangesListModifyLineData
693 $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
694
695 # Article or log link
696 if ( $logType ) {
697 $logPage = new LogPage( $logType );
698 $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
699 $logName = $logPage->getName()->text();
700 $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
701 $this->linkRenderer->makeKnownLink( $logTitle, $logName )
702 );
703 } else {
704 $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
705 }
706
707 # Diff and hist links
709 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
710 }
711 $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
712
713 # Character diff
714 if ( $this->getConfig()->get( MainConfigNames::RCShowChangedSize ) ) {
715 $cd = $this->formatCharacterDifference( $rcObj );
716 if ( $cd !== '' ) {
717 $data['characterDiff'] = $cd;
718 $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
719 }
720 }
721
723 $data['logEntry'] = $this->insertLogEntry( $rcObj );
724 } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
725 $data['comment'] = $this->insertComment( $rcObj );
726 } else {
727 $data['userLink'] = $rcObj->userlink;
728 $data['userTalkLink'] = $rcObj->usertalklink;
729 $data['comment'] = $this->insertComment( $rcObj );
731 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
732 }
733 $data['rollback'] = $this->getRollback( $rcObj );
734 }
735
736 # Tags
737 $data['tags'] = $this->getTags( $rcObj, $classes );
738
739 # WatchlistLabels
740 $data['labels'] = $this->getLabels( $rcObj, $classes );
741
742 # Show how many people are watching this if enabled
743 $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
744
745 $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
746
747 // give the hook a chance to modify the data
748 $success = $this->getHookRunner()->onEnhancedChangesListModifyBlockLineData(
749 $this, $data, $rcObj );
750 if ( !$success ) {
751 // skip entry if hook aborted it
752 return '';
753 }
754 $attribs = $data['attribs'];
755 unset( $data['attribs'] );
756 $attribs = array_filter( $attribs, static function ( $key ) {
757 return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
758 }, ARRAY_FILTER_USE_KEY );
759
760 $prefix = '';
761 if ( is_callable( $this->changeLinePrefixer ) ) {
762 $prefix = ( $this->changeLinePrefixer )( $rcObj, $this, false );
763 }
764
765 $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
766 // Highlight block
767 $line .= Html::rawElement( 'td', [],
768 $this->getHighlightsContainerDiv()
769 );
770
771 $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
772 $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
773 $line .= '<td class="mw-enhanced-rc" colspan="2">';
774
775 if ( isset( $data['recentChangesFlags'] ) ) {
776 $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
777 unset( $data['recentChangesFlags'] );
778 }
779
780 if ( isset( $data['timestampLink'] ) ) {
781 $line .= "\u{00A0}" . $data['timestampLink'];
782 unset( $data['timestampLink'] );
783 }
784
785 $titleText = $rcObj->getTitle();
786 if ( !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
787 $titleText = $this->msg( 'rev-deleted-event' )->escaped();
788 }
789 $line .= "\u{00A0}</td>";
790 $line .= Html::openElement( 'td', [
791 'class' => 'mw-changeslist-line-inner',
792 // Used for reliable determination of the affiliated page
793 'data-target-page' => $titleText,
794 ] );
795
796 // everything else: makes it easier for extensions to add or remove data
797 foreach ( $data as $key => $dataItem ) {
798 $line .= Html::rawElement( 'span', [
799 'class' => 'mw-changeslist-line-inner-' . $key,
800 ], $dataItem );
801 }
802
803 $line .= "</td></tr></table>\n";
804
805 return $line;
806 }
807
819 public function getDiffHistLinks( RCCacheEntry $rc, $query = null, $useParentheses = null ) {
820 if ( is_bool( $query ) ) {
821 $useParentheses = $query;
822 } elseif ( $query !== null ) {
823 wfDeprecated( __METHOD__ . ' with $query parameter', '1.36' );
824 }
825 $pageTitle = $rc->getTitle();
826 if ( $rc->getAttribute( 'rc_source' ) == RecentChange::SRC_CATEGORIZE ) {
827 // For categorizations we must swap the category title with the page title!
828 $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
829 if ( !$pageTitle ) {
830 // The page has been deleted, but the RC entry
831 // deletion job has not run yet. Just skip.
832 return '';
833 }
834 }
835
836 $histLink = $this->linkRenderer->makeKnownLink(
837 $pageTitle,
838 new HtmlArmor( $this->message['hist'] ),
839 [ 'class' => 'mw-changeslist-history' ],
840 [
841 'curid' => $rc->getAttribute( 'rc_cur_id' ),
842 'action' => 'history'
843 ]
844 );
845 if ( $useParentheses !== false ) {
846 $retVal = $this->msg( 'parentheses' )
847 ->rawParams( $rc->difflink . $this->message['pipe-separator']
848 . $histLink )->escaped();
849 } else {
850 $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
851 Html::rawElement( 'span', [], $rc->difflink ) .
852 Html::rawElement( 'span', [], $histLink )
853 );
854 }
855 return ' ' . $retVal;
856 }
857
864 protected function recentChangesBlock() {
865 if ( count( $this->rc_cache ) == 0 ) {
866 return '';
867 }
868
869 $blockOut = '';
870 foreach ( $this->rc_cache as $block ) {
871 if ( count( $block ) < 2 ) {
872 $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
873 } else {
874 $blockOut .= $this->recentChangesBlockGroup( $block );
875 }
876 }
877
878 if ( $blockOut === '' ) {
879 return '';
880 }
881 // $this->lastdate is kept up to date by recentChangesLine()
882 return Html::element( 'h4', [], $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
883 }
884
890 public function endRecentChangesList() {
891 return $this->recentChangesBlock() . '</div>';
892 }
893}
894
896class_alias( EnhancedChangesList::class, 'EnhancedChangesList' );
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
Handles compiling Mustache templates into PHP rendering functions.
Class to simplify the use of log pages.
Definition LogPage.php:34
A class containing constants representing the names of configuration variables.
const RecentChangesFlags
Name constant for the RecentChangesFlags setting, for use with Config::get()
const RCShowChangedSize
Name constant for the RCShowChangedSize setting, for use with Config::get()
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:34
Base class for lists of recent changes shown on special pages.
numberofWatchingusers( $count)
Returns the string which indicates the number of watching users.
getHTMLClasses( $rc, $watched)
Get an array of default HTML class attributes for the change.
getTags(RecentChange $rc, array &$classes)
getLabels(RecentChange $rc, &$classes)
insertLogEntry( $rc)
Insert a formatted action.
maybeWatchedLink( $link, $watched=false)
getArticleLink(&$rc, $unpatrolled, $watched)
Get the HTML link to the changed page, possibly with a prefix from hook handlers, and a suffix for te...
recentChangesFlags( $flags, $nothing="\u{00A0}")
Returns the appropriate flags for new page, minor change and patrolling.
insertComment( $rc)
Insert a formatted comment.
ChangesListFilterGroupContainer $filterGroups
static userCan( $rc, $field, Authority $performer)
Determine if the given user is allowed to view a particular field of this revision,...
isCategorizationWithoutRevision( $rcObj)
Determines whether a revision is linked to this change; this may not be the case when the categorizat...
getHTMLClassesForFilters( $rc)
Get an array of CSS classes attributed to filters for this row.
formatCharacterDifference(RecentChange $old, ?RecentChange $new=null)
Format the character difference of one or several changes.
getDataAttributes(RecentChange $rc)
Get recommended data attributes for a change line.
Generate a list of changes using an Enhanced system (uses javascript).
getDiffHistLinks(RCCacheEntry $rc, $query=null, $useParentheses=null)
Returns value to be used in 'historyLink' element of $data param in EnhancedChangesListModifyBlockLin...
recentChangesBlock()
If enhanced RC is in use, this function takes the previously cached RC lines, arranges them,...
getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden)
Generates amount of changes (linking to diff ) & link to history.
__construct( $context, ?ChangesListFilterGroupContainer $filterGroups=null)
recentChangesBlockLine( $rcObj)
Enhanced RC ungrouped line.
recentChangesLine(&$rc, $watched=false, $linenumber=null)
Format a line for enhanced recentchange (aka with javascript and block of lines).
beginRecentChangesList()
Add the JavaScript file for enhanced changeslist.
endRecentChangesList()
Returns text for the end of RC If enhanced RC is in use, returns pretty much all the text.
getLineData(array $block, RCCacheEntry $rcObj, array $queryParams=[], bool $includeLabels=true)
addCacheEntry(RCCacheEntry $cacheEntry)
Put accumulated information into the cache, for later display.
Create a RCCacheEntry from a RecentChange to use in EnhancedChangesList.
getAttribute( $name)
Get an attribute value.
Page revision base class.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a title within MediaWiki.
Definition Title.php:69
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:18
Interface for objects which can provide a MediaWiki context on request.
$source
element(SerializerNode $parent, SerializerNode $node, $contents)