MediaWiki  master
EnhancedChangesList.php
Go to the documentation of this file.
1 <?php
24 
28  protected $cacheEntryFactory;
29 
33  protected $rc_cache;
34 
38  protected $templateParser;
39 
45  public function __construct( $obj, array $filterGroups = [] ) {
46  if ( $obj instanceof Skin ) {
47  // @todo: deprecate constructing with Skin
48  $context = $obj->getContext();
49  } else {
50  if ( !$obj instanceof IContextSource ) {
51  throw new MWException( 'EnhancedChangesList must be constructed with a '
52  . 'context source or skin.' );
53  }
54 
55  $context = $obj;
56  }
57 
58  parent::__construct( $context, $filterGroups );
59 
60  // message is set by the parent ChangesList class
61  $this->cacheEntryFactory = new RCCacheEntryFactory(
62  $context,
63  $this->message,
64  $this->linkRenderer
65  );
66  $this->templateParser = new TemplateParser();
67  }
68 
73  public function beginRecentChangesList() {
74  $this->rc_cache = [];
75  $this->rcMoveIndex = 0;
76  $this->rcCacheIndex = 0;
77  $this->lastdate = '';
78  $this->rclistOpen = false;
79  $this->getOutput()->addModuleStyles( [
80  'mediawiki.icon',
81  'mediawiki.interface.helpers.styles',
82  'mediawiki.special.changeslist',
83  'mediawiki.special.changeslist.enhanced',
84  ] );
85  $this->getOutput()->addModules( [
86  'jquery.makeCollapsible',
87  ] );
88 
89  return '<div class="mw-changeslist">';
90  }
91 
101  public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
102  $date = $this->getLanguage()->userDate(
103  $rc->mAttribs['rc_timestamp'],
104  $this->getUser()
105  );
106  if ( $this->lastdate === '' ) {
107  $this->lastdate = $date;
108  }
109 
110  $ret = '';
111 
112  # If it's a new day, flush the cache and update $this->lastdate
113  if ( $date !== $this->lastdate ) {
114  # Process current cache (uses $this->lastdate to generate a heading)
115  $ret = $this->recentChangesBlock();
116  $this->rc_cache = [];
117  $this->lastdate = $date;
118  }
119 
120  $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched );
121  $this->addCacheEntry( $cacheEntry );
122 
123  return $ret;
124  }
125 
132  protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
133  $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
134 
135  if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) {
136  $this->rc_cache[$cacheGroupingKey] = [];
137  }
138 
139  array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry );
140  }
141 
149  protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
150  $title = $cacheEntry->getTitle();
151  $cacheGroupingKey = $title->getPrefixedDBkey();
152 
153  $type = $cacheEntry->mAttribs['rc_type'];
154 
155  if ( $type == RC_LOG ) {
156  // Group by log type
157  $cacheGroupingKey = SpecialPage::getTitleFor(
158  'Log',
159  $cacheEntry->mAttribs['rc_log_type']
160  )->getPrefixedDBkey();
161  }
162 
163  return $cacheGroupingKey;
164  }
165 
172  protected function recentChangesBlockGroup( $block ) {
173  $recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' );
174 
175  # Add the namespace and title of the block as part of the class
176  $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc', 'mw-changeslist-line' ];
177  if ( $block[0]->mAttribs['rc_log_type'] ) {
178  # Log entry
179  $tableClasses[] = 'mw-changeslist-log';
180  $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
181  . $block[0]->mAttribs['rc_log_type'] );
182  } else {
183  $tableClasses[] = 'mw-changeslist-edit';
184  $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
185  . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
186  }
187  if ( $block[0]->watched ) {
188  $tableClasses[] = 'mw-changeslist-line-watched';
189  } else {
190  $tableClasses[] = 'mw-changeslist-line-not-watched';
191  }
192 
193  # Collate list of users
194  $userlinks = [];
195  # Other properties
196  $curId = 0;
197  # Some catalyst variables...
198  $namehidden = true;
199  $allLogs = true;
200  $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
201 
202  # Default values for RC flags
203  $collectedRcFlags = [];
204  foreach ( $recentChangesFlags as $key => $value ) {
205  $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
206  switch ( $flagGrouping ) {
207  case 'all':
208  $collectedRcFlags[$key] = true;
209  break;
210  case 'any':
211  $collectedRcFlags[$key] = false;
212  break;
213  default:
214  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
215  }
216  }
217  foreach ( $block as $rcObj ) {
218  // If all log actions to this page were hidden, then don't
219  // give the name of the affected page for this block!
220  if ( !static::isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
221  $namehidden = false;
222  }
223  $u = $rcObj->userlink;
224  if ( !isset( $userlinks[$u] ) ) {
225  $userlinks[$u] = 0;
226  }
227  if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
228  $allLogs = false;
229  }
230  # Get the latest entry with a page_id and oldid
231  # since logs may not have these.
232  if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
233  $curId = $rcObj->mAttribs['rc_cur_id'];
234  }
235 
236  $userlinks[$u]++;
237  }
238 
239  # Sort the list and convert to text
240  krsort( $userlinks );
241  asort( $userlinks );
242  $users = [];
243  foreach ( $userlinks as $userlink => $count ) {
244  $text = $userlink;
245  $text .= $this->getLanguage()->getDirMark();
246  if ( $count > 1 ) {
247  $formattedCount = $this->msg( 'ntimes' )->numParams( $count )->escaped();
248  $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
249  }
250  array_push( $users, $text );
251  }
252 
253  # Article link
254  $articleLink = '';
255  $revDeletedMsg = false;
256  if ( $namehidden ) {
257  $revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped();
258  } elseif ( $allLogs ) {
259  $articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
260  } else {
261  $articleLink = $this->getArticleLink(
262  $block[0], $block[0]->unpatrolled, $block[0]->watched );
263  }
264 
265  $queryParams['curid'] = $curId;
266 
267  # Sub-entries
268  $lines = [];
269  $filterClasses = [];
270  foreach ( $block as $i => $rcObj ) {
271  $line = $this->getLineData( $block, $rcObj, $queryParams );
272  if ( !$line ) {
273  // completely ignore this RC entry if we don't want to render it
274  unset( $block[$i] );
275  continue;
276  }
277 
278  // Roll up flags
279  foreach ( $line['recentChangesFlagsRaw'] as $key => $value ) {
280  $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
281  switch ( $flagGrouping ) {
282  case 'all':
283  if ( !$value ) {
284  $collectedRcFlags[$key] = false;
285  }
286  break;
287  case 'any':
288  if ( $value ) {
289  $collectedRcFlags[$key] = true;
290  }
291  break;
292  default:
293  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
294  }
295  }
296 
297  // Roll up filter-based CSS classes
298  $filterClasses = array_merge( $filterClasses, $this->getHTMLClassesForFilters( $rcObj ) );
299  // Add classes for change tags separately, getHTMLClassesForFilters() doesn't add them
300  $this->getTags( $rcObj, $filterClasses );
301  $filterClasses = array_unique( $filterClasses );
302 
303  $lines[] = $line;
304  }
305 
306  // Further down are some assumptions that $block is a 0-indexed array
307  // with (count-1) as last key. Let's make sure it is.
308  $block = array_values( $block );
309  $filterClasses = array_values( $filterClasses );
310 
311  if ( empty( $block ) || !$lines ) {
312  // if we can't show anything, don't display this block altogether
313  return '';
314  }
315 
316  $logText = $this->getLogText( $block, $queryParams, $allLogs,
317  $collectedRcFlags['newpage'], $namehidden
318  );
319 
320  # Character difference (does not apply if only log items)
321  $charDifference = false;
322  if ( $RCShowChangedSize && !$allLogs ) {
323  $last = 0;
324  $first = count( $block ) - 1;
325  # Some events (like logs and category changes) have an "empty" size, so we need to skip those...
326  while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
327  $last++;
328  }
329  while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) {
330  $first--;
331  }
332  # Get net change
333  $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ) ?: false;
334  }
335 
336  $numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
337  $usersList = $this->msg( 'brackets' )->rawParams(
338  implode( $this->message['semicolon-separator'], $users )
339  )->escaped();
340 
341  $prefix = '';
342  if ( is_callable( $this->changeLinePrefixer ) ) {
343  $prefix = call_user_func( $this->changeLinePrefixer, $block[0], $this, true );
344  }
345 
346  $templateParams = [
347  'articleLink' => $articleLink,
348  'charDifference' => $charDifference,
349  'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ),
350  'filterClasses' => $filterClasses,
351  'languageDirMark' => $this->getLanguage()->getDirMark(),
352  'lines' => $lines,
353  'logText' => $logText,
354  'numberofWatchingusers' => $numberofWatchingusers,
355  'prefix' => $prefix,
356  'rev-deleted-event' => $revDeletedMsg,
357  'tableClasses' => $tableClasses,
358  'timestamp' => $block[0]->timestamp,
359  'fullTimestamp' => $block[0]->getAttribute( 'rc_timestamp' ),
360  'users' => $usersList,
361  ];
362 
363  $this->rcCacheIndex++;
364 
365  return $this->templateParser->processTemplate(
366  'EnhancedChangesListGroup',
367  $templateParams
368  );
369  }
370 
380  protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
381  $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
382 
383  $type = $rcObj->mAttribs['rc_type'];
384  $data = [];
385  $lineParams = [ 'targetTitle' => $rcObj->getTitle() ];
386 
387  $classes = [ 'mw-enhanced-rc' ];
388  if ( $rcObj->watched ) {
389  $classes[] = 'mw-enhanced-watched';
390  }
391  $classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
392 
393  $separator = ' <span class="mw-changeslist-separator"></span> ';
394 
395  $data['recentChangesFlags'] = [
396  'newpage' => $type == RC_NEW,
397  'minor' => $rcObj->mAttribs['rc_minor'],
398  'unpatrolled' => $rcObj->unpatrolled,
399  'bot' => $rcObj->mAttribs['rc_bot'],
400  ];
401 
402  $params = $queryParams;
403 
404  if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
405  $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
406  }
407 
408  # Log timestamp
409  if ( $type == RC_LOG ) {
410  $link = htmlspecialchars( $rcObj->timestamp );
411  # Revision link
412  } elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
413  $link = Html::element( 'span', [ 'class' => 'history-deleted' ], $rcObj->timestamp );
414  } else {
415  $link = $this->linkRenderer->makeKnownLink(
416  $rcObj->getTitle(),
417  $rcObj->timestamp,
418  [],
419  $params
420  );
421  if ( static::isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
422  $link = '<span class="history-deleted">' . $link . '</span> ';
423  }
424  }
425  $data['timestampLink'] = $link;
426 
427  $currentAndLastLinks = '';
428  if ( !$type == RC_LOG || $type == RC_NEW ) {
429  $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
430  $rcObj->curlink .
431  $this->message['pipe-separator'] .
432  $rcObj->lastlink
433  )->escaped();
434  }
435  $data['currentAndLastLinks'] = $currentAndLastLinks;
436  $data['separatorAfterCurrentAndLastLinks'] = $separator;
437 
438  # Character diff
439  if ( $RCShowChangedSize ) {
440  $cd = $this->formatCharacterDifference( $rcObj );
441  if ( $cd !== '' ) {
442  $data['characterDiff'] = $cd;
443  $data['separatorAfterCharacterDiff'] = $separator;
444  }
445  }
446 
447  if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
448  $data['logEntry'] = $this->insertLogEntry( $rcObj );
449  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
450  $data['comment'] = $this->insertComment( $rcObj );
451  } else {
452  # User links
453  $data['userLink'] = $rcObj->userlink;
454  $data['userTalkLink'] = $rcObj->usertalklink;
455  $data['comment'] = $this->insertComment( $rcObj );
456  }
457 
458  # Rollback
459  $data['rollback'] = $this->getRollback( $rcObj );
460 
461  # Tags
462  $data['tags'] = $this->getTags( $rcObj, $classes );
463 
464  $attribs = $this->getDataAttributes( $rcObj );
465 
466  // give the hook a chance to modify the data
467  $success = Hooks::run( 'EnhancedChangesListModifyLineData',
468  [ $this, &$data, $block, $rcObj, &$classes, &$attribs ] );
469  if ( !$success ) {
470  // skip entry if hook aborted it
471  return [];
472  }
473  $attribs = array_filter( $attribs,
474  [ Sanitizer::class, 'isReservedDataAttribute' ],
475  ARRAY_FILTER_USE_KEY
476  );
477 
478  $lineParams['recentChangesFlagsRaw'] = [];
479  if ( isset( $data['recentChangesFlags'] ) ) {
480  $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
481  # FIXME: This is used by logic, don't return it in the template params.
482  $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
483  unset( $data['recentChangesFlags'] );
484  }
485 
486  if ( isset( $data['timestampLink'] ) ) {
487  $lineParams['timestampLink'] = $data['timestampLink'];
488  unset( $data['timestampLink'] );
489  }
490 
491  $lineParams['classes'] = array_values( $classes );
492  $lineParams['attribs'] = Html::expandAttributes( $attribs );
493 
494  // everything else: makes it easier for extensions to add or remove data
495  $lineParams['data'] = array_values( $data );
496 
497  return $lineParams;
498  }
499 
510  protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
511  if ( empty( $block ) ) {
512  return '';
513  }
514 
515  # Changes message
516  static $nchanges = [];
517  static $sinceLastVisitMsg = [];
518 
519  $n = count( $block );
520  if ( !isset( $nchanges[$n] ) ) {
521  $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
522  }
523 
524  $sinceLast = 0;
525  $unvisitedOldid = null;
527  foreach ( $block as $rcObj ) {
528  // Same logic as below inside main foreach
529  if ( $rcObj->watched ) {
530  $sinceLast++;
531  $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
532  }
533  }
534  if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
535  $sinceLastVisitMsg[$sinceLast] =
536  $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
537  }
538 
539  $currentRevision = 0;
540  foreach ( $block as $rcObj ) {
541  if ( !$currentRevision ) {
542  $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
543  }
544  }
545 
546  # Total change link
547  $links = [];
549  $block0 = $block[0];
550  $last = $block[count( $block ) - 1];
551  if ( !$allLogs ) {
552  if (
553  $isnew ||
554  $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ||
555  !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() )
556  ) {
557  $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
558  } else {
559  $links['total-changes'] = Html::rawElement( 'span', [],
560  $this->linkRenderer->makeKnownLink(
561  $block0->getTitle(),
562  new HtmlArmor( $nchanges[$n] ),
563  [ 'class' => 'mw-changeslist-groupdiff' ],
564  $queryParams + [
565  'diff' => $currentRevision,
566  'oldid' => $last->mAttribs['rc_last_oldid'],
567  ]
568  )
569  );
570  }
571 
572  if (
573  $rcObj->mAttribs['rc_type'] != RC_CATEGORIZE &&
574  $sinceLast > 0 &&
575  $sinceLast < $n
576  ) {
577  $links['total-changes-since-last'] = Html::rawElement( 'span', [],
578  $this->linkRenderer->makeKnownLink(
579  $block0->getTitle(),
580  new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
581  [ 'class' => 'mw-changeslist-groupdiff' ],
582  $queryParams + [
583  'diff' => $currentRevision,
584  'oldid' => $unvisitedOldid,
585  ]
586  )
587  );
588  }
589  }
590 
591  # History
592  if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
593  // don't show history link for logs
594  } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
595  $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
596  } else {
597  $params = $queryParams;
598  $params['action'] = 'history';
599 
600  $links['history'] = Html::rawElement( 'span', [],
601  $this->linkRenderer->makeKnownLink(
602  $block0->getTitle(),
603  new HtmlArmor( $this->message['enhancedrc-history'] ),
604  [ 'class' => 'mw-changeslist-history' ],
605  $params
606  )
607  );
608  }
609 
610  # Allow others to alter, remove or add to these links
611  Hooks::run( 'EnhancedChangesList::getLogText',
612  [ $this, &$links, $block ] );
613 
614  if ( !$links ) {
615  return '';
616  }
617 
618  $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
619  implode( ' ', $links ) );
620  return ' ' . $logtext;
621  }
622 
629  protected function recentChangesBlockLine( $rcObj ) {
630  $data = [];
631 
632  $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
633 
634  $type = $rcObj->mAttribs['rc_type'];
635  $logType = $rcObj->mAttribs['rc_log_type'];
636  $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
637  $classes[] = 'mw-enhanced-rc';
638 
639  if ( $logType ) {
640  # Log entry
641  $classes[] = 'mw-changeslist-log';
642  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
643  } else {
644  $classes[] = 'mw-changeslist-edit';
645  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
646  $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
647  }
648 
649  # Flag and Timestamp
650  $data['recentChangesFlags'] = [
651  'newpage' => $type == RC_NEW,
652  'minor' => $rcObj->mAttribs['rc_minor'],
653  'unpatrolled' => $rcObj->unpatrolled,
654  'bot' => $rcObj->mAttribs['rc_bot'],
655  ];
656  // timestamp is not really a link here, but is called timestampLink
657  // for consistency with EnhancedChangesListModifyLineData
658  $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
659 
660  # Article or log link
661  if ( $logType ) {
662  $logPage = new LogPage( $logType );
663  $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
664  $logName = $logPage->getName()->text();
665  $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
666  $this->linkRenderer->makeKnownLink( $logTitle, $logName )
667  );
668  } else {
669  $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
670  }
671 
672  # Diff and hist links
673  if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
674  $query['action'] = 'history';
675  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
676  }
677  $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
678 
679  # Character diff
680  if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
681  $cd = $this->formatCharacterDifference( $rcObj );
682  if ( $cd !== '' ) {
683  $data['characterDiff'] = $cd;
684  $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
685  }
686  }
687 
688  if ( $type == RC_LOG ) {
689  $data['logEntry'] = $this->insertLogEntry( $rcObj );
690  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
691  $data['comment'] = $this->insertComment( $rcObj );
692  } else {
693  $data['userLink'] = $rcObj->userlink;
694  $data['userTalkLink'] = $rcObj->usertalklink;
695  $data['comment'] = $this->insertComment( $rcObj );
696  if ( $type == RC_CATEGORIZE ) {
697  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
698  }
699  $data['rollback'] = $this->getRollback( $rcObj );
700  }
701 
702  # Tags
703  $data['tags'] = $this->getTags( $rcObj, $classes );
704 
705  # Show how many people are watching this if enabled
706  $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
707 
708  $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
709 
710  // give the hook a chance to modify the data
711  $success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
712  [ $this, &$data, $rcObj ] );
713  if ( !$success ) {
714  // skip entry if hook aborted it
715  return '';
716  }
717  $attribs = $data['attribs'];
718  unset( $data['attribs'] );
719  $attribs = array_filter( $attribs, function ( $key ) {
720  return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
721  }, ARRAY_FILTER_USE_KEY );
722 
723  $prefix = '';
724  if ( is_callable( $this->changeLinePrefixer ) ) {
725  $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false );
726  }
727 
728  $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
729  // Highlight block
730  $line .= Html::rawElement( 'td', [],
732  );
733 
734  $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
735  $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
736  $line .= '<td class="mw-enhanced-rc" colspan="2">';
737 
738  if ( isset( $data['recentChangesFlags'] ) ) {
739  $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
740  unset( $data['recentChangesFlags'] );
741  }
742 
743  if ( isset( $data['timestampLink'] ) ) {
744  $line .= "\u{00A0}" . $data['timestampLink'];
745  unset( $data['timestampLink'] );
746  }
747  $line .= "\u{00A0}</td>";
748  $line .= Html::openElement( 'td', [
749  'class' => 'mw-changeslist-line-inner',
750  // Used for reliable determination of the affiliated page
751  'data-target-page' => $rcObj->getTitle(),
752  ] );
753 
754  // everything else: makes it easier for extensions to add or remove data
755  foreach ( $data as $key => $dataItem ) {
756  $line .= Html::rawElement( 'span', [
757  'class' => 'mw-changeslist-line-inner-' . $key,
758  ], $dataItem );
759  }
760 
761  $line .= "</td></tr></table>\n";
762 
763  return $line;
764  }
765 
777  public function getDiffHistLinks( RCCacheEntry $rc, array $query, $useParentheses = true ) {
778  $pageTitle = $rc->getTitle();
779  if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
780  // For categorizations we must swap the category title with the page title!
781  $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
782  if ( !$pageTitle ) {
783  // The page has been deleted, but the RC entry
784  // deletion job has not run yet. Just skip.
785  return '';
786  }
787  }
788 
789  $histLink = $this->linkRenderer->makeKnownLink(
790  $pageTitle,
791  new HtmlArmor( $this->message['hist'] ),
792  [ 'class' => 'mw-changeslist-history' ],
793  $query
794  );
795  if ( $useParentheses ) {
796  $retVal = $this->msg( 'parentheses' )
797  ->rawParams( $rc->difflink . $this->message['pipe-separator']
798  . $histLink )->escaped();
799  } else {
800  $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
801  Html::rawElement( 'span', [], $rc->difflink ) .
802  Html::rawElement( 'span', [], $histLink )
803  );
804  }
805  return ' ' . $retVal;
806  }
807 
814  protected function recentChangesBlock() {
815  if ( count( $this->rc_cache ) == 0 ) {
816  return '';
817  }
818 
819  $blockOut = '';
820  foreach ( $this->rc_cache as $block ) {
821  if ( count( $block ) < 2 ) {
822  $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
823  } else {
824  $blockOut .= $this->recentChangesBlockGroup( $block );
825  }
826  }
827 
828  if ( $blockOut === '' ) {
829  return '';
830  }
831  // $this->lastdate is kept up to date by recentChangesLine()
832  return Xml::element( 'h4', null, $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
833  }
834 
840  public function endRecentChangesList() {
841  return $this->recentChangesBlock() . '</div>';
842  }
843 }
getHTMLClassesForFilters( $rc)
Get an array of CSS classes attributed to filters for this row.
const RC_CATEGORIZE
Definition: Defines.php:142
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:1585
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:470
$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:1982
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:252
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
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:908
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:58
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:3051
$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:1982
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:780
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:925
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1411
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:481
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
const DELETED_TEXT
Definition: Revision.php:46
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:2151
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:3051
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:139
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:140