MediaWiki REL1_32
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.special.changeslist',
81 'mediawiki.special.changeslist.enhanced',
82 ] );
83 $this->getOutput()->addModules( [
84 'jquery.makeCollapsible',
85 'mediawiki.icon',
86 ] );
87
88 return '<div class="mw-changeslist">';
89 }
90
100 public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
101 $date = $this->getLanguage()->userDate(
102 $rc->mAttribs['rc_timestamp'],
103 $this->getUser()
104 );
105 if ( $this->lastdate === '' ) {
106 $this->lastdate = $date;
107 }
108
109 $ret = '';
110
111 # If it's a new day, flush the cache and update $this->lastdate
112 if ( $date !== $this->lastdate ) {
113 # Process current cache (uses $this->lastdate to generate a heading)
114 $ret = $this->recentChangesBlock();
115 $this->rc_cache = [];
116 $this->lastdate = $date;
117 }
118
119 $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched );
120 $this->addCacheEntry( $cacheEntry );
121
122 return $ret;
123 }
124
131 protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
132 $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
133
134 if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) {
135 $this->rc_cache[$cacheGroupingKey] = [];
136 }
137
138 array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry );
139 }
140
148 protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
149 $title = $cacheEntry->getTitle();
150 $cacheGroupingKey = $title->getPrefixedDBkey();
151
152 $type = $cacheEntry->mAttribs['rc_type'];
153
154 if ( $type == RC_LOG ) {
155 // Group by log type
156 $cacheGroupingKey = SpecialPage::getTitleFor(
157 'Log',
158 $cacheEntry->mAttribs['rc_log_type']
159 )->getPrefixedDBkey();
160 }
161
162 return $cacheGroupingKey;
163 }
164
171 protected function recentChangesBlockGroup( $block ) {
172 $recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' );
173
174 # Add the namespace and title of the block as part of the class
175 $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc', 'mw-changeslist-line' ];
176 if ( $block[0]->mAttribs['rc_log_type'] ) {
177 # Log entry
178 $tableClasses[] = 'mw-changeslist-log';
179 $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
180 . $block[0]->mAttribs['rc_log_type'] );
181 } else {
182 $tableClasses[] = 'mw-changeslist-edit';
183 $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
184 . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
185 }
186 if ( $block[0]->watched
187 && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
188 ) {
189 $tableClasses[] = 'mw-changeslist-line-watched';
190 } else {
191 $tableClasses[] = 'mw-changeslist-line-not-watched';
192 }
193
194 # Collate list of users
195 $userlinks = [];
196 # Other properties
197 $curId = 0;
198 # Some catalyst variables...
199 $namehidden = true;
200 $allLogs = true;
201 $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
202
203 # Default values for RC flags
204 $collectedRcFlags = [];
205 foreach ( $recentChangesFlags as $key => $value ) {
206 $flagGrouping = ( $recentChangesFlags[$key]['grouping'] ?? 'any' );
207 switch ( $flagGrouping ) {
208 case 'all':
209 $collectedRcFlags[$key] = true;
210 break;
211 case 'any':
212 $collectedRcFlags[$key] = false;
213 break;
214 default:
215 throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" );
216 }
217 }
218 foreach ( $block as $rcObj ) {
219 // If all log actions to this page were hidden, then don't
220 // give the name of the affected page for this block!
221 if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
222 $namehidden = false;
223 }
224 $u = $rcObj->userlink;
225 if ( !isset( $userlinks[$u] ) ) {
226 $userlinks[$u] = 0;
227 }
228 if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
229 $allLogs = false;
230 }
231 # Get the latest entry with a page_id and oldid
232 # since logs may not have these.
233 if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
234 $curId = $rcObj->mAttribs['rc_cur_id'];
235 }
236
237 $userlinks[$u]++;
238 }
239
240 # Sort the list and convert to text
241 krsort( $userlinks );
242 asort( $userlinks );
243 $users = [];
244 foreach ( $userlinks as $userlink => $count ) {
245 $text = $userlink;
246 $text .= $this->getLanguage()->getDirMark();
247 if ( $count > 1 ) {
248 $formattedCount = $this->msg( 'ntimes' )->numParams( $count )->escaped();
249 $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
250 }
251 array_push( $users, $text );
252 }
253
254 # Article link
255 $articleLink = '';
256 $revDeletedMsg = false;
257 if ( $namehidden ) {
258 $revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped();
259 } elseif ( $allLogs ) {
260 $articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
261 } else {
262 $articleLink = $this->getArticleLink( $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 && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
390 ) {
391 $classes[] = 'mw-enhanced-watched';
392 }
393 $classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
394
395 $separator = ' <span class="mw-changeslist-separator">. .</span> ';
396
397 $data['recentChangesFlags'] = [
398 'newpage' => $type == RC_NEW,
399 'minor' => $rcObj->mAttribs['rc_minor'],
400 'unpatrolled' => $rcObj->unpatrolled,
401 'bot' => $rcObj->mAttribs['rc_bot'],
402 ];
403
404 $params = $queryParams;
405
406 if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
407 $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
408 }
409
410 # Log timestamp
411 if ( $type == RC_LOG ) {
412 $link = htmlspecialchars( $rcObj->timestamp );
413 # Revision link
414 } elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
415 $link = Html::element( 'span', [ 'class' => 'history-deleted' ], $rcObj->timestamp );
416 } else {
417 $link = $this->linkRenderer->makeKnownLink(
418 $rcObj->getTitle(),
419 $rcObj->timestamp,
420 [],
421 $params
422 );
423 if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
424 $link = '<span class="history-deleted">' . $link . '</span> ';
425 }
426 }
427 $data['timestampLink'] = $link;
428
429 $currentAndLastLinks = '';
430 if ( !$type == RC_LOG || $type == RC_NEW ) {
431 $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams(
432 $rcObj->curlink .
433 $this->message['pipe-separator'] .
434 $rcObj->lastlink
435 )->escaped();
436 }
437 $data['currentAndLastLinks'] = $currentAndLastLinks;
438 $data['separatorAfterCurrentAndLastLinks'] = $separator;
439
440 # Character diff
441 if ( $RCShowChangedSize ) {
442 $cd = $this->formatCharacterDifference( $rcObj );
443 if ( $cd !== '' ) {
444 $data['characterDiff'] = $cd;
445 $data['separatorAfterCharacterDiff'] = $separator;
446 }
447 }
448
449 if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
450 $data['logEntry'] = $this->insertLogEntry( $rcObj );
451 } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
452 $data['comment'] = $this->insertComment( $rcObj );
453 } else {
454 # User links
455 $data['userLink'] = $rcObj->userlink;
456 $data['userTalkLink'] = $rcObj->usertalklink;
457 $data['comment'] = $this->insertComment( $rcObj );
458 }
459
460 # Rollback
461 $data['rollback'] = $this->getRollback( $rcObj );
462
463 # Tags
464 $data['tags'] = $this->getTags( $rcObj, $classes );
465
466 $attribs = $this->getDataAttributes( $rcObj );
467
468 // give the hook a chance to modify the data
469 $success = Hooks::run( 'EnhancedChangesListModifyLineData',
470 [ $this, &$data, $block, $rcObj, &$classes, &$attribs ] );
471 if ( !$success ) {
472 // skip entry if hook aborted it
473 return [];
474 }
475 $attribs = array_filter( $attribs,
476 [ Sanitizer::class, 'isReservedDataAttribute' ],
477 ARRAY_FILTER_USE_KEY
478 );
479
480 $lineParams['recentChangesFlagsRaw'] = [];
481 if ( isset( $data['recentChangesFlags'] ) ) {
482 $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] );
483 # FIXME: This is used by logic, don't return it in the template params.
484 $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags'];
485 unset( $data['recentChangesFlags'] );
486 }
487
488 if ( isset( $data['timestampLink'] ) ) {
489 $lineParams['timestampLink'] = $data['timestampLink'];
490 unset( $data['timestampLink'] );
491 }
492
493 $lineParams['classes'] = array_values( $classes );
494 $lineParams['attribs'] = Html::expandAttributes( $attribs );
495
496 // everything else: makes it easier for extensions to add or remove data
497 $lineParams['data'] = array_values( $data );
498
499 return $lineParams;
500 }
501
512 protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
513 if ( empty( $block ) ) {
514 return '';
515 }
516
517 # Changes message
518 static $nchanges = [];
519 static $sinceLastVisitMsg = [];
520
521 $n = count( $block );
522 if ( !isset( $nchanges[$n] ) ) {
523 $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
524 }
525
526 $sinceLast = 0;
527 $unvisitedOldid = null;
529 foreach ( $block as $rcObj ) {
530 // Same logic as below inside main foreach
531 if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
532 $sinceLast++;
533 $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
534 }
535 }
536 if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
537 $sinceLastVisitMsg[$sinceLast] =
538 $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
539 }
540
541 $currentRevision = 0;
542 foreach ( $block as $rcObj ) {
543 if ( !$currentRevision ) {
544 $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
545 }
546 }
547
548 # Total change link
549 $links = [];
551 $block0 = $block[0];
552 $last = $block[count( $block ) - 1];
553 if ( !$allLogs ) {
554 if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ||
555 $isnew ||
556 $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE
557 ) {
558 $links['total-changes'] = $nchanges[$n];
559 } else {
560 $links['total-changes'] = $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 if ( $sinceLast > 0 && $sinceLast < $n ) {
570 $links['total-changes-since-last'] = $this->linkRenderer->makeKnownLink(
571 $block0->getTitle(),
572 new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
573 [ 'class' => 'mw-changeslist-groupdiff' ],
574 $queryParams + [
575 'diff' => $currentRevision,
576 'oldid' => $unvisitedOldid,
577 ]
578 );
579 }
580 }
581 }
582
583 # History
584 if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
585 // don't show history link for logs
586 } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
587 $links['history'] = $this->message['enhancedrc-history'];
588 } else {
589 $params = $queryParams;
590 $params['action'] = 'history';
591
592 $links['history'] = $this->linkRenderer->makeKnownLink(
593 $block0->getTitle(),
594 new HtmlArmor( $this->message['enhancedrc-history'] ),
595 [ 'class' => 'mw-changeslist-history' ],
596 $params
597 );
598 }
599
600 # Allow others to alter, remove or add to these links
601 Hooks::run( 'EnhancedChangesList::getLogText',
602 [ $this, &$links, $block ] );
603
604 if ( !$links ) {
605 return '';
606 }
607
608 $logtext = implode( $this->message['pipe-separator'], $links );
609 $logtext = $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
610 return ' ' . $logtext;
611 }
612
619 protected function recentChangesBlockLine( $rcObj ) {
620 $data = [];
621
622 $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
623
624 $type = $rcObj->mAttribs['rc_type'];
625 $logType = $rcObj->mAttribs['rc_log_type'];
626 $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched );
627 $classes[] = 'mw-enhanced-rc';
628
629 if ( $logType ) {
630 # Log entry
631 $classes[] = 'mw-changeslist-log';
632 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
633 } else {
634 $classes[] = 'mw-changeslist-edit';
635 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
636 $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
637 }
638
639 # Flag and Timestamp
640 $data['recentChangesFlags'] = [
641 'newpage' => $type == RC_NEW,
642 'minor' => $rcObj->mAttribs['rc_minor'],
643 'unpatrolled' => $rcObj->unpatrolled,
644 'bot' => $rcObj->mAttribs['rc_bot'],
645 ];
646 // timestamp is not really a link here, but is called timestampLink
647 // for consistency with EnhancedChangesListModifyLineData
648 $data['timestampLink'] = htmlspecialchars( $rcObj->timestamp );
649
650 # Article or log link
651 if ( $logType ) {
652 $logPage = new LogPage( $logType );
653 $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
654 $logName = $logPage->getName()->text();
655 $data['logLink'] = $this->msg( 'parentheses' )
656 ->rawParams(
657 $this->linkRenderer->makeKnownLink( $logTitle, $logName )
658 )->escaped();
659 } else {
660 $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
661 }
662
663 # Diff and hist links
664 if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
665 $query['action'] = 'history';
666 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
667 }
668 $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator">. .</span> ';
669
670 # Character diff
671 if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
672 $cd = $this->formatCharacterDifference( $rcObj );
673 if ( $cd !== '' ) {
674 $data['characterDiff'] = $cd;
675 $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator">. .</span> ';
676 }
677 }
678
679 if ( $type == RC_LOG ) {
680 $data['logEntry'] = $this->insertLogEntry( $rcObj );
681 } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
682 $data['comment'] = $this->insertComment( $rcObj );
683 } else {
684 $data['userLink'] = $rcObj->userlink;
685 $data['userTalkLink'] = $rcObj->usertalklink;
686 $data['comment'] = $this->insertComment( $rcObj );
687 if ( $type == RC_CATEGORIZE ) {
688 $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
689 }
690 $data['rollback'] = $this->getRollback( $rcObj );
691 }
692
693 # Tags
694 $data['tags'] = $this->getTags( $rcObj, $classes );
695
696 # Show how many people are watching this if enabled
697 $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
698
699 $data['attribs'] = array_merge( $this->getDataAttributes( $rcObj ), [ 'class' => $classes ] );
700
701 // give the hook a chance to modify the data
702 $success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
703 [ $this, &$data, $rcObj ] );
704 if ( !$success ) {
705 // skip entry if hook aborted it
706 return '';
707 }
708 $attribs = $data['attribs'];
709 unset( $data['attribs'] );
710 $attribs = array_filter( $attribs, function ( $key ) {
711 return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
712 }, ARRAY_FILTER_USE_KEY );
713
714 $prefix = '';
715 if ( is_callable( $this->changeLinePrefixer ) ) {
716 $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false );
717 }
718
719 $line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
720 // Highlight block
721 $line .= Html::rawElement( 'td', [],
723 );
724
725 $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
726 $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
727 $line .= '<td class="mw-enhanced-rc" colspan="2">';
728
729 if ( isset( $data['recentChangesFlags'] ) ) {
730 $line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
731 unset( $data['recentChangesFlags'] );
732 }
733
734 if ( isset( $data['timestampLink'] ) ) {
735 $line .= "\u{00A0}" . $data['timestampLink'];
736 unset( $data['timestampLink'] );
737 }
738 $line .= "\u{00A0}</td>";
739 $line .= Html::openElement( 'td', [
740 'class' => 'mw-changeslist-line-inner',
741 // Used for reliable determination of the affiliated page
742 'data-target-page' => $rcObj->getTitle(),
743 ] );
744
745 // everything else: makes it easier for extensions to add or remove data
746 $line .= implode( '', $data );
747
748 $line .= "</td></tr></table>\n";
749
750 return $line;
751 }
752
763 public function getDiffHistLinks( RCCacheEntry $rc, array $query ) {
764 $pageTitle = $rc->getTitle();
765 if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
766 // For categorizations we must swap the category title with the page title!
767 $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
768 if ( !$pageTitle ) {
769 // The page has been deleted, but the RC entry
770 // deletion job has not run yet. Just skip.
771 return '';
772 }
773 }
774
775 $retVal = ' ' . $this->msg( 'parentheses' )
776 ->rawParams( $rc->difflink . $this->message['pipe-separator']
777 . $this->linkRenderer->makeKnownLink(
778 $pageTitle,
779 new HtmlArmor( $this->message['hist'] ),
780 [ 'class' => 'mw-changeslist-history' ],
781 $query
782 ) )->escaped();
783 return $retVal;
784 }
785
792 protected function recentChangesBlock() {
793 if ( count( $this->rc_cache ) == 0 ) {
794 return '';
795 }
796
797 $blockOut = '';
798 foreach ( $this->rc_cache as $block ) {
799 if ( count( $block ) < 2 ) {
800 $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
801 } else {
802 $blockOut .= $this->recentChangesBlockGroup( $block );
803 }
804 }
805
806 if ( $blockOut === '' ) {
807 return '';
808 }
809 // $this->lastdate is kept up to date by recentChangesLine()
810 return Xml::element( 'h4', null, $this->lastdate ) . "\n<div>" . $blockOut . '</div>';
811 }
812
818 public function endRecentChangesList() {
819 return $this->recentChangesBlock() . '</div>';
820 }
821}
$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.
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.
getDiffHistLinks(RCCacheEntry $rc, array $query)
Returns value to be used in 'historyLink' element of $data param in EnhancedChangesListModifyBlockLin...
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:47
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
const RC_NEW
Definition Defines.php:143
const RC_LOG
Definition Defines.php:144
const RC_CATEGORIZE
Definition Defines.php:146
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:3108
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
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:2054
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:2213
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3106
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:2063
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:1656
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