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