MediaWiki  master
EnhancedChangesList.php
Go to the documentation of this file.
1 <?php
2 
4 
27 
31  protected $cacheEntryFactory;
32 
36  protected $rc_cache;
37 
41  protected $templateParser;
42 
48  public function __construct( $obj, array $filterGroups = [] ) {
49  if ( $obj instanceof Skin ) {
50  // @todo: deprecate constructing with Skin
51  $context = $obj->getContext();
52  } else {
53  if ( !$obj instanceof IContextSource ) {
54  throw new MWException( 'EnhancedChangesList must be constructed with a '
55  . 'context source or skin.' );
56  }
57 
58  $context = $obj;
59  }
60 
61  parent::__construct( $context, $filterGroups );
62 
63  // message is set by the parent ChangesList class
64  $this->cacheEntryFactory = new RCCacheEntryFactory(
65  $context,
66  $this->message,
67  $this->linkRenderer
68  );
69  $this->templateParser = new TemplateParser();
70  }
71 
76  public function beginRecentChangesList() {
77  $this->rc_cache = [];
78  $this->rcMoveIndex = 0;
79  $this->rcCacheIndex = 0;
80  $this->lastdate = '';
81  $this->rclistOpen = false;
82  $this->getOutput()->addModuleStyles( [
83  'mediawiki.icon',
84  'mediawiki.interface.helpers.styles',
85  'mediawiki.special.changeslist',
86  'mediawiki.special.changeslist.enhanced',
87  ] );
88  $this->getOutput()->addModules( [
89  'jquery.makeCollapsible',
90  ] );
91 
92  return '<div class="mw-changeslist">';
93  }
94 
104  public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
105  $date = $this->getLanguage()->userDate(
106  $rc->mAttribs['rc_timestamp'],
107  $this->getUser()
108  );
109  if ( $this->lastdate === '' ) {
110  $this->lastdate = $date;
111  }
112 
113  $ret = '';
114 
115  # If it's a new day, flush the cache and update $this->lastdate
116  if ( $date !== $this->lastdate ) {
117  # Process current cache (uses $this->lastdate to generate a heading)
118  $ret = $this->recentChangesBlock();
119  $this->rc_cache = [];
120  $this->lastdate = $date;
121  }
122 
123  $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched );
124  $this->addCacheEntry( $cacheEntry );
125 
126  return $ret;
127  }
128 
135  protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
136  $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
137 
138  if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) {
139  $this->rc_cache[$cacheGroupingKey] = [];
140  }
141 
142  array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry );
143  }
144 
152  protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
153  $title = $cacheEntry->getTitle();
154  $cacheGroupingKey = $title->getPrefixedDBkey();
155 
156  $type = $cacheEntry->mAttribs['rc_type'];
157 
158  if ( $type == RC_LOG ) {
159  // Group by log type
160  $cacheGroupingKey = SpecialPage::getTitleFor(
161  'Log',
162  $cacheEntry->mAttribs['rc_log_type']
163  )->getPrefixedDBkey();
164  }
165 
166  return $cacheGroupingKey;
167  }
168 
175  protected function recentChangesBlockGroup( $block ) {
176  $recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' );
177 
178  # Add the namespace and title of the block as part of the class
179  $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc', 'mw-changeslist-line' ];
180  if ( $block[0]->mAttribs['rc_log_type'] ) {
181  # Log entry
182  $tableClasses[] = 'mw-changeslist-log';
183  $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
184  . $block[0]->mAttribs['rc_log_type'] );
185  } else {
186  $tableClasses[] = 'mw-changeslist-edit';
187  $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
188  . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
189  }
190  if ( $block[0]->watched ) {
191  $tableClasses[] = 'mw-changeslist-line-watched';
192  } else {
193  $tableClasses[] = 'mw-changeslist-line-not-watched';
194  }
195 
196  # Collate list of users
197  $userlinks = [];
198  # Other properties
199  $curId = 0;
200  # Some catalyst variables...
201  $namehidden = true;
202  $allLogs = true;
203  $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
204 
205  # Default values for RC flags
206  $collectedRcFlags = [];
207  foreach ( $recentChangesFlags as $key => $value ) {
208  $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
209  switch ( $flagGrouping ) {
210  case 'all':
211  $collectedRcFlags[$key] = true;
212  break;
213  case 'any':
214  $collectedRcFlags[$key] = false;
215  break;
216  default:
217  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
218  }
219  }
220  foreach ( $block as $rcObj ) {
221  // If all log actions to this page were hidden, then don't
222  // give the name of the affected page for this block!
223  if ( !static::isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
224  $namehidden = false;
225  }
226  $u = $rcObj->userlink;
227  if ( !isset( $userlinks[$u] ) ) {
228  $userlinks[$u] = 0;
229  }
230  if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
231  $allLogs = false;
232  }
233  # Get the latest entry with a page_id and oldid
234  # since logs may not have these.
235  if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
236  $curId = $rcObj->mAttribs['rc_cur_id'];
237  }
238 
239  $userlinks[$u]++;
240  }
241 
242  # Sort the list and convert to text
243  krsort( $userlinks );
244  asort( $userlinks );
245  $users = [];
246  foreach ( $userlinks as $userlink => $count ) {
247  $text = $userlink;
248  $text .= $this->getLanguage()->getDirMark();
249  if ( $count > 1 ) {
250  $formattedCount = $this->msg( 'ntimes' )->numParams( $count )->escaped();
251  $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
252  }
253  array_push( $users, $text );
254  }
255 
256  # Article link
257  $articleLink = '';
258  $revDeletedMsg = false;
259  if ( $namehidden ) {
260  $revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped();
261  } elseif ( $allLogs ) {
262  $articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
263  } else {
264  $articleLink = $this->getArticleLink(
265  $block[0], $block[0]->unpatrolled, $block[0]->watched );
266  }
267 
268  $queryParams['curid'] = $curId;
269 
270  # Sub-entries
271  $lines = [];
272  $filterClasses = [];
273  foreach ( $block as $i => $rcObj ) {
274  $line = $this->getLineData( $block, $rcObj, $queryParams );
275  if ( !$line ) {
276  // completely ignore this RC entry if we don't want to render it
277  unset( $block[$i] );
278  continue;
279  }
280 
281  // Roll up flags
282  foreach ( $line['recentChangesFlagsRaw'] as $key => $value ) {
283  $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
284  switch ( $flagGrouping ) {
285  case 'all':
286  if ( !$value ) {
287  $collectedRcFlags[$key] = false;
288  }
289  break;
290  case 'any':
291  if ( $value ) {
292  $collectedRcFlags[$key] = true;
293  }
294  break;
295  default:
296  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
297  }
298  }
299 
300  // Roll up filter-based CSS classes
301  $filterClasses = array_merge( $filterClasses, $this->getHTMLClassesForFilters( $rcObj ) );
302  // Add classes for change tags separately, getHTMLClassesForFilters() doesn't add them
303  $this->getTags( $rcObj, $filterClasses );
304  $filterClasses = array_unique( $filterClasses );
305 
306  $lines[] = $line;
307  }
308 
309  // Further down are some assumptions that $block is a 0-indexed array
310  // with (count-1) as last key. Let's make sure it is.
311  $block = array_values( $block );
312  $filterClasses = array_values( $filterClasses );
313 
314  if ( empty( $block ) || !$lines ) {
315  // if we can't show anything, don't display this block altogether
316  return '';
317  }
318 
319  $logText = $this->getLogText( $block, $queryParams, $allLogs,
320  $collectedRcFlags['newpage'], $namehidden
321  );
322 
323  # Character difference (does not apply if only log items)
324  $charDifference = false;
325  if ( $RCShowChangedSize && !$allLogs ) {
326  $last = 0;
327  $first = count( $block ) - 1;
328  # Some events (like logs and category changes) have an "empty" size, so we need to skip those...
329  while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
330  $last++;
331  }
332  while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) {
333  $first--;
334  }
335  # Get net change
336  $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ) ?: false;
337  }
338 
339  $numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
340  $usersList = $this->msg( 'brackets' )->rawParams(
341  implode( $this->message['semicolon-separator'], $users )
342  )->escaped();
343 
344  $prefix = '';
345  if ( is_callable( $this->changeLinePrefixer ) ) {
346  $prefix = call_user_func( $this->changeLinePrefixer, $block[0], $this, true );
347  }
348 
349  $templateParams = [
350  'articleLink' => $articleLink,
351  'charDifference' => $charDifference,
352  'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ),
353  'filterClasses' => $filterClasses,
354  'languageDirMark' => $this->getLanguage()->getDirMark(),
355  'lines' => $lines,
356  'logText' => $logText,
357  'numberofWatchingusers' => $numberofWatchingusers,
358  'prefix' => $prefix,
359  'rev-deleted-event' => $revDeletedMsg,
360  'tableClasses' => $tableClasses,
361  'timestamp' => $block[0]->timestamp,
362  'fullTimestamp' => $block[0]->getAttribute( 'rc_timestamp' ),
363  'users' => $usersList,
364  ];
365 
366  $this->rcCacheIndex++;
367 
368  return $this->templateParser->processTemplate(
369  'EnhancedChangesListGroup',
370  $templateParams
371  );
372  }
373 
383  protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
384  $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
385 
386  $type = $rcObj->mAttribs['rc_type'];
387  $data = [];
388  $lineParams = [ 'targetTitle' => $rcObj->getTitle() ];
389 
390  $classes = [ 'mw-enhanced-rc' ];
391  if ( $rcObj->watched ) {
392  $classes[] = 'mw-enhanced-watched';
393  }
394  $classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
395 
396  $separator = ' <span class="mw-changeslist-separator"></span> ';
397 
398  $data['recentChangesFlags'] = [
399  'newpage' => $type == RC_NEW,
400  'minor' => $rcObj->mAttribs['rc_minor'],
401  'unpatrolled' => $rcObj->unpatrolled,
402  'bot' => $rcObj->mAttribs['rc_bot'],
403  ];
404 
405  $params = $queryParams;
406 
407  if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
408  $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
409  }
410 
411  # Log timestamp
412  if ( $type == RC_LOG ) {
413  $link = htmlspecialchars( $rcObj->timestamp );
414  # Revision link
415  } elseif ( !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getUser() ) ) {
416  $link = Html::element( 'span', [ 'class' => 'history-deleted' ], $rcObj->timestamp );
417  } else {
418  $link = $this->linkRenderer->makeKnownLink(
419  $rcObj->getTitle(),
420  $rcObj->timestamp,
421  [],
422  $params
423  );
424  if ( static::isDeleted( $rcObj, RevisionRecord::DELETED_TEXT ) ) {
425  $link = '<span class="history-deleted">' . $link . '</span> ';
426  }
427  }
428  $data['timestampLink'] = $link;
429 
430  $currentAndLastLinks = '';
431  if ( !$type == RC_LOG || $type == RC_NEW ) {
432  $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
433  $rcObj->curlink .
434  $this->message['pipe-separator'] .
435  $rcObj->lastlink
436  )->escaped();
437  }
438  $data['currentAndLastLinks'] = $currentAndLastLinks;
439  $data['separatorAfterCurrentAndLastLinks'] = $separator;
440 
441  # Character diff
442  if ( $RCShowChangedSize ) {
443  $cd = $this->formatCharacterDifference( $rcObj );
444  if ( $cd !== '' ) {
445  $data['characterDiff'] = $cd;
446  $data['separatorAfterCharacterDiff'] = $separator;
447  }
448  }
449 
450  if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
451  $data['logEntry'] = $this->insertLogEntry( $rcObj );
452  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
453  $data['comment'] = $this->insertComment( $rcObj );
454  } else {
455  # User links
456  $data['userLink'] = $rcObj->userlink;
457  $data['userTalkLink'] = $rcObj->usertalklink;
458  $data['comment'] = $this->insertComment( $rcObj );
459  }
460 
461  # Rollback
462  $data['rollback'] = $this->getRollback( $rcObj );
463 
464  # Tags
465  $data['tags'] = $this->getTags( $rcObj, $classes );
466 
467  $attribs = $this->getDataAttributes( $rcObj );
468 
469  // give the hook a chance to modify the data
470  $success = Hooks::run( 'EnhancedChangesListModifyLineData',
471  [ $this, &$data, $block, $rcObj, &$classes, &$attribs ] );
472  if ( !$success ) {
473  // skip entry if hook aborted it
474  return [];
475  }
476  $attribs = array_filter( $attribs,
477  [ Sanitizer::class, 'isReservedDataAttribute' ],
478  ARRAY_FILTER_USE_KEY
479  );
480 
481  $lineParams['recentChangesFlagsRaw'] = [];
482  if ( isset( $data['recentChangesFlags'] ) ) {
483  $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
484  # FIXME: This is used by logic, don't return it in the template params.
485  $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
486  unset( $data['recentChangesFlags'] );
487  }
488 
489  if ( isset( $data['timestampLink'] ) ) {
490  $lineParams['timestampLink'] = $data['timestampLink'];
491  unset( $data['timestampLink'] );
492  }
493 
494  $lineParams['classes'] = array_values( $classes );
495  $lineParams['attribs'] = Html::expandAttributes( $attribs );
496 
497  // everything else: makes it easier for extensions to add or remove data
498  $lineParams['data'] = array_values( $data );
499 
500  return $lineParams;
501  }
502 
513  protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
514  if ( empty( $block ) ) {
515  return '';
516  }
517 
518  # Changes message
519  static $nchanges = [];
520  static $sinceLastVisitMsg = [];
521 
522  $n = count( $block );
523  if ( !isset( $nchanges[$n] ) ) {
524  $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
525  }
526 
527  $sinceLast = 0;
528  $unvisitedOldid = null;
530  foreach ( $block as $rcObj ) {
531  // Same logic as below inside main foreach
532  if ( $rcObj->watched ) {
533  $sinceLast++;
534  $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
535  }
536  }
537  if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
538  $sinceLastVisitMsg[$sinceLast] =
539  $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
540  }
541 
542  $currentRevision = 0;
543  foreach ( $block as $rcObj ) {
544  if ( !$currentRevision ) {
545  $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
546  }
547  }
548 
549  # Total change link
550  $links = [];
552  $block0 = $block[0];
553  $last = $block[count( $block ) - 1];
554  if ( !$allLogs ) {
555  if (
556  $isnew ||
557  $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ||
558  !ChangesList::userCan( $rcObj, RevisionRecord::DELETED_TEXT, $this->getUser() )
559  ) {
560  $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
561  } else {
562  $links['total-changes'] = Html::rawElement( 'span', [],
563  $this->linkRenderer->makeKnownLink(
564  $block0->getTitle(),
565  new HtmlArmor( $nchanges[$n] ),
566  [ 'class' => 'mw-changeslist-groupdiff' ],
567  $queryParams + [
568  'diff' => $currentRevision,
569  'oldid' => $last->mAttribs['rc_last_oldid'],
570  ]
571  )
572  );
573  }
574 
575  if (
576  $rcObj->mAttribs['rc_type'] != RC_CATEGORIZE &&
577  $sinceLast > 0 &&
578  $sinceLast < $n
579  ) {
580  $links['total-changes-since-last'] = Html::rawElement( 'span', [],
581  $this->linkRenderer->makeKnownLink(
582  $block0->getTitle(),
583  new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
584  [ 'class' => 'mw-changeslist-groupdiff' ],
585  $queryParams + [
586  'diff' => $currentRevision,
587  'oldid' => $unvisitedOldid,
588  ]
589  )
590  );
591  }
592  }
593 
594  # History
595  if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
596  // don't show history link for logs
597  } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
598  $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
599  } else {
600  $params = $queryParams;
601  $params['action'] = 'history';
602 
603  $links['history'] = Html::rawElement( 'span', [],
604  $this->linkRenderer->makeKnownLink(
605  $block0->getTitle(),
606  new HtmlArmor( $this->message['enhancedrc-history'] ),
607  [ 'class' => 'mw-changeslist-history' ],
608  $params
609  )
610  );
611  }
612 
613  # Allow others to alter, remove or add to these links
614  Hooks::run( 'EnhancedChangesList::getLogText',
615  [ $this, &$links, $block ] );
616 
617  if ( !$links ) {
618  return '';
619  }
620 
621  $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
622  implode( ' ', $links ) );
623  return ' ' . $logtext;
624  }
625 
632  protected function recentChangesBlockLine( $rcObj ) {
633  $data = [];
634 
635  $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
636 
637  $type = $rcObj->mAttribs['rc_type'];
638  $logType = $rcObj->mAttribs['rc_log_type'];
639  $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
640  $classes[] = 'mw-enhanced-rc';
641 
642  if ( $logType ) {
643  # Log entry
644  $classes[] = 'mw-changeslist-log';
645  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
646  } else {
647  $classes[] = 'mw-changeslist-edit';
648  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
649  $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
650  }
651 
652  # Flag and Timestamp
653  $data['recentChangesFlags'] = [
654  'newpage' => $type == RC_NEW,
655  'minor' => $rcObj->mAttribs['rc_minor'],
656  'unpatrolled' => $rcObj->unpatrolled,
657  'bot' => $rcObj->mAttribs['rc_bot'],
658  ];
659  // timestamp is not really a link here, but is called timestampLink
660  // for consistency with EnhancedChangesListModifyLineData
661  $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
662 
663  # Article or log link
664  if ( $logType ) {
665  $logPage = new LogPage( $logType );
666  $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
667  $logName = $logPage->getName()->text();
668  $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
669  $this->linkRenderer->makeKnownLink( $logTitle, $logName )
670  );
671  } else {
672  $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
673  }
674 
675  # Diff and hist links
676  if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
677  $query['action'] = 'history';
678  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
679  }
680  $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
681 
682  # Character diff
683  if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
684  $cd = $this->formatCharacterDifference( $rcObj );
685  if ( $cd !== '' ) {
686  $data['characterDiff'] = $cd;
687  $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
688  }
689  }
690 
691  if ( $type == RC_LOG ) {
692  $data['logEntry'] = $this->insertLogEntry( $rcObj );
693  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
694  $data['comment'] = $this->insertComment( $rcObj );
695  } else {
696  $data['userLink'] = $rcObj->userlink;
697  $data['userTalkLink'] = $rcObj->usertalklink;
698  $data['comment'] = $this->insertComment( $rcObj );
699  if ( $type == RC_CATEGORIZE ) {
700  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
701  }
702  $data['rollback'] = $this->getRollback( $rcObj );
703  }
704 
705  # Tags
706  $data['tags'] = $this->getTags( $rcObj, $classes );
707 
708  # Show how many people are watching this if enabled
709  $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
710 
711  $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
712 
713  // give the hook a chance to modify the data
714  $success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
715  [ $this, &$data, $rcObj ] );
716  if ( !$success ) {
717  // skip entry if hook aborted it
718  return '';
719  }
720  $attribs = $data['attribs'];
721  unset( $data['attribs'] );
722  $attribs = array_filter( $attribs, function ( $key ) {
723  return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
724  }, ARRAY_FILTER_USE_KEY );
725 
726  $prefix = '';
727  if ( is_callable( $this->changeLinePrefixer ) ) {
728  $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false );
729  }
730 
731  $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
732  // Highlight block
733  $line .= Html::rawElement( 'td', [],
735  );
736 
737  $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
738  $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
739  $line .= '<td class="mw-enhanced-rc" colspan="2">';
740 
741  if ( isset( $data['recentChangesFlags'] ) ) {
742  $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
743  unset( $data['recentChangesFlags'] );
744  }
745 
746  if ( isset( $data['timestampLink'] ) ) {
747  $line .= "\u{00A0}" . $data['timestampLink'];
748  unset( $data['timestampLink'] );
749  }
750  $line .= "\u{00A0}</td>";
751  $line .= Html::openElement( 'td', [
752  'class' => 'mw-changeslist-line-inner',
753  // Used for reliable determination of the affiliated page
754  'data-target-page' => $rcObj->getTitle(),
755  ] );
756 
757  // everything else: makes it easier for extensions to add or remove data
758  foreach ( $data as $key => $dataItem ) {
759  $line .= Html::rawElement( 'span', [
760  'class' => 'mw-changeslist-line-inner-' . $key,
761  ], $dataItem );
762  }
763 
764  $line .= "</td></tr></table>\n";
765 
766  return $line;
767  }
768 
780  public function getDiffHistLinks( RCCacheEntry $rc, array $query, $useParentheses = true ) {
781  $pageTitle = $rc->getTitle();
782  if ( $rc->getAttribute( 'rc_type' ) == RC_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  $query
797  );
798  if ( $useParentheses ) {
799  $retVal = $this->msg( 'parentheses' )
800  ->rawParams( $rc->difflink . $this->message['pipe-separator']
801  . $histLink )->escaped();
802  } else {
803  $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
804  Html::rawElement( 'span', [], $rc->difflink ) .
805  Html::rawElement( 'span', [], $histLink )
806  );
807  }
808  return ' ' . $retVal;
809  }
810 
817  protected function recentChangesBlock() {
818  if ( count( $this->rc_cache ) == 0 ) {
819  return '';
820  }
821 
822  $blockOut = '';
823  foreach ( $this->rc_cache as $block ) {
824  if ( count( $block ) < 2 ) {
825  $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
826  } else {
827  $blockOut .= $this->recentChangesBlockGroup( $block );
828  }
829  }
830 
831  if ( $blockOut === '' ) {
832  return '';
833  }
834  // $this->lastdate is kept up to date by recentChangesLine()
835  return Xml::element( 'h4', null, $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
836  }
837 
843  public function endRecentChangesList() {
844  return $this->recentChangesBlock() . '</div>';
845  }
846 }
getHTMLClassesForFilters( $rc)
Get an array of CSS classes attributed to filters for this row.
const RC_CATEGORIZE
Definition: Defines.php:126
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1535
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:473
$success
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1978
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
getTags(RecentChange $rc, array &$classes)
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing &#39;/&#39;...
Definition: Html.php:251
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
getAttribute( $name)
Get an attribute value.
recentChangesFlags( $flags, $nothing="\00A0}")
Returns the appropriate flags for new page, minor change and patrolling.
$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:915
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
formatCharacterDifference(RecentChange $old, RecentChange $new=null)
Format the character difference of one or several changes.
IContextSource $context
array $filterGroups
Definition: ChangesList.php:59
maybeWatchedLink( $link, $watched=false)
Class to simplify the use of log pages.
Definition: LogPage.php:33
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3045
$last
getHighlightsContainerDiv()
Get the container for highlights that are used in the new StructuredFilters system.
RCCacheEntryFactory $cacheEntryFactory
getDataAttributes(RecentChange $rc)
Get recommended data attributes for a change line.
getLineData(array $block, RCCacheEntry $rcObj, array $queryParams=[])
insertLogEntry( $rc)
Insert a formatted action.
getRollback(RecentChange $rc)
getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden)
Generates amount of changes (linking to diff ) & link to history.
getArticleLink(&$rc, $unpatrolled, $watched)
$params
array $rc_cache
Array of array of RCCacheEntry.
makeCacheGroupingKey(RCCacheEntry $cacheEntry)
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1978
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:773
recentChangesBlock()
If enhanced RC is in use, this function takes the previously cached RC lines, arranges them...
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:918
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1418
getHTMLClasses( $rc, $watched)
Get an array of default HTML class attributes for the change.
TemplateParser $templateParser
static expandAttributes(array $attribs)
Given an associative array of element attributes, generate a string to stick after the element name i...
Definition: Html.php:480
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
beginRecentChangesList()
Add the JavaScript file for enhanced changeslist.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getDiffHistLinks(RCCacheEntry $rc, array $query, $useParentheses=true)
Returns value to be used in &#39;historyLink&#39; element of $data param in EnhancedChangesListModifyBlockLin...
endRecentChangesList()
Returns text for the end of RC If enhanced RC is in use, returns pretty much all the text...
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
addCacheEntry(RCCacheEntry $cacheEntry)
Put accumulated information into the cache, for later display.
$lines
Definition: router.php:61
recentChangesBlockLine( $rcObj)
Enhanced RC ungrouped line.
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
$line
Definition: cdb.php:59
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a message
Definition: hooks.txt:2145
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
Definition: hooks.txt:3045
static userCan( $rc, $field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision, if it&#39;s marked as deleted.
numberofWatchingusers( $count)
Returns the string which indicates the number of watching users.
recentChangesBlockGroup( $block)
Enhanced RC group.
__construct( $obj, array $filterGroups=[])
const RC_NEW
Definition: Defines.php:123
const DELETED_ACTION
Definition: LogPage.php:34
insertComment( $rc)
Insert a formatted comment.
recentChangesLine(&$rc, $watched=false, $linenumber=null)
Format a line for enhanced recentchange (aka with javascript and block of lines). ...
isCategorizationWithoutRevision( $rcObj)
Determines whether a revision is linked to this change; this may not be the case when the categorizat...
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
const RC_LOG
Definition: Defines.php:124