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 );
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 $logText = $this->getLogText( $block, [], $allLogs,
291 $collectedRcFlags['newpage'], $namehidden
292 );
293
294 # Character difference (does not apply if only log items)
295 $charDifference = false;
296 if ( $RCShowChangedSize && !$allLogs ) {
297 $last = 0;
298 $first = count( $block ) - 1;
299 # Some events (like logs and category changes) have an "empty" size, so we need to skip those...
300 while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
301 $last++;
302 }
303 while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) {
304 $first--;
305 }
306 # Get net change
307 $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ) ?: false;
308 }
309
310 $numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
311 $usersList = $this->msg( 'brackets' )->rawParams(
312 implode( $this->message['semicolon-separator'], $users )
313 )->escaped();
314
315 $prefix = '';
316 if ( is_callable( $this->changeLinePrefixer ) ) {
317 $prefix = ( $this->changeLinePrefixer )( $block[0], $this, true );
318 }
319
320 $templateParams = [
321 'checkboxId' => 'mw-checkbox-' . base64_encode( random_bytes( 3 ) ),
322 'articleLink' => $articleLink,
323 'charDifference' => $charDifference,
324 'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ),
325 'filterClasses' => $filterClasses,
326 'lines' => $lines,
327 'logText' => $logText,
328 'numberofWatchingusers' => $numberofWatchingusers,
329 'prefix' => $prefix,
330 'rev-deleted-event' => $revDeletedMsg,
331 'tableClasses' => $tableClasses,
332 'timestamp' => $block[0]->timestamp,
333 'fullTimestamp' => $block[0]->getAttribute( 'rc_timestamp' ),
334 'users' => $usersList,
335 ];
336
337 $this->rcCacheIndex++;
338
339 return $this->templateParser->processTemplate(
340 'EnhancedChangesListGroup',
341 $templateParams
342 );
343 }
344
351 protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
352 $RCShowChangedSize = $this->getConfig()->get( MainConfigNames::RCShowChangedSize );
353
354 $source = $rcObj->mAttribs['rc_source'];
355 $data = [];
356 $lineParams = [ 'targetTitle' => $rcObj->getTitle() ];
357
358 $classes = [ 'mw-enhanced-rc' ];
359 if ( $rcObj->watched ) {
360 $classes[] = 'mw-enhanced-watched';
361 }
362 $classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
363
364 $separator = ' <span class="mw-changeslist-separator"></span> ';
365
366 $data['recentChangesFlags'] = [
367 'newpage' => $source == RecentChange::SRC_NEW,
368 'minor' => $rcObj->mAttribs['rc_minor'],
369 'unpatrolled' => $rcObj->unpatrolled,
370 'bot' => $rcObj->mAttribs['rc_bot'],
371 ];
372
373 # Log timestamp
375 $link = htmlspecialchars( $rcObj->timestamp );
376 # Revision link
377 } elseif ( !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
378 $link = Html::element( 'span', [ 'class' => 'history-deleted' ], $rcObj->timestamp );
379 } else {
380 $params = [];
381 $params['curid'] = $rcObj->mAttribs['rc_cur_id'];
382 if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
383 $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
384 }
385 // FIXME: The link has incorrect "title=" when rc_source = RecentChange::SRC_CATEGORIZE.
386 // rc_cur_id refers to the page that was categorized
387 // whereas RecentChange::getTitle refers to the category.
388 $link = $this->linkRenderer->makeKnownLink(
389 $rcObj->getTitle(),
390 $rcObj->timestamp,
391 [],
392 $params + $queryParams
393 );
394 if ( static::isDeleted( $rcObj, RevisionRecord::DELETED_TEXT ) ) {
395 $link = '<span class="history-deleted">' . $link . '</span> ';
396 }
397 }
398 $data['timestampLink'] = $link;
399
400 $currentAndLastLinks = '';
402 $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
403 $rcObj->curlink .
404 $this->message['pipe-separator'] .
405 $rcObj->lastlink
406 )->escaped();
407 }
408 $data['currentAndLastLinks'] = $currentAndLastLinks;
409 $data['separatorAfterCurrentAndLastLinks'] = $separator;
410
411 # Character diff
412 if ( $RCShowChangedSize ) {
413 $cd = $this->formatCharacterDifference( $rcObj );
414 if ( $cd !== '' ) {
415 $data['characterDiff'] = $cd;
416 $data['separatorAfterCharacterDiff'] = $separator;
417 }
418 }
419
421 $data['logEntry'] = $this->insertLogEntry( $rcObj );
422 } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
423 $data['comment'] = $this->insertComment( $rcObj );
424 } else {
425 # User links
426 $data['userLink'] = $rcObj->userlink;
427 $data['userTalkLink'] = $rcObj->usertalklink;
428 $data['comment'] = $this->insertComment( $rcObj );
430 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
431 }
432 # Rollback, thanks etc...
433 $data['rollback'] = $this->getRollback( $rcObj );
434 }
435
436 # Tags
437 $data['tags'] = $this->getTags( $rcObj, $classes );
438
439 $attribs = $this->getDataAttributes( $rcObj );
440
441 // give the hook a chance to modify the data
442 $success = $this->getHookRunner()->onEnhancedChangesListModifyLineData(
443 $this, $data, $block, $rcObj, $classes, $attribs );
444 if ( !$success ) {
445 // skip entry if hook aborted it
446 return [];
447 }
448 $attribs = array_filter( $attribs,
449 Sanitizer::isReservedDataAttribute( ... ),
450 ARRAY_FILTER_USE_KEY
451 );
452
453 $lineParams['recentChangesFlagsRaw'] = [];
454 if ( isset( $data['recentChangesFlags'] ) ) {
455 $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
456 # FIXME: This is used by logic, don't return it in the template params.
457 $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
458 unset( $data['recentChangesFlags'] );
459 }
460
461 if ( isset( $data['timestampLink'] ) ) {
462 $lineParams['timestampLink'] = $data['timestampLink'];
463 unset( $data['timestampLink'] );
464 }
465
466 $lineParams['classes'] = array_values( $classes );
467 $lineParams['attribs'] = Html::expandAttributes( $attribs );
468
469 // everything else: makes it easier for extensions to add or remove data
470 $lineParams['data'] = array_values( $data );
471
472 return $lineParams;
473 }
474
485 protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
486 if ( !$block ) {
487 return '';
488 }
489
490 // Changes message
491 static $nchanges = [];
492 static $sinceLastVisitMsg = [];
493
494 $n = count( $block );
495 if ( !isset( $nchanges[$n] ) ) {
496 $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
497 }
498
499 $sinceLast = 0;
500 $unvisitedOldid = null;
501 $currentRevision = 0;
502 $previousRevision = 0;
503 $curId = 0;
504 $allCategorization = true;
506 foreach ( $block as $rcObj ) {
507 // Fields of categorization entries refer to the changed page
508 // rather than the category for which we are building the log text.
509 if ( $rcObj->mAttribs['rc_source'] == RecentChange::SRC_CATEGORIZE ) {
510 continue;
511 }
512
513 $allCategorization = false;
514 $previousRevision = $rcObj->mAttribs['rc_last_oldid'];
515 // Same logic as below inside main foreach
516 if ( $rcObj->watched ) {
517 $sinceLast++;
518 $unvisitedOldid = $previousRevision;
519 }
520 if ( !$currentRevision ) {
521 $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
522 }
523 if ( !$curId ) {
524 $curId = $rcObj->mAttribs['rc_cur_id'];
525 }
526 }
527
528 // Total change link
529 $links = [];
530 $title = $block[0]->getTitle();
531 if ( !$allLogs ) {
532 // TODO: Disable the link if the user cannot see it (rc_deleted).
533 // Beware of possibly interspersed categorization entries.
534 if ( $isnew || $allCategorization ) {
535 $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
536 } else {
537 $links['total-changes'] = Html::rawElement( 'span', [],
538 $this->linkRenderer->makeKnownLink(
539 $title,
540 new HtmlArmor( $nchanges[$n] ),
541 [ 'class' => 'mw-changeslist-groupdiff' ],
542 $queryParams + [
543 'curid' => $curId,
544 'diff' => $currentRevision,
545 'oldid' => $previousRevision,
546 ]
547 )
548 );
549 }
550
551 if (
552 !$allCategorization &&
553 $sinceLast > 0 &&
554 $sinceLast < $n
555 ) {
556 if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
557 $sinceLastVisitMsg[$sinceLast] =
558 $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
559 }
560 $links['total-changes-since-last'] = Html::rawElement( 'span', [],
561 $this->linkRenderer->makeKnownLink(
562 $title,
563 new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
564 [ 'class' => 'mw-changeslist-groupdiff' ],
565 $queryParams + [
566 'curid' => $curId,
567 'diff' => $currentRevision,
568 'oldid' => $unvisitedOldid,
569 ]
570 )
571 );
572 }
573 }
574
575 // History
576 if ( $allLogs || $allCategorization ) {
577 // don't show history link for logs
578 } elseif ( $namehidden || !$title->exists() ) {
579 $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
580 } else {
581 $links['history'] = Html::rawElement( 'span', [],
582 $this->linkRenderer->makeKnownLink(
583 $title,
584 new HtmlArmor( $this->message['enhancedrc-history'] ),
585 [ 'class' => 'mw-changeslist-history' ],
586 [
587 'curid' => $curId,
588 'action' => 'history',
589 ] + $queryParams
590 )
591 );
592 }
593
594 // Allow others to alter, remove or add to these links
595 $this->getHookRunner()->onEnhancedChangesList__getLogText( $this, $links, $block );
596
597 if ( !$links ) {
598 return '';
599 }
600
601 $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
602 implode( ' ', $links ) );
603 return ' ' . $logtext;
604 }
605
612 protected function recentChangesBlockLine( $rcObj ) {
613 $data = [];
614
615 $source = $rcObj->mAttribs['rc_source'];
616 $logType = $rcObj->mAttribs['rc_log_type'];
617 $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
618 $classes[] = 'mw-enhanced-rc';
619
620 if ( $logType ) {
621 # Log entry
622 $classes[] = 'mw-changeslist-log';
623 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
624 } else {
625 $classes[] = 'mw-changeslist-edit';
626 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
627 $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
628 }
629
630 # Flag and Timestamp
631 $data['recentChangesFlags'] = [
632 'newpage' => $source == RecentChange::SRC_NEW,
633 'minor' => $rcObj->mAttribs['rc_minor'],
634 'unpatrolled' => $rcObj->unpatrolled,
635 'bot' => $rcObj->mAttribs['rc_bot'],
636 ];
637 // timestamp is not really a link here, but is called timestampLink
638 // for consistency with EnhancedChangesListModifyLineData
639 $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
640
641 # Article or log link
642 if ( $logType ) {
643 $logPage = new LogPage( $logType );
644 $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
645 $logName = $logPage->getName()->text();
646 $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
647 $this->linkRenderer->makeKnownLink( $logTitle, $logName )
648 );
649 } else {
650 $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
651 }
652
653 # Diff and hist links
655 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
656 }
657 $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
658
659 # Character diff
660 if ( $this->getConfig()->get( MainConfigNames::RCShowChangedSize ) ) {
661 $cd = $this->formatCharacterDifference( $rcObj );
662 if ( $cd !== '' ) {
663 $data['characterDiff'] = $cd;
664 $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
665 }
666 }
667
669 $data['logEntry'] = $this->insertLogEntry( $rcObj );
670 } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
671 $data['comment'] = $this->insertComment( $rcObj );
672 } else {
673 $data['userLink'] = $rcObj->userlink;
674 $data['userTalkLink'] = $rcObj->usertalklink;
675 $data['comment'] = $this->insertComment( $rcObj );
677 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
678 }
679 $data['rollback'] = $this->getRollback( $rcObj );
680 }
681
682 # Tags
683 $data['tags'] = $this->getTags( $rcObj, $classes );
684
685 # Show how many people are watching this if enabled
686 $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
687
688 $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
689
690 // give the hook a chance to modify the data
691 $success = $this->getHookRunner()->onEnhancedChangesListModifyBlockLineData(
692 $this, $data, $rcObj );
693 if ( !$success ) {
694 // skip entry if hook aborted it
695 return '';
696 }
697 $attribs = $data['attribs'];
698 unset( $data['attribs'] );
699 $attribs = array_filter( $attribs, static function ( $key ) {
700 return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
701 }, ARRAY_FILTER_USE_KEY );
702
703 $prefix = '';
704 if ( is_callable( $this->changeLinePrefixer ) ) {
705 $prefix = ( $this->changeLinePrefixer )( $rcObj, $this, false );
706 }
707
708 $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
709 // Highlight block
710 $line .= Html::rawElement( 'td', [],
712 );
713
714 $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
715 $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
716 $line .= '<td class="mw-enhanced-rc" colspan="2">';
717
718 if ( isset( $data['recentChangesFlags'] ) ) {
719 $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
720 unset( $data['recentChangesFlags'] );
721 }
722
723 if ( isset( $data['timestampLink'] ) ) {
724 $line .= "\u{00A0}" . $data['timestampLink'];
725 unset( $data['timestampLink'] );
726 }
727 $line .= "\u{00A0}</td>";
728 $line .= Html::openElement( 'td', [
729 'class' => 'mw-changeslist-line-inner',
730 // Used for reliable determination of the affiliated page
731 'data-target-page' => $rcObj->getTitle(),
732 ] );
733
734 // everything else: makes it easier for extensions to add or remove data
735 foreach ( $data as $key => $dataItem ) {
736 $line .= Html::rawElement( 'span', [
737 'class' => 'mw-changeslist-line-inner-' . $key,
738 ], $dataItem );
739 }
740
741 $line .= "</td></tr></table>\n";
742
743 return $line;
744 }
745
757 public function getDiffHistLinks( RCCacheEntry $rc, $query = null, $useParentheses = null ) {
758 if ( is_bool( $query ) ) {
759 $useParentheses = $query;
760 } elseif ( $query !== null ) {
761 wfDeprecated( __METHOD__ . ' with $query parameter', '1.36' );
762 }
763 $pageTitle = $rc->getTitle();
764 if ( $rc->getAttribute( 'rc_source' ) == RecentChange::SRC_CATEGORIZE ) {
765 // For categorizations we must swap the category title with the page title!
766 $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
767 if ( !$pageTitle ) {
768 // The page has been deleted, but the RC entry
769 // deletion job has not run yet. Just skip.
770 return '';
771 }
772 }
773
774 $histLink = $this->linkRenderer->makeKnownLink(
775 $pageTitle,
776 new HtmlArmor( $this->message['hist'] ),
777 [ 'class' => 'mw-changeslist-history' ],
778 [
779 'curid' => $rc->getAttribute( 'rc_cur_id' ),
780 'action' => 'history'
781 ]
782 );
783 if ( $useParentheses !== false ) {
784 $retVal = $this->msg( 'parentheses' )
785 ->rawParams( $rc->difflink . $this->message['pipe-separator']
786 . $histLink )->escaped();
787 } else {
788 $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
789 Html::rawElement( 'span', [], $rc->difflink ) .
790 Html::rawElement( 'span', [], $histLink )
791 );
792 }
793 return ' ' . $retVal;
794 }
795
802 protected function recentChangesBlock() {
803 if ( count( $this->rc_cache ) == 0 ) {
804 return '';
805 }
806
807 $blockOut = '';
808 foreach ( $this->rc_cache as $block ) {
809 if ( count( $block ) < 2 ) {
810 $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
811 } else {
812 $blockOut .= $this->recentChangesBlockGroup( $block );
813 }
814 }
815
816 if ( $blockOut === '' ) {
817 return '';
818 }
819 // $this->lastdate is kept up to date by recentChangesLine()
820 return Html::element( 'h4', [], $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
821 }
822
828 public function endRecentChangesList() {
829 return $this->recentChangesBlock() . '</div>';
830 }
831}
832
834class_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)
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=[])
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)