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