MediaWiki REL1_33
EnhancedChangesList.php
Go to the documentation of this file.
1<?php
24
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(
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}
$line
Definition cdb.php:59
maybeWatchedLink( $link, $watched=false)
static userCan( $rc, $field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision,...
formatCharacterDifference(RecentChange $old, RecentChange $new=null)
Format the character difference of one or several changes.
array $filterGroups
getHighlightsContainerDiv()
Get the container for highlights that are used in the new StructuredFilters system.
recentChangesFlags( $flags, $nothing="\u{00A0}")
Returns the appropriate flags for new page, minor change and patrolling.
getDataAttributes(RecentChange $rc)
Get recommended data attributes for a change line.
numberofWatchingusers( $count)
Returns the string which indicates the number of watching users.
getHTMLClasses( $rc, $watched)
Get an array of default HTML class attributes for the change.
getTags(RecentChange $rc, array &$classes)
getArticleLink(&$rc, $unpatrolled, $watched)
getRollback(RecentChange $rc)
insertLogEntry( $rc)
Insert a formatted action.
static isDeleted( $rc, $field)
Determine if said field of a revision is hidden.
insertComment( $rc)
Insert a formatted comment.
isCategorizationWithoutRevision( $rcObj)
Determines whether a revision is linked to this change; this may not be the case when the categorizat...
getHTMLClassesForFilters( $rc)
Get an array of CSS classes attributed to filters for this row.
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
TemplateParser $templateParser
recentChangesBlock()
If enhanced RC is in use, this function takes the previously cached RC lines, arranges them,...
makeCacheGroupingKey(RCCacheEntry $cacheEntry)
beginRecentChangesList()
Add the JavaScript file for enhanced changeslist.
endRecentChangesList()
Returns text for the end of RC If enhanced RC is in use, returns pretty much all the text.
getDiffHistLinks(RCCacheEntry $rc, array $query, $useParentheses=true)
Returns value to be used in 'historyLink' element of $data param in EnhancedChangesListModifyBlockLin...
addCacheEntry(RCCacheEntry $cacheEntry)
Put accumulated information into the cache, for later display.
RCCacheEntryFactory $cacheEntryFactory
recentChangesBlockGroup( $block)
Enhanced RC group.
getLineData(array $block, RCCacheEntry $rcObj, array $queryParams=[])
array $rc_cache
Array of array of RCCacheEntry.
recentChangesBlockLine( $rcObj)
Enhanced RC ungrouped line.
__construct( $obj, array $filterGroups=[])
recentChangesLine(&$rc, $watched=false, $linenumber=null)
Format a line for enhanced recentchange (aka with javascript and block of lines).
getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden)
Generates amount of changes (linking to diff ) & link to history.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
Class to simplify the use of log pages.
Definition LogPage.php:33
const DELETED_ACTION
Definition LogPage.php:34
MediaWiki exception.
getAttribute( $name)
Get an attribute value.
const DELETED_TEXT
Definition Revision.php:46
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:38
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
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
const RC_NEW
Definition Defines.php:152
const RC_LOG
Definition Defines.php:153
const RC_CATEGORIZE
Definition Defines.php:155
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:3071
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:955
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:2003
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:2162
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3069
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:2012
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:1617
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:37
Interface for objects which can provide a MediaWiki context on request.
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))
$last
$lines
Definition router.php:61
$params