blob: 34adea9c4ccac08f8f2ac7a01faf10ce1b6adc3b [file] [log] [blame]
<?php
/**
* Base class for all changes lists.
*
* The class is used for formatting recent changes, related changes and watchlist.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\IResultWrapper;
class ChangesList extends ContextSource {
const CSS_CLASS_PREFIX = 'mw-changeslist-';
/**
* @var Skin
*/
public $skin;
protected $watchlist = false;
protected $lastdate;
protected $message;
protected $rc_cache;
protected $rcCacheIndex;
protected $rclistOpen;
protected $rcMoveIndex;
/** @var callable */
protected $changeLinePrefixer;
/** @var MapCacheLRU */
protected $watchMsgCache;
/**
* @var LinkRenderer
*/
protected $linkRenderer;
/**
* @var array
*/
protected $filterGroups;
/**
* @param Skin|IContextSource $obj
* @param array $filterGroups Array of ChangesListFilterGroup objects (currently optional)
*/
public function __construct( $obj, array $filterGroups = [] ) {
if ( $obj instanceof IContextSource ) {
$this->setContext( $obj );
$this->skin = $obj->getSkin();
} else {
$this->setContext( $obj->getContext() );
$this->skin = $obj;
}
$this->preCacheMessages();
$this->watchMsgCache = new MapCacheLRU( 50 );
$this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
$this->filterGroups = $filterGroups;
}
/**
* Fetch an appropriate changes list class for the specified context
* Some users might want to use an enhanced list format, for instance
*
* @param IContextSource $context
* @param array $groups Array of ChangesListFilterGroup objects (currently optional)
* @return ChangesList
*/
public static function newFromContext( IContextSource $context, array $groups = [] ) {
$user = $context->getUser();
$sk = $context->getSkin();
$list = null;
if ( Hooks::run( 'FetchChangesList', [ $user, &$sk, &$list ] ) ) {
$new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
return $new ?
new EnhancedChangesList( $context, $groups ) :
new OldChangesList( $context, $groups );
} else {
return $list;
}
}
/**
* Format a line
*
* @since 1.27
*
* @param RecentChange &$rc Passed by reference
* @param bool $watched (default false)
* @param int|null $linenumber (default null)
*
* @return string|bool
*/
public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
throw new RuntimeException( 'recentChangesLine should be implemented' );
}
/**
* Get the container for highlights that are used in the new StructuredFilters
* system
*
* @return string HTML structure of the highlight container div
*/
protected function getHighlightsContainerDiv() {
$highlightColorDivs = '';
foreach ( [ 'none', 'c1', 'c2', 'c3', 'c4', 'c5' ] as $color ) {
$highlightColorDivs .= Html::rawElement(
'div',
[
'class' => 'mw-rcfilters-ui-highlights-color-' . $color,
'data-color' => $color
]
);
}
return Html::rawElement(
'div',
[ 'class' => 'mw-rcfilters-ui-highlights' ],
$highlightColorDivs
);
}
/**
* Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag
* @param bool $value
*/
public function setWatchlistDivs( $value = true ) {
$this->watchlist = $value;
}
/**
* @return bool True when setWatchlistDivs has been called
* @since 1.23
*/
public function isWatchlist() {
return (bool)$this->watchlist;
}
/**
* As we use the same small set of messages in various methods and that
* they are called often, we call them once and save them in $this->message
*/
private function preCacheMessages() {
if ( !isset( $this->message ) ) {
foreach ( [
'cur', 'diff', 'hist', 'enhancedrc-history', 'last', 'blocklink', 'history',
'semicolon-separator', 'pipe-separator' ] as $msg
) {
$this->message[$msg] = $this->msg( $msg )->escaped();
}
}
}
/**
* Returns the appropriate flags for new page, minor change and patrolling
* @param array $flags Associative array of 'flag' => Bool
* @param string $nothing To use for empty space
* @return string
*/
public function recentChangesFlags( $flags, $nothing = "\u{00A0}" ) {
$f = '';
foreach ( array_keys( $this->getConfig()->get( 'RecentChangesFlags' ) ) as $flag ) {
$f .= isset( $flags[$flag] ) && $flags[$flag]
? self::flag( $flag, $this->getContext() )
: $nothing;
}
return $f;
}
/**
* Get an array of default HTML class attributes for the change.
*
* @param RecentChange|RCCacheEntry $rc
* @param string|bool $watched Optionally timestamp for adding watched class
*
* @return string[] List of CSS class names
*/
protected function getHTMLClasses( $rc, $watched ) {
$classes = [ self::CSS_CLASS_PREFIX . 'line' ];
$logType = $rc->mAttribs['rc_log_type'];
if ( $logType ) {
$classes[] = self::CSS_CLASS_PREFIX . 'log';
$classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'log-' . $logType );
} else {
$classes[] = self::CSS_CLASS_PREFIX . 'edit';
$classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns' .
$rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
}
// Indicate watched status on the line to allow for more
// comprehensive styling.
$classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched
? self::CSS_CLASS_PREFIX . 'line-watched'
: self::CSS_CLASS_PREFIX . 'line-not-watched';
$classes = array_merge( $classes, $this->getHTMLClassesForFilters( $rc ) );
return $classes;
}
/**
* Get an array of CSS classes attributed to filters for this row. Used for highlighting
* in the front-end.
*
* @param RecentChange $rc
* @return array Array of CSS classes
*/
protected function getHTMLClassesForFilters( $rc ) {
$classes = [];
$classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns-' .
$rc->mAttribs['rc_namespace'] );
if ( $this->filterGroups !== null ) {
foreach ( $this->filterGroups as $filterGroup ) {
foreach ( $filterGroup->getFilters() as $filter ) {
$filter->applyCssClassIfNeeded( $this, $rc, $classes );
}
}
}
return $classes;
}
/**
* Make an "<abbr>" element for a given change flag. The flag indicating a new page, minor edit,
* bot edit, or unpatrolled edit. In English it typically contains "N", "m", "b", or "!".
*
* @param string $flag One key of $wgRecentChangesFlags
* @param IContextSource|null $context
* @return string HTML
*/
public static function flag( $flag, IContextSource $context = null ) {
static $map = [ 'minoredit' => 'minor', 'botedit' => 'bot' ];
static $flagInfos = null;
if ( is_null( $flagInfos ) ) {
global $wgRecentChangesFlags;
$flagInfos = [];
foreach ( $wgRecentChangesFlags as $key => $value ) {
$flagInfos[$key]['letter'] = $value['letter'];
$flagInfos[$key]['title'] = $value['title'];
// Allow customized class name, fall back to flag name
$flagInfos[$key]['class'] = $value['class'] ?? $key;
}
}
$context = $context ?: RequestContext::getMain();
// Inconsistent naming, kepted for b/c
if ( isset( $map[$flag] ) ) {
$flag = $map[$flag];
}
$info = $flagInfos[$flag];
return Html::element( 'abbr', [
'class' => $info['class'],
'title' => wfMessage( $info['title'] )->setContext( $context )->text(),
], wfMessage( $info['letter'] )->setContext( $context )->text() );
}
/**
* Returns text for the start of the tabular part of RC
* @return string
*/
public function beginRecentChangesList() {
$this->rc_cache = [];
$this->rcMoveIndex = 0;
$this->rcCacheIndex = 0;
$this->lastdate = '';
$this->rclistOpen = false;
$this->getOutput()->addModuleStyles( [
'mediawiki.interface.helpers.styles',
'mediawiki.special.changeslist'
] );
return '<div class="mw-changeslist">';
}
/**
* @param IResultWrapper|array $rows
*/
public function initChangesListRows( $rows ) {
Hooks::run( 'ChangesListInitRows', [ $this, $rows ] );
}
/**
* Show formatted char difference
*
* Needs the css module 'mediawiki.special.changeslist' to style output
*
* @param int $old Number of bytes
* @param int $new Number of bytes
* @param IContextSource|null $context
* @return string
*/
public static function showCharacterDifference( $old, $new, IContextSource $context = null ) {
if ( !$context ) {
$context = RequestContext::getMain();
}
$new = (int)$new;
$old = (int)$old;
$szdiff = $new - $old;
$lang = $context->getLanguage();
$config = $context->getConfig();
$code = $lang->getCode();
static $fastCharDiff = [];
if ( !isset( $fastCharDiff[$code] ) ) {
$fastCharDiff[$code] = $config->get( 'MiserMode' )
|| $context->msg( 'rc-change-size' )->plain() === '$1';
}
$formattedSize = $lang->formatNum( $szdiff );
if ( !$fastCharDiff[$code] ) {
$formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text();
}
if ( abs( $szdiff ) > abs( $config->get( 'RCChangedSizeThreshold' ) ) ) {
$tag = 'strong';
} else {
$tag = 'span';
}
if ( $szdiff === 0 ) {
$formattedSizeClass = 'mw-plusminus-null';
} elseif ( $szdiff > 0 ) {
$formattedSize = '+' . $formattedSize;
$formattedSizeClass = 'mw-plusminus-pos';
} else {
$formattedSizeClass = 'mw-plusminus-neg';
}
$formattedSizeClass .= ' mw-diff-bytes';
$formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text();
return Html::element( $tag,
[ 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ],
$formattedSize ) . $lang->getDirMark();
}
/**
* Format the character difference of one or several changes.
*
* @param RecentChange $old
* @param RecentChange|null $new Last change to use, if not provided, $old will be used
* @return string HTML fragment
*/
public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) {
$oldlen = $old->mAttribs['rc_old_len'];
if ( $new ) {
$newlen = $new->mAttribs['rc_new_len'];
} else {
$newlen = $old->mAttribs['rc_new_len'];
}
if ( $oldlen === null || $newlen === null ) {
return '';
}
return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() );
}
/**
* Returns text for the end of RC
* @return string
*/
public function endRecentChangesList() {
$out = $this->rclistOpen ? "</ul>\n" : '';
$out .= '</div>';
return $out;
}
/**
* Render the date and time of a revision in the current user language
* based on whether the user is able to view this information or not.
* @param Revision $rev
* @param User $user
* @param Language $lang
* @param Title|null $title (optional) where Title does not match
* the Title associated with the Revision
* @internal For usage by Pager classes only (e.g. HistoryPager and ContribsPager).
* @return string HTML
*/
public static function revDateLink( Revision $rev, User $user, Language $lang, $title = null ) {
$ts = $rev->getTimestamp();
$date = $lang->userTimeAndDate( $ts, $user );
if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
$title ?? $rev->getTitle(),
$date,
[ 'class' => 'mw-changeslist-date' ],
[ 'oldid' => $rev->getId() ]
);
} else {
$link = htmlspecialchars( $date );
}
if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$link = "<span class=\"history-deleted mw-changeslist-date\">$link</span>";
}
return $link;
}
/**
* @param string &$s HTML to update
* @param mixed $rc_timestamp
*/
public function insertDateHeader( &$s, $rc_timestamp ) {
# Make date header if necessary
$date = $this->getLanguage()->userDate( $rc_timestamp, $this->getUser() );
if ( $date != $this->lastdate ) {
if ( $this->lastdate != '' ) {
$s .= "</ul>\n";
}
$s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">";
$this->lastdate = $date;
$this->rclistOpen = true;
}
}
/**
* @param string &$s HTML to update
* @param Title $title
* @param string $logtype
*/
public function insertLog( &$s, $title, $logtype ) {
$page = new LogPage( $logtype );
$logname = $page->getName()->setContext( $this->getContext() )->text();
$s .= $this->msg( 'parentheses' )->rawParams(
$this->linkRenderer->makeKnownLink( $title, $logname )
)->escaped();
}
/**
* @param string &$s HTML to update
* @param RecentChange &$rc
* @param bool|null $unpatrolled Unused variable, since 1.27.
*/
public function insertDiffHist( &$s, &$rc, $unpatrolled = null ) {
# Diff link
if (
$rc->mAttribs['rc_type'] == RC_NEW ||
$rc->mAttribs['rc_type'] == RC_LOG ||
$rc->mAttribs['rc_type'] == RC_CATEGORIZE
) {
$diffLink = $this->message['diff'];
} elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
$diffLink = $this->message['diff'];
} else {
$query = [
'curid' => $rc->mAttribs['rc_cur_id'],
'diff' => $rc->mAttribs['rc_this_oldid'],
'oldid' => $rc->mAttribs['rc_last_oldid']
];
$diffLink = $this->linkRenderer->makeKnownLink(
$rc->getTitle(),
new HtmlArmor( $this->message['diff'] ),
[ 'class' => 'mw-changeslist-diff' ],
$query
);
}
if ( $rc->mAttribs['rc_type'] == RC_CATEGORIZE ) {
$histLink = $this->message['hist'];
} else {
$histLink = $this->linkRenderer->makeKnownLink(
$rc->getTitle(),
new HtmlArmor( $this->message['hist'] ),
[ 'class' => 'mw-changeslist-history' ],
[
'curid' => $rc->mAttribs['rc_cur_id'],
'action' => 'history'
]
);
}
$s .= Html::rawElement( 'div', [ 'class' => 'mw-changeslist-links' ],
Html::rawElement( 'span', [], $diffLink ) .
Html::rawElement( 'span', [], $histLink )
) .
' <span class="mw-changeslist-separator"></span> ';
}
/**
* @param RecentChange &$rc
* @param bool $unpatrolled
* @param bool $watched
* @return string HTML
* @since 1.26
*/
public function getArticleLink( &$rc, $unpatrolled, $watched ) {
$params = [];
if ( $rc->getTitle()->isRedirect() ) {
$params = [ 'redirect' => 'no' ];
}
$articlelink = $this->linkRenderer->makeLink(
$rc->getTitle(),
null,
[ 'class' => 'mw-changeslist-title' ],
$params
);
if ( $this->isDeleted( $rc, Revision::DELETED_TEXT ) ) {
$articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
}
# To allow for boldening pages watched by this user
$articlelink = "<span class=\"mw-title\">{$articlelink}</span>";
# RTL/LTR marker
$articlelink .= $this->getLanguage()->getDirMark();
# TODO: Deprecate the $s argument, it seems happily unused.
$s = '';
# Avoid PHP 7.1 warning from passing $this by reference
$changesList = $this;
Hooks::run( 'ChangesListInsertArticleLink',
[ &$changesList, &$articlelink, &$s, &$rc, $unpatrolled, $watched ] );
return "{$s} {$articlelink}";
}
/**
* Get the timestamp from $rc formatted with current user's settings
* and a separator
*
* @param RecentChange $rc
* @deprecated use revDateLink instead.
* @return string HTML fragment
*/
public function getTimestamp( $rc ) {
// @todo FIXME: Hard coded ". .". Is there a message for this? Should there be?
return $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' .
htmlspecialchars( $this->getLanguage()->userTime(
$rc->mAttribs['rc_timestamp'],
$this->getUser()
) ) . '</span> <span class="mw-changeslist-separator"></span> ';
}
/**
* Insert time timestamp string from $rc into $s
*
* @param string &$s HTML to update
* @param RecentChange $rc
*/
public function insertTimestamp( &$s, $rc ) {
$s .= $this->getTimestamp( $rc );
}
/**
* Insert links to user page, user talk page and eventually a blocking link
*
* @param string &$s HTML to update
* @param RecentChange &$rc
*/
public function insertUserRelatedLinks( &$s, &$rc ) {
if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
$s .= ' <span class="history-deleted">' .
$this->msg( 'rev-deleted-user' )->escaped() . '</span>';
} else {
$s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'],
$rc->mAttribs['rc_user_text'] );
$s .= Linker::userToolLinks(
$rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'],
false, 0, null,
// The text content of tools is not wrapped with parenthesises or "piped".
// This will be handled in CSS (T205581).
false
);
}
}
/**
* Insert a formatted action
*
* @param RecentChange $rc
* @return string
*/
public function insertLogEntry( $rc ) {
$formatter = LogFormatter::newFromRow( $rc->mAttribs );
$formatter->setContext( $this->getContext() );
$formatter->setShowUserToolLinks( true );
$mark = $this->getLanguage()->getDirMark();
return $formatter->getActionText() . " $mark" . $formatter->getComment();
}
/**
* Insert a formatted comment
* @param RecentChange $rc
* @return string
*/
public function insertComment( $rc ) {
if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
return ' <span class="history-deleted">' .
$this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
} else {
return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle(),
// Whether section links should refer to local page (using default false)
false,
// wikid to generate links for (using default null) */
null,
// whether parentheses should be rendered as part of the message
false );
}
}
/**
* Returns the string which indicates the number of watching users
* @param int $count Number of user watching a page
* @return string
*/
protected function numberofWatchingusers( $count ) {
if ( $count <= 0 ) {
return '';
}
return $this->watchMsgCache->getWithSetCallback(
"watching-users-msg:$count",
function () use ( $count ) {
return $this->msg( 'number-of-watching-users-for-recent-changes' )
->numParams( $count )->escaped();
}
);
}
/**
* Determine if said field of a revision is hidden
* @param RCCacheEntry|RecentChange $rc
* @param int $field One of DELETED_* bitfield constants
* @return bool
*/
public static function isDeleted( $rc, $field ) {
return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
}
/**
* Determine if the current user is allowed to view a particular
* field of this revision, if it's marked as deleted.
* @param RCCacheEntry|RecentChange $rc
* @param int $field
* @param User|null $user User object to check, or null to use $wgUser
* @return bool
*/
public static function userCan( $rc, $field, User $user = null ) {
if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
} else {
return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
}
}
/**
* @param string $link
* @param bool $watched
* @return string
*/
protected function maybeWatchedLink( $link, $watched = false ) {
if ( $watched ) {
return '<strong class="mw-watched">' . $link . '</strong>';
} else {
return '<span class="mw-rc-unwatched">' . $link . '</span>';
}
}
/**
* Insert a rollback link
*
* @param string &$s
* @param RecentChange &$rc
*/
public function insertRollback( &$s, &$rc ) {
if ( $rc->mAttribs['rc_type'] == RC_EDIT
&& $rc->mAttribs['rc_this_oldid']
&& $rc->mAttribs['rc_cur_id']
&& $rc->getAttribute( 'page_latest' ) == $rc->mAttribs['rc_this_oldid']
) {
$title = $rc->getTitle();
/** Check for rollback permissions, disallow special pages, and only
* show a link on the top-most revision */
if ( $title->quickUserCan( 'rollback', $this->getUser() ) ) {
$rev = new Revision( [
'title' => $title,
'id' => $rc->mAttribs['rc_this_oldid'],
'user' => $rc->mAttribs['rc_user'],
'user_text' => $rc->mAttribs['rc_user_text'],
'actor' => $rc->mAttribs['rc_actor'] ?? null,
'deleted' => $rc->mAttribs['rc_deleted']
] );
$s .= ' ' . Linker::generateRollback( $rev, $this->getContext() );
}
}
}
/**
* @param RecentChange $rc
* @return string
* @since 1.26
*/
public function getRollback( RecentChange $rc ) {
$s = '';
$this->insertRollback( $s, $rc );
return $s;
}
/**
* @param string &$s
* @param RecentChange &$rc
* @param array &$classes
*/
public function insertTags( &$s, &$rc, &$classes ) {
if ( empty( $rc->mAttribs['ts_tags'] ) ) {
return;
}
list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
$rc->mAttribs['ts_tags'],
'changeslist',
$this->getContext()
);
$classes = array_merge( $classes, $newClasses );
$s .= ' ' . $tagSummary;
}
/**
* @param RecentChange $rc
* @param array &$classes
* @return string
* @since 1.26
*/
public function getTags( RecentChange $rc, array &$classes ) {
$s = '';
$this->insertTags( $s, $rc, $classes );
return $s;
}
public function insertExtra( &$s, &$rc, &$classes ) {
// Empty, used for subclasses to add anything special.
}
protected function showAsUnpatrolled( RecentChange $rc ) {
return self::isUnpatrolled( $rc, $this->getUser() );
}
/**
* @param object|RecentChange $rc Database row from recentchanges or a RecentChange object
* @param User $user
* @return bool
*/
public static function isUnpatrolled( $rc, User $user ) {
if ( $rc instanceof RecentChange ) {
$isPatrolled = $rc->mAttribs['rc_patrolled'];
$rcType = $rc->mAttribs['rc_type'];
$rcLogType = $rc->mAttribs['rc_log_type'];
} else {
$isPatrolled = $rc->rc_patrolled;
$rcType = $rc->rc_type;
$rcLogType = $rc->rc_log_type;
}
if ( !$isPatrolled ) {
if ( $user->useRCPatrol() ) {
return true;
}
if ( $user->useNPPatrol() && $rcType == RC_NEW ) {
return true;
}
if ( $user->useFilePatrol() && $rcLogType == 'upload' ) {
return true;
}
}
return false;
}
/**
* Determines whether a revision is linked to this change; this may not be the case
* when the categorization wasn't done by an edit but a conditional parser function
*
* @since 1.27
*
* @param RecentChange|RCCacheEntry $rcObj
* @return bool
*/
protected function isCategorizationWithoutRevision( $rcObj ) {
return intval( $rcObj->getAttribute( 'rc_type' ) ) === RC_CATEGORIZE
&& intval( $rcObj->getAttribute( 'rc_this_oldid' ) ) === 0;
}
/**
* Get recommended data attributes for a change line.
* @param RecentChange $rc
* @return string[] attribute name => value
*/
protected function getDataAttributes( RecentChange $rc ) {
$attrs = [];
$type = $rc->getAttribute( 'rc_source' );
switch ( $type ) {
case RecentChange::SRC_EDIT:
case RecentChange::SRC_NEW:
$attrs['data-mw-revid'] = $rc->mAttribs['rc_this_oldid'];
break;
case RecentChange::SRC_LOG:
$attrs['data-mw-logid'] = $rc->mAttribs['rc_logid'];
$attrs['data-mw-logaction'] =
$rc->mAttribs['rc_log_type'] . '/' . $rc->mAttribs['rc_log_action'];
break;
}
$attrs[ 'data-mw-ts' ] = $rc->getAttribute( 'rc_timestamp' );
return $attrs;
}
/**
* Sets the callable that generates a change line prefix added to the beginning of each line.
*
* @param callable $prefixer Callable to run that generates the change line prefix.
* Takes three parameters: a RecentChange object, a ChangesList object,
* and whether the current entry is a grouped entry.
*/
public function setChangeLinePrefixer( callable $prefixer ) {
$this->changeLinePrefixer = $prefixer;
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy