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  && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
189  ) {
190  $tableClasses[] = 'mw-changeslist-line-watched';
191  } else {
192  $tableClasses[] = 'mw-changeslist-line-not-watched';
193  }
194 
195  # Collate list of users
196  $userlinks = [];
197  # Other properties
198  $curId = 0;
199  # Some catalyst variables...
200  $namehidden = true;
201  $allLogs = true;
202  $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
203 
204  # Default values for RC flags
205  $collectedRcFlags = [];
206  foreach ( $recentChangesFlags as $key => $value ) {
207  $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
208  switch ( $flagGrouping ) {
209  case 'all':
210  $collectedRcFlags[$key] = true;
211  break;
212  case 'any':
213  $collectedRcFlags[$key] = false;
214  break;
215  default:
216  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
217  }
218  }
219  foreach ( $block as $rcObj ) {
220  // If all log actions to this page were hidden, then don't
221  // give the name of the affected page for this block!
222  if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
223  $namehidden = false;
224  }
225  $u = $rcObj->userlink;
226  if ( !isset( $userlinks[$u] ) ) {
227  $userlinks[$u] = 0;
228  }
229  if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
230  $allLogs = false;
231  }
232  # Get the latest entry with a page_id and oldid
233  # since logs may not have these.
234  if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
235  $curId = $rcObj->mAttribs['rc_cur_id'];
236  }
237 
238  $userlinks[$u]++;
239  }
240 
241  # Sort the list and convert to text
242  krsort( $userlinks );
243  asort( $userlinks );
244  $users = [];
245  foreach ( $userlinks as $userlink => $count ) {
246  $text = $userlink;
247  $text .= $this->getLanguage()->getDirMark();
248  if ( $count > 1 ) {
249  $formattedCount = $this->msg( 'ntimes' )->numParams( $count )->escaped();
250  $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
251  }
252  array_push( $users, $text );
253  }
254 
255  # Article link
256  $articleLink = '';
257  $revDeletedMsg = false;
258  if ( $namehidden ) {
259  $revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped();
260  } elseif ( $allLogs ) {
261  $articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
262  } else {
263  $articleLink = $this->getArticleLink( $block[0], $block[0]->unpatrolled, $block[0]->watched );
264  }
265 
266  $queryParams['curid'] = $curId;
267 
268  # Sub-entries
269  $lines = [];
270  $filterClasses = [];
271  foreach ( $block as $i => $rcObj ) {
272  $line = $this->getLineData( $block, $rcObj, $queryParams );
273  if ( !$line ) {
274  // completely ignore this RC entry if we don't want to render it
275  unset( $block[$i] );
276  continue;
277  }
278 
279  // Roll up flags
280  foreach ( $line['recentChangesFlagsRaw'] as $key => $value ) {
281  $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
282  switch ( $flagGrouping ) {
283  case 'all':
284  if ( !$value ) {
285  $collectedRcFlags[$key] = false;
286  }
287  break;
288  case 'any':
289  if ( $value ) {
290  $collectedRcFlags[$key] = true;
291  }
292  break;
293  default:
294  throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
295  }
296  }
297 
298  // Roll up filter-based CSS classes
299  $filterClasses = array_merge( $filterClasses, $this->getHTMLClassesForFilters( $rcObj ) );
300  // Add classes for change tags separately, getHTMLClassesForFilters() doesn't add them
301  $this->getTags( $rcObj, $filterClasses );
302  $filterClasses = array_unique( $filterClasses );
303 
304  $lines[] = $line;
305  }
306 
307  // Further down are some assumptions that $block is a 0-indexed array
308  // with (count-1) as last key. Let's make sure it is.
309  $block = array_values( $block );
310  $filterClasses = array_values( $filterClasses );
311 
312  if ( empty( $block ) || !$lines ) {
313  // if we can't show anything, don't display this block altogether
314  return '';
315  }
316 
317  $logText = $this->getLogText( $block, $queryParams, $allLogs,
318  $collectedRcFlags['newpage'], $namehidden
319  );
320 
321  # Character difference (does not apply if only log items)
322  $charDifference = false;
323  if ( $RCShowChangedSize && !$allLogs ) {
324  $last = 0;
325  $first = count( $block ) - 1;
326  # Some events (like logs and category changes) have an "empty" size, so we need to skip those...
327  while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
328  $last++;
329  }
330  while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) {
331  $first--;
332  }
333  # Get net change
334  $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ) ?: false;
335  }
336 
337  $numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
338  $usersList = $this->msg( 'brackets' )->rawParams(
339  implode( $this->message['semicolon-separator'], $users )
340  )->escaped();
341 
342  $prefix = '';
343  if ( is_callable( $this->changeLinePrefixer ) ) {
344  $prefix = call_user_func( $this->changeLinePrefixer, $block[0], $this, true );
345  }
346 
347  $templateParams = [
348  'articleLink' => $articleLink,
349  'charDifference' => $charDifference,
350  'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ),
351  'filterClasses' => $filterClasses,
352  'languageDirMark' => $this->getLanguage()->getDirMark(),
353  'lines' => $lines,
354  'logText' => $logText,
355  'numberofWatchingusers' => $numberofWatchingusers,
356  'prefix' => $prefix,
357  'rev-deleted-event' => $revDeletedMsg,
358  'tableClasses' => $tableClasses,
359  'timestamp' => $block[0]->timestamp,
360  'fullTimestamp' => $block[0]->getAttribute( 'rc_timestamp' ),
361  'users' => $usersList,
362  ];
363 
364  $this->rcCacheIndex++;
365 
366  return $this->templateParser->processTemplate(
367  'EnhancedChangesListGroup',
368  $templateParams
369  );
370  }
371 
381  protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
382  $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
383 
384  $type = $rcObj->mAttribs['rc_type'];
385  $data = [];
386  $lineParams = [ 'targetTitle' => $rcObj->getTitle() ];
387 
388  $classes = [ 'mw-enhanced-rc' ];
389  if ( $rcObj->watched
390  && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
391  ) {
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, Revision::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 ( $this->isDeleted( $rcObj, Revision::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 && $rcObj->mAttribs['rc_timestamp'] >= $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 ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ||
556  $isnew ||
557  $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE
558  ) {
559  $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
560  } else {
561  $links['total-changes'] = Html::rawElement( 'span', [],
562  $this->linkRenderer->makeKnownLink(
563  $block0->getTitle(),
564  new HtmlArmor( $nchanges[$n] ),
565  [ 'class' => 'mw-changeslist-groupdiff' ],
566  $queryParams + [
567  'diff' => $currentRevision,
568  'oldid' => $last->mAttribs['rc_last_oldid'],
569  ]
570  )
571  );
572  if ( $sinceLast > 0 && $sinceLast < $n ) {
573  $links['total-changes-since-last'] = Html::rawElement( 'span', [],
574  $this->linkRenderer->makeKnownLink(
575  $block0->getTitle(),
576  new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
577  [ 'class' => 'mw-changeslist-groupdiff' ],
578  $queryParams + [
579  'diff' => $currentRevision,
580  'oldid' => $unvisitedOldid,
581  ]
582  )
583  );
584  }
585  }
586  }
587 
588  # History
589  if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
590  // don't show history link for logs
591  } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
592  $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
593  } else {
594  $params = $queryParams;
595  $params['action'] = 'history';
596 
597  $links['history'] = Html::rawElement( 'span', [],
598  $this->linkRenderer->makeKnownLink(
599  $block0->getTitle(),
600  new HtmlArmor( $this->message['enhancedrc-history'] ),
601  [ 'class' => 'mw-changeslist-history' ],
602  $params
603  )
604  );
605  }
606 
607  # Allow others to alter, remove or add to these links
608  Hooks::run( 'EnhancedChangesList::getLogText',
609  [ $this, &$links, $block ] );
610 
611  if ( !$links ) {
612  return '';
613  }
614 
615  $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
616  implode( ' ', $links ) );
617  return ' ' . $logtext;
618  }
619 
626  protected function recentChangesBlockLine( $rcObj ) {
627  $data = [];
628 
629  $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
630 
631  $type = $rcObj->mAttribs['rc_type'];
632  $logType = $rcObj->mAttribs['rc_log_type'];
633  $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
634  $classes[] = 'mw-enhanced-rc';
635 
636  if ( $logType ) {
637  # Log entry
638  $classes[] = 'mw-changeslist-log';
639  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
640  } else {
641  $classes[] = 'mw-changeslist-edit';
642  $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
643  $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
644  }
645 
646  # Flag and Timestamp
647  $data['recentChangesFlags'] = [
648  'newpage' => $type == RC_NEW,
649  'minor' => $rcObj->mAttribs['rc_minor'],
650  'unpatrolled' => $rcObj->unpatrolled,
651  'bot' => $rcObj->mAttribs['rc_bot'],
652  ];
653  // timestamp is not really a link here, but is called timestampLink
654  // for consistency with EnhancedChangesListModifyLineData
655  $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
656 
657  # Article or log link
658  if ( $logType ) {
659  $logPage = new LogPage( $logType );
660  $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
661  $logName = $logPage->getName()->text();
662  $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
663  $this->linkRenderer->makeKnownLink( $logTitle, $logName )
664  );
665  } else {
666  $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
667  }
668 
669  # Diff and hist links
670  if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
671  $query['action'] = 'history';
672  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
673  }
674  $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
675 
676  # Character diff
677  if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
678  $cd = $this->formatCharacterDifference( $rcObj );
679  if ( $cd !== '' ) {
680  $data['characterDiff'] = $cd;
681  $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
682  }
683  }
684 
685  if ( $type == RC_LOG ) {
686  $data['logEntry'] = $this->insertLogEntry( $rcObj );
687  } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
688  $data['comment'] = $this->insertComment( $rcObj );
689  } else {
690  $data['userLink'] = $rcObj->userlink;
691  $data['userTalkLink'] = $rcObj->usertalklink;
692  $data['comment'] = $this->insertComment( $rcObj );
693  if ( $type == RC_CATEGORIZE ) {
694  $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
695  }
696  $data['rollback'] = $this->getRollback( $rcObj );
697  }
698 
699  # Tags
700  $data['tags'] = $this->getTags( $rcObj, $classes );
701 
702  # Show how many people are watching this if enabled
703  $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
704 
705  $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
706 
707  // give the hook a chance to modify the data
708  $success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
709  [ $this, &$data, $rcObj ] );
710  if ( !$success ) {
711  // skip entry if hook aborted it
712  return '';
713  }
714  $attribs = $data['attribs'];
715  unset( $data['attribs'] );
716  $attribs = array_filter( $attribs, function ( $key ) {
717  return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
718  }, ARRAY_FILTER_USE_KEY );
719 
720  $prefix = '';
721  if ( is_callable( $this->changeLinePrefixer ) ) {
722  $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false );
723  }
724 
725  $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
726  // Highlight block
727  $line .= Html::rawElement( 'td', [],
729  );
730 
731  $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
732  $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
733  $line .= '<td class="mw-enhanced-rc" colspan="2">';
734 
735  if ( isset( $data['recentChangesFlags'] ) ) {
736  $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
737  unset( $data['recentChangesFlags'] );
738  }
739 
740  if ( isset( $data['timestampLink'] ) ) {
741  $line .= "\u{00A0}" . $data['timestampLink'];
742  unset( $data['timestampLink'] );
743  }
744  $line .= "\u{00A0}</td>";
745  $line .= Html::openElement( 'td', [
746  'class' => 'mw-changeslist-line-inner',
747  // Used for reliable determination of the affiliated page
748  'data-target-page' => $rcObj->getTitle(),
749  ] );
750 
751  // everything else: makes it easier for extensions to add or remove data
752  foreach ( $data as $key => $dataItem ) {
753  $line .= Html::rawElement( 'span', [
754  'class' => 'mw-changeslist-line-inner-' . $key,
755  ], $dataItem );
756  }
757 
758  $line .= "</td></tr></table>\n";
759 
760  return $line;
761  }
762 
774  public function getDiffHistLinks( RCCacheEntry $rc, array $query, $useParentheses = true ) {
775  $pageTitle = $rc->getTitle();
776  if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
777  // For categorizations we must swap the category title with the page title!
778  $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
779  if ( !$pageTitle ) {
780  // The page has been deleted, but the RC entry
781  // deletion job has not run yet. Just skip.
782  return '';
783  }
784  }
785 
786  $histLink = $this->linkRenderer->makeKnownLink(
787  $pageTitle,
788  new HtmlArmor( $this->message['hist'] ),
789  [ 'class' => 'mw-changeslist-history' ],
790  $query
791  );
792  if ( $useParentheses ) {
793  $retVal = $this->msg( 'parentheses' )
794  ->rawParams( $rc->difflink . $this->message['pipe-separator']
795  . $histLink )->escaped();
796  } else {
797  $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
798  Html::rawElement( 'span', [], $rc->difflink ) .
799  Html::rawElement( 'span', [], $histLink )
800  );
801  }
802  return ' ' . $retVal;
803  }
804 
811  protected function recentChangesBlock() {
812  if ( count( $this->rc_cache ) == 0 ) {
813  return '';
814  }
815 
816  $blockOut = '';
817  foreach ( $this->rc_cache as $block ) {
818  if ( count( $block ) < 2 ) {
819  $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
820  } else {
821  $blockOut .= $this->recentChangesBlockGroup( $block );
822  }
823  }
824 
825  if ( $blockOut === '' ) {
826  return '';
827  }
828  // $this->lastdate is kept up to date by recentChangesLine()
829  return Xml::element( 'h4', null, $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
830  }
831 
837  public function endRecentChangesList() {
838  return $this->recentChangesBlock() . '</div>';
839  }
840 }
getHTMLClassesForFilters( $rc)
Get an array of CSS classes attributed to filters for this row.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
const RC_CATEGORIZE
Definition: Defines.php:146
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
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
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:3050
$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=[])
static isDeleted( $rc, $field)
Determine if said field of a revision is hidden.
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:1410
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:82
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:3050
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:143
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:144