MediaWiki  master
EnhancedChangesList.php
Go to the documentation of this file.
1 <?php
2 
10 
33 
37  protected $cacheEntryFactory;
38 
42  protected $rc_cache;
43 
47  protected $templateParser;
48 
53  public function __construct( $context, array $filterGroups = [] ) {
54  parent::__construct( $context, $filterGroups );
55 
56  // message is set by the parent ChangesList class
57  $this->cacheEntryFactory = new RCCacheEntryFactory(
58  $context,
59  $this->message,
60  $this->linkRenderer
61  );
62  $this->templateParser = new TemplateParser();
63  }
64 
69  public function beginRecentChangesList() {
70  $this->getOutput()->addModuleStyles( [
71  'mediawiki.special.changeslist.enhanced',
72  ] );
73 
74  parent::beginRecentChangesList();
75  return '<div class="mw-changeslist" aria-live="polite">';
76  }
77 
87  public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
88  $date = $this->getLanguage()->userDate(
89  $rc->mAttribs['rc_timestamp'],
90  $this->getUser()
91  );
92  if ( $this->lastdate === '' ) {
93  $this->lastdate = $date;
94  }
95 
96  $ret = '';
97 
98  # If it's a new day, flush the cache and update $this->lastdate
99  if ( $date !== $this->lastdate ) {
100  # Process current cache (uses $this->lastdate to generate a heading)
101  $ret = $this->recentChangesBlock();
102  $this->rc_cache = [];
103  $this->lastdate = $date;
104  }
105 
106  $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched );
107  $this->addCacheEntry( $cacheEntry );
108 
109  return $ret;
110  }
111 
118  protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
119  $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
120  $this->rc_cache[$cacheGroupingKey][] = $cacheEntry;
121  }
122 
130  protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
131  $title = $cacheEntry->getTitle();
132  $cacheGroupingKey = $title->getPrefixedDBkey();
133 
134  $type = $cacheEntry->mAttribs['rc_type'];
135 
136  if ( $type == RC_LOG ) {
137  // Group by log type
138  $cacheGroupingKey = SpecialPage::getTitleFor(
139  'Log',
140  $cacheEntry->mAttribs['rc_log_type']
141  )->getPrefixedDBkey();
142  }
143 
144  return $cacheGroupingKey;
145  }
146 
152  protected function recentChangesBlockGroup( $block ) {
153  $recentChangesFlags = $this->getConfig()->get( MainConfigNames::RecentChangesFlags );
154 
155  # Add the namespace and title of the block as part of the class
156  $tableClasses = [ 'mw-enhanced-rc', 'mw-changeslist-line' ];
157  if ( $block[0]->mAttribs['rc_log_type'] ) {
158  # Log entry
159  $tableClasses[] = 'mw-changeslist-log';
160  $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
161  . $block[0]->mAttribs['rc_log_type'] );
162  } else {
163  $tableClasses[] = 'mw-changeslist-edit';
164  $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
165  . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
166  }
167  if ( $block[0]->watched ) {
168  $tableClasses[] = 'mw-changeslist-line-watched';
169  } else {
170  $tableClasses[] = 'mw-changeslist-line-not-watched';
171  }
172 
173  # Collate list of users
174  $usercounts = [];
175  $userlinks = [];
176  # Some catalyst variables...
177  $namehidden = true;
178  $allLogs = true;
179  $RCShowChangedSize = $this->getConfig()->get( MainConfigNames::RCShowChangedSize );
180 
181  # Default values for RC flags
182  $collectedRcFlags = [];
183  foreach ( $recentChangesFlags as $key => $value ) {
184  $flagGrouping = $value['grouping'] ?? 'any';
185  switch ( $flagGrouping ) {
186  case 'all':
187  $collectedRcFlags[$key] = true;
188  break;
189  case 'any':
190  $collectedRcFlags[$key] = false;
191  break;
192  default:
193  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
194  }
195  }
196  foreach ( $block as $rcObj ) {
197  // If all log actions to this page were hidden, then don't
198  // give the name of the affected page for this block!
199  if ( !static::isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
200  $namehidden = false;
201  }
202  $username = $rcObj->getPerformerIdentity()->getName();
203  $userlink = $rcObj->userlink;
204  if ( !isset( $usercounts[$username] ) ) {
205  $usercounts[$username] = 0;
206  $userlinks[$username] = $userlink;
207  }
208  if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
209  $allLogs = false;
210  }
211 
212  $usercounts[$username]++;
213  }
214 
215  # Sort the list and convert to text
216  krsort( $usercounts );
217  asort( $usercounts );
218  $users = [];
219  foreach ( $usercounts as $username => $count ) {
220  $text = $userlinks[$username];
221  $text .= $this->getLanguage()->getDirMark();
222  if ( $count > 1 ) {
223  $formattedCount = $this->msg( 'ntimes' )->numParams( $count )->escaped();
224  $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
225  }
226  $users[] = $text;
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 = call_user_func( $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  'languageDirMark' => $this->getLanguage()->getDirMark(),
327  'lines' => $lines,
328  'logText' => $logText,
329  'numberofWatchingusers' => $numberofWatchingusers,
330  'prefix' => $prefix,
331  'rev-deleted-event' => $revDeletedMsg,
332  'tableClasses' => $tableClasses,
333  'timestamp' => $block[0]->timestamp,
334  'fullTimestamp' => $block[0]->getAttribute( 'rc_timestamp' ),
335  'users' => $usersList,
336  ];
337 
338  $this->rcCacheIndex++;
339 
340  return $this->templateParser->processTemplate(
341  'EnhancedChangesListGroup',
342  $templateParams
343  );
344  }
345 
352  protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
353  $RCShowChangedSize = $this->getConfig()->get( MainConfigNames::RCShowChangedSize );
354 
355  $type = $rcObj->mAttribs['rc_type'];
356  $data = [];
357  $lineParams = [ 'targetTitle' => $rcObj->getTitle() ];
358 
359  $classes = [ 'mw-enhanced-rc' ];
360  if ( $rcObj->watched ) {
361  $classes[] = 'mw-enhanced-watched';
362  }
363  $classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
364 
365  $separator = ' <span class="mw-changeslist-separator"></span> ';
366 
367  $data['recentChangesFlags'] = [
368  'newpage' => $type == RC_NEW,
369  'minor' => $rcObj->mAttribs['rc_minor'],
370  'unpatrolled' => $rcObj->unpatrolled,
371  'bot' => $rcObj->mAttribs['rc_bot'],
372  ];
373 
374  # Log timestamp
375  if ( $type == RC_LOG ) {
376  $link = htmlspecialchars( $rcObj->timestamp );
377  # Revision link
378  } elseif ( !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
379  $link = Html::element( 'span', [ 'class' => 'history-deleted' ], $rcObj->timestamp );
380  } else {
381  $params = [];
382  $params['curid'] = $rcObj->mAttribs['rc_cur_id'];
383  if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
384  $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
385  }
386  // FIXME: The link has incorrect "title=" when rc_type = RC_CATEGORIZE.
387  // rc_cur_id refers to the page that was categorized
388  // whereas RecentChange::getTitle refers to the category.
389  $link = $this->linkRenderer->makeKnownLink(
390  $rcObj->getTitle(),
391  $rcObj->timestamp,
392  [],
393  $params + $queryParams
394  );
395  if ( static::isDeleted( $rcObj, RevisionRecord::DELETED_TEXT ) ) {
396  $link = '<span class="history-deleted">' . $link . '</span> ';
397  }
398  }
399  $data['timestampLink'] = $link;
400 
401  $currentAndLastLinks = '';
402  if ( $type == RC_EDIT || $type == RC_NEW ) {
403  $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
404  $rcObj->curlink .
405  $this->message['pipe-separator'] .
406  $rcObj->lastlink
407  )->escaped();
408  }
409  $data['currentAndLastLinks'] = $currentAndLastLinks;
410  $data['separatorAfterCurrentAndLastLinks'] = $separator;
411 
412  # Character diff
413  if ( $RCShowChangedSize ) {
414  $cd = $this->formatCharacterDifference( $rcObj );
415  if ( $cd !== '' ) {
416  $data['characterDiff'] = $cd;
417  $data['separatorAfterCharacterDiff'] = $separator;
418  }
419  }
420 
421  if ( $type == RC_LOG ) {
422  $data['logEntry'] = $this->insertLogEntry( $rcObj );
423  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
424  $data['comment'] = $this->insertComment( $rcObj );
425  } else {
426  # User links
427  $data['userLink'] = $rcObj->userlink;
428  $data['userTalkLink'] = $rcObj->usertalklink;
429  $data['comment'] = $this->insertComment( $rcObj );
430  if ( $type == RC_CATEGORIZE ) {
431  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
432  }
433  # Rollback, thanks etc...
434  $data['rollback'] = $this->getRollback( $rcObj );
435  }
436 
437  # Tags
438  $data['tags'] = $this->getTags( $rcObj, $classes );
439 
440  $attribs = $this->getDataAttributes( $rcObj );
441 
442  // give the hook a chance to modify the data
443  $success = $this->getHookRunner()->onEnhancedChangesListModifyLineData(
444  $this, $data, $block, $rcObj, $classes, $attribs );
445  if ( !$success ) {
446  // skip entry if hook aborted it
447  return [];
448  }
449  $attribs = array_filter( $attribs,
450  [ Sanitizer::class, 'isReservedDataAttribute' ],
451  ARRAY_FILTER_USE_KEY
452  );
453 
454  $lineParams['recentChangesFlagsRaw'] = [];
455  if ( isset( $data['recentChangesFlags'] ) ) {
456  $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
457  # FIXME: This is used by logic, don't return it in the template params.
458  $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
459  unset( $data['recentChangesFlags'] );
460  }
461 
462  if ( isset( $data['timestampLink'] ) ) {
463  $lineParams['timestampLink'] = $data['timestampLink'];
464  unset( $data['timestampLink'] );
465  }
466 
467  $lineParams['classes'] = array_values( $classes );
468  $lineParams['attribs'] = Html::expandAttributes( $attribs );
469 
470  // everything else: makes it easier for extensions to add or remove data
471  $lineParams['data'] = array_values( $data );
472 
473  return $lineParams;
474  }
475 
486  protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
487  if ( !$block ) {
488  return '';
489  }
490 
491  // Changes message
492  static $nchanges = [];
493  static $sinceLastVisitMsg = [];
494 
495  $n = count( $block );
496  if ( !isset( $nchanges[$n] ) ) {
497  $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
498  }
499 
500  $sinceLast = 0;
501  $unvisitedOldid = null;
502  $currentRevision = 0;
503  $previousRevision = 0;
504  $curId = 0;
505  $allCategorization = true;
507  foreach ( $block as $rcObj ) {
508  // Fields of categorization entries refer to the changed page
509  // rather than the category for which we are building the log text.
510  if ( $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
511  continue;
512  }
513 
514  $allCategorization = false;
515  $previousRevision = $rcObj->mAttribs['rc_last_oldid'];
516  // Same logic as below inside main foreach
517  if ( $rcObj->watched ) {
518  $sinceLast++;
519  $unvisitedOldid = $previousRevision;
520  }
521  if ( !$currentRevision ) {
522  $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
523  }
524  if ( !$curId ) {
525  $curId = $rcObj->mAttribs['rc_cur_id'];
526  }
527  }
528 
529  // Total change link
530  $links = [];
531  $title = $block[0]->getTitle();
532  if ( !$allLogs ) {
533  // TODO: Disable the link if the user cannot see it (rc_deleted).
534  // Beware of possibly interspersed categorization entries.
535  if ( $isnew || $allCategorization ) {
536  $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
537  } else {
538  $links['total-changes'] = Html::rawElement( 'span', [],
539  $this->linkRenderer->makeKnownLink(
540  $title,
541  new HtmlArmor( $nchanges[$n] ),
542  [ 'class' => 'mw-changeslist-groupdiff' ],
543  $queryParams + [
544  'curid' => $curId,
545  'diff' => $currentRevision,
546  'oldid' => $previousRevision,
547  ]
548  )
549  );
550  }
551 
552  if (
553  !$allCategorization &&
554  $sinceLast > 0 &&
555  $sinceLast < $n
556  ) {
557  if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
558  $sinceLastVisitMsg[$sinceLast] =
559  $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
560  }
561  $links['total-changes-since-last'] = Html::rawElement( 'span', [],
562  $this->linkRenderer->makeKnownLink(
563  $title,
564  new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
565  [ 'class' => 'mw-changeslist-groupdiff' ],
566  $queryParams + [
567  'curid' => $curId,
568  'diff' => $currentRevision,
569  'oldid' => $unvisitedOldid,
570  ]
571  )
572  );
573  }
574  }
575 
576  // History
577  if ( $allLogs || $allCategorization ) {
578  // don't show history link for logs
579  } elseif ( $namehidden || !$title->exists() ) {
580  $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
581  } else {
582  $links['history'] = Html::rawElement( 'span', [],
583  $this->linkRenderer->makeKnownLink(
584  $title,
585  new HtmlArmor( $this->message['enhancedrc-history'] ),
586  [ 'class' => 'mw-changeslist-history' ],
587  [
588  'curid' => $curId,
589  'action' => 'history',
590  ] + $queryParams
591  )
592  );
593  }
594 
595  // Allow others to alter, remove or add to these links
596  $this->getHookRunner()->onEnhancedChangesList__getLogText( $this, $links, $block );
597 
598  if ( !$links ) {
599  return '';
600  }
601 
602  $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
603  implode( ' ', $links ) );
604  return ' ' . $logtext;
605  }
606 
613  protected function recentChangesBlockLine( $rcObj ) {
614  $data = [];
615 
616  $type = $rcObj->mAttribs['rc_type'];
617  $logType = $rcObj->mAttribs['rc_log_type'];
618  $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
619  $classes[] = 'mw-enhanced-rc';
620 
621  if ( $logType ) {
622  # Log entry
623  $classes[] = 'mw-changeslist-log';
624  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
625  } else {
626  $classes[] = 'mw-changeslist-edit';
627  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
628  $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
629  }
630 
631  # Flag and Timestamp
632  $data['recentChangesFlags'] = [
633  'newpage' => $type == RC_NEW,
634  'minor' => $rcObj->mAttribs['rc_minor'],
635  'unpatrolled' => $rcObj->unpatrolled,
636  'bot' => $rcObj->mAttribs['rc_bot'],
637  ];
638  // timestamp is not really a link here, but is called timestampLink
639  // for consistency with EnhancedChangesListModifyLineData
640  $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
641 
642  # Article or log link
643  if ( $logType ) {
644  $logPage = new LogPage( $logType );
645  $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
646  $logName = $logPage->getName()->text();
647  $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
648  $this->linkRenderer->makeKnownLink( $logTitle, $logName )
649  );
650  } else {
651  $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
652  }
653 
654  # Diff and hist links
655  if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
656  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
657  }
658  $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
659 
660  # Character diff
661  if ( $this->getConfig()->get( MainConfigNames::RCShowChangedSize ) ) {
662  $cd = $this->formatCharacterDifference( $rcObj );
663  if ( $cd !== '' ) {
664  $data['characterDiff'] = $cd;
665  $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
666  }
667  }
668 
669  if ( $type == RC_LOG ) {
670  $data['logEntry'] = $this->insertLogEntry( $rcObj );
671  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
672  $data['comment'] = $this->insertComment( $rcObj );
673  } else {
674  $data['userLink'] = $rcObj->userlink;
675  $data['userTalkLink'] = $rcObj->usertalklink;
676  $data['comment'] = $this->insertComment( $rcObj );
677  if ( $type == RC_CATEGORIZE ) {
678  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, false );
679  }
680  $data['rollback'] = $this->getRollback( $rcObj );
681  }
682 
683  # Tags
684  $data['tags'] = $this->getTags( $rcObj, $classes );
685 
686  # Show how many people are watching this if enabled
687  $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
688 
689  $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
690 
691  // give the hook a chance to modify the data
692  $success = $this->getHookRunner()->onEnhancedChangesListModifyBlockLineData(
693  $this, $data, $rcObj );
694  if ( !$success ) {
695  // skip entry if hook aborted it
696  return '';
697  }
698  $attribs = $data['attribs'];
699  unset( $data['attribs'] );
700  $attribs = array_filter( $attribs, static function ( $key ) {
701  return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
702  }, ARRAY_FILTER_USE_KEY );
703 
704  $prefix = '';
705  if ( is_callable( $this->changeLinePrefixer ) ) {
706  $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false );
707  }
708 
709  $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
710  // Highlight block
711  $line .= Html::rawElement( 'td', [],
713  );
714 
715  $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
716  $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
717  $line .= '<td class="mw-enhanced-rc" colspan="2">';
718 
719  if ( isset( $data['recentChangesFlags'] ) ) {
720  $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
721  unset( $data['recentChangesFlags'] );
722  }
723 
724  if ( isset( $data['timestampLink'] ) ) {
725  $line .= "\u{00A0}" . $data['timestampLink'];
726  unset( $data['timestampLink'] );
727  }
728  $line .= "\u{00A0}</td>";
729  $line .= Html::openElement( 'td', [
730  'class' => 'mw-changeslist-line-inner',
731  // Used for reliable determination of the affiliated page
732  'data-target-page' => $rcObj->getTitle(),
733  ] );
734 
735  // everything else: makes it easier for extensions to add or remove data
736  foreach ( $data as $key => $dataItem ) {
737  $line .= Html::rawElement( 'span', [
738  'class' => 'mw-changeslist-line-inner-' . $key,
739  ], $dataItem );
740  }
741 
742  $line .= "</td></tr></table>\n";
743 
744  return $line;
745  }
746 
758  public function getDiffHistLinks( RCCacheEntry $rc, $query = null, $useParentheses = null ) {
759  if ( is_bool( $query ) ) {
760  $useParentheses = $query;
761  } elseif ( $query !== null ) {
762  wfDeprecated( __METHOD__ . ' with $query parameter', '1.36' );
763  }
764  $pageTitle = $rc->getTitle();
765  if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
766  // For categorizations we must swap the category title with the page title!
767  $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
768  if ( !$pageTitle ) {
769  // The page has been deleted, but the RC entry
770  // deletion job has not run yet. Just skip.
771  return '';
772  }
773  }
774 
775  $histLink = $this->linkRenderer->makeKnownLink(
776  $pageTitle,
777  new HtmlArmor( $this->message['hist'] ),
778  [ 'class' => 'mw-changeslist-history' ],
779  [
780  'curid' => $rc->getAttribute( 'rc_cur_id' ),
781  'action' => 'history'
782  ]
783  );
784  if ( $useParentheses !== false ) {
785  $retVal = $this->msg( 'parentheses' )
786  ->rawParams( $rc->difflink . $this->message['pipe-separator']
787  . $histLink )->escaped();
788  } else {
789  $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
790  Html::rawElement( 'span', [], $rc->difflink ) .
791  Html::rawElement( 'span', [], $histLink )
792  );
793  }
794  return ' ' . $retVal;
795  }
796 
803  protected function recentChangesBlock() {
804  if ( count( $this->rc_cache ) == 0 ) {
805  return '';
806  }
807 
808  $blockOut = '';
809  foreach ( $this->rc_cache as $block ) {
810  if ( count( $block ) < 2 ) {
811  $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
812  } else {
813  $blockOut .= $this->recentChangesBlockGroup( $block );
814  }
815  }
816 
817  if ( $blockOut === '' ) {
818  return '';
819  }
820  // $this->lastdate is kept up to date by recentChangesLine()
821  return Xml::element( 'h4', null, $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
822  }
823 
829  public function endRecentChangesList() {
830  return $this->recentChangesBlock() . '</div>';
831  }
832 }
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:81
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:43
const DELETED_ACTION
Definition: LogPage.php:44
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
A class containing constants representing the names of configuration variables.
HTML sanitizer for MediaWiki.
Definition: Sanitizer.php:46
Page revision base class.
Parent class for all special pages.
Definition: SpecialPage.php:65
Represents a title within MediaWiki.
Definition: Title.php:76
getAttribute( $name)
Get an attribute value.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:50
if(!file_exists( $CREDITS)) $lines