2017-12-16 00:26:25 +00:00
|
|
|
<?php
|
|
|
|
|
2018-04-29 17:52:45 +00:00
|
|
|
use MediaWiki\Linker\LinkRenderer;
|
|
|
|
|
2017-12-16 00:26:25 +00:00
|
|
|
/**
|
|
|
|
* Class to build paginated filter list
|
|
|
|
*/
|
|
|
|
class AbuseFilterPager extends TablePager {
|
|
|
|
|
|
|
|
/**
|
2018-04-29 17:52:45 +00:00
|
|
|
* @var LinkRenderer
|
2017-12-16 00:26:25 +00:00
|
|
|
*/
|
|
|
|
protected $linkRenderer;
|
|
|
|
|
2018-11-08 14:34:32 +00:00
|
|
|
/**
|
|
|
|
* @var AbuseFilterViewList The associated page
|
|
|
|
*/
|
|
|
|
public $mPage;
|
|
|
|
/**
|
|
|
|
* @var array Query WHERE conditions
|
|
|
|
*/
|
|
|
|
public $mConds;
|
|
|
|
/**
|
|
|
|
* @var string[] Info used for searching patterns. The first element is the specified pattern,
|
|
|
|
* the second is the search mode (LIKE, RLIKE or IRLIKE)
|
|
|
|
*/
|
|
|
|
public $mQuery;
|
2018-04-29 17:52:45 +00:00
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @param AbuseFilterViewList $page
|
|
|
|
* @param array $conds
|
|
|
|
* @param LinkRenderer $linkRenderer
|
|
|
|
* @param array $query
|
|
|
|
*/
|
2018-10-17 05:15:21 +00:00
|
|
|
public function __construct( AbuseFilterViewList $page, $conds, LinkRenderer $linkRenderer,
|
|
|
|
$query ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
$this->mPage = $page;
|
|
|
|
$this->mConds = $conds;
|
|
|
|
$this->linkRenderer = $linkRenderer;
|
2018-02-17 14:09:36 +00:00
|
|
|
$this->mQuery = $query;
|
2017-12-16 00:26:25 +00:00
|
|
|
parent::__construct( $this->mPage->getContext() );
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getQueryInfo() {
|
2017-12-16 00:26:25 +00:00
|
|
|
return [
|
|
|
|
'tables' => [ 'abuse_filter' ],
|
|
|
|
'fields' => [
|
2018-05-03 18:32:03 +00:00
|
|
|
// All columns but af_comments
|
2017-12-16 00:26:25 +00:00
|
|
|
'af_id',
|
|
|
|
'af_enabled',
|
|
|
|
'af_deleted',
|
2018-02-17 14:09:36 +00:00
|
|
|
'af_pattern',
|
2017-12-16 00:26:25 +00:00
|
|
|
'af_global',
|
|
|
|
'af_public_comments',
|
|
|
|
'af_hidden',
|
|
|
|
'af_hit_count',
|
|
|
|
'af_timestamp',
|
|
|
|
'af_user_text',
|
|
|
|
'af_user',
|
|
|
|
'af_actions',
|
|
|
|
'af_group',
|
2018-05-03 18:32:03 +00:00
|
|
|
'af_throttled'
|
2017-12-16 00:26:25 +00:00
|
|
|
],
|
|
|
|
'conds' => $this->mConds,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @see Pager::getFieldNames()
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getFieldNames() {
|
2017-12-16 00:26:25 +00:00
|
|
|
static $headers = null;
|
|
|
|
|
|
|
|
if ( !empty( $headers ) ) {
|
|
|
|
return $headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
$headers = [
|
|
|
|
'af_id' => 'abusefilter-list-id',
|
|
|
|
'af_public_comments' => 'abusefilter-list-public',
|
|
|
|
'af_actions' => 'abusefilter-list-consequences',
|
|
|
|
'af_enabled' => 'abusefilter-list-status',
|
|
|
|
'af_timestamp' => 'abusefilter-list-lastmodified',
|
|
|
|
'af_hidden' => 'abusefilter-list-visibility',
|
|
|
|
];
|
|
|
|
|
|
|
|
if ( $this->mPage->getUser()->isAllowed( 'abusefilter-log-detail' ) ) {
|
|
|
|
$headers['af_hit_count'] = 'abusefilter-list-hitcount';
|
|
|
|
}
|
|
|
|
|
2018-02-17 14:09:36 +00:00
|
|
|
if ( AbuseFilterView::canViewPrivate() && !empty( $this->mQuery[0] ) ) {
|
|
|
|
$headers['af_pattern'] = 'abusefilter-list-pattern';
|
|
|
|
}
|
|
|
|
|
2018-03-10 21:47:33 +00:00
|
|
|
if ( count( $this->getConfig()->get( 'AbuseFilterValidGroups' ) ) > 1 ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
$headers['af_group'] = 'abusefilter-list-group';
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $headers as &$msg ) {
|
|
|
|
$msg = $this->msg( $msg )->text();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $headers;
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @param string $value
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function formatValue( $name, $value ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
$lang = $this->getLanguage();
|
|
|
|
$row = $this->mCurrentRow;
|
|
|
|
|
|
|
|
switch ( $name ) {
|
|
|
|
case 'af_id':
|
|
|
|
return $this->linkRenderer->makeLink(
|
|
|
|
SpecialPage::getTitleFor( 'AbuseFilter', intval( $value ) ),
|
|
|
|
$lang->formatNum( intval( $value ) )
|
|
|
|
);
|
2018-02-17 14:09:36 +00:00
|
|
|
case 'af_pattern':
|
|
|
|
if ( $this->mQuery[1] === 'LIKE' ) {
|
2018-08-20 17:40:06 +00:00
|
|
|
$position = mb_stripos( $row->af_pattern, $this->mQuery[0] );
|
2018-02-17 14:09:36 +00:00
|
|
|
if ( $position === false ) {
|
|
|
|
// This may happen due to problems with character encoding
|
|
|
|
// which aren't easy to solve
|
2018-08-20 17:40:06 +00:00
|
|
|
return htmlspecialchars( mb_substr( $row->af_pattern, 0, 50 ) );
|
2018-02-17 14:09:36 +00:00
|
|
|
}
|
2018-08-20 17:40:06 +00:00
|
|
|
$length = mb_strlen( $this->mQuery[0] );
|
|
|
|
} else {
|
|
|
|
$regex = '/' . $this->mQuery[0] . '/u';
|
|
|
|
if ( $this->mQuery[1] === 'IRLIKE' ) {
|
|
|
|
$regex .= 'i';
|
2018-02-17 14:09:36 +00:00
|
|
|
}
|
2018-08-20 17:40:06 +00:00
|
|
|
|
|
|
|
$matches = [];
|
2018-04-10 16:10:06 +00:00
|
|
|
Wikimedia\suppressWarnings();
|
2018-02-17 14:09:36 +00:00
|
|
|
$check = preg_match(
|
2018-08-20 17:40:06 +00:00
|
|
|
$regex,
|
2018-02-17 14:09:36 +00:00
|
|
|
$row->af_pattern,
|
2018-08-20 17:40:06 +00:00
|
|
|
$matches
|
2018-02-17 14:09:36 +00:00
|
|
|
);
|
2018-04-10 16:10:06 +00:00
|
|
|
Wikimedia\restoreWarnings();
|
2018-02-17 14:09:36 +00:00
|
|
|
// This may happen in case of catastrophic backtracking
|
|
|
|
if ( $check === false ) {
|
2018-08-20 17:40:06 +00:00
|
|
|
return htmlspecialchars( mb_substr( $row->af_pattern, 0, 50 ) );
|
2018-02-17 14:09:36 +00:00
|
|
|
}
|
2018-08-20 17:40:06 +00:00
|
|
|
|
|
|
|
$length = mb_strlen( $matches[0] );
|
|
|
|
$position = mb_strpos( $row->af_pattern, $matches[0] );
|
2018-02-17 14:09:36 +00:00
|
|
|
}
|
2018-08-20 17:40:06 +00:00
|
|
|
|
2018-02-17 14:09:36 +00:00
|
|
|
$remaining = 50 - $length;
|
|
|
|
if ( $remaining <= 0 ) {
|
2018-08-20 17:40:06 +00:00
|
|
|
// Truncate the filter pattern and only show the first 50 characters of the match
|
2018-02-17 14:09:36 +00:00
|
|
|
$pattern = '<b>' .
|
2018-08-20 17:40:06 +00:00
|
|
|
htmlspecialchars( mb_substr( $row->af_pattern, $position, 50 ) ) .
|
2018-02-17 14:09:36 +00:00
|
|
|
'</b>';
|
|
|
|
} else {
|
2018-08-20 17:40:06 +00:00
|
|
|
// Center the snippet on the matched string
|
2018-02-17 14:09:36 +00:00
|
|
|
$minoffset = max( $position - round( $remaining / 2 ), 0 );
|
2018-08-20 17:40:06 +00:00
|
|
|
$pattern = mb_substr( $row->af_pattern, $minoffset, 50 );
|
2018-02-17 14:09:36 +00:00
|
|
|
$pattern =
|
2018-08-20 17:40:06 +00:00
|
|
|
htmlspecialchars( mb_substr( $pattern, 0, $position - $minoffset ) ) .
|
2018-02-17 14:09:36 +00:00
|
|
|
'<b>' .
|
2018-08-20 17:40:06 +00:00
|
|
|
htmlspecialchars( mb_substr( $pattern, $position - $minoffset, $length ) ) .
|
2018-02-17 14:09:36 +00:00
|
|
|
'</b>' .
|
|
|
|
htmlspecialchars( mb_substr(
|
|
|
|
$pattern,
|
|
|
|
$position - $minoffset + $length,
|
2018-08-20 17:40:06 +00:00
|
|
|
$remaining - ( $position - $minoffset + $length )
|
2018-02-17 14:09:36 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $pattern;
|
2017-12-16 00:26:25 +00:00
|
|
|
case 'af_public_comments':
|
|
|
|
return $this->linkRenderer->makeLink(
|
|
|
|
SpecialPage::getTitleFor( 'AbuseFilter', intval( $row->af_id ) ),
|
|
|
|
$value
|
|
|
|
);
|
|
|
|
case 'af_actions':
|
|
|
|
$actions = explode( ',', $value );
|
|
|
|
$displayActions = [];
|
|
|
|
foreach ( $actions as $action ) {
|
|
|
|
$displayActions[] = AbuseFilter::getActionDisplay( $action );
|
|
|
|
}
|
2018-07-04 15:04:05 +00:00
|
|
|
return $lang->commaList( $displayActions );
|
2017-12-16 00:26:25 +00:00
|
|
|
case 'af_enabled':
|
|
|
|
$statuses = [];
|
|
|
|
if ( $row->af_deleted ) {
|
|
|
|
$statuses[] = $this->msg( 'abusefilter-deleted' )->parse();
|
|
|
|
} elseif ( $row->af_enabled ) {
|
|
|
|
$statuses[] = $this->msg( 'abusefilter-enabled' )->parse();
|
2018-05-03 18:32:03 +00:00
|
|
|
if ( $row->af_throttled ) {
|
|
|
|
$statuses[] = $this->msg( 'abusefilter-throttled' )->parse();
|
|
|
|
}
|
2017-12-16 00:26:25 +00:00
|
|
|
} else {
|
|
|
|
$statuses[] = $this->msg( 'abusefilter-disabled' )->parse();
|
|
|
|
}
|
|
|
|
|
2018-03-10 21:47:33 +00:00
|
|
|
if ( $row->af_global && $this->getConfig()->get( 'AbuseFilterIsCentral' ) ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
$statuses[] = $this->msg( 'abusefilter-status-global' )->parse();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $lang->commaList( $statuses );
|
|
|
|
case 'af_hidden':
|
|
|
|
$msg = $value ? 'abusefilter-hidden' : 'abusefilter-unhidden';
|
|
|
|
return $this->msg( $msg )->parse();
|
|
|
|
case 'af_hit_count':
|
2019-03-28 19:59:53 +00:00
|
|
|
// Global here is used to determine whether the log entry is for an external, global
|
|
|
|
// filter, but all filters shown on Special:AbuseFilter are local.
|
|
|
|
$global = false;
|
|
|
|
if ( SpecialAbuseLog::canSeeDetails( $row->af_id, $global, $row->af_hidden ) ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
$count_display = $this->msg( 'abusefilter-hitcount' )
|
2018-07-04 15:04:05 +00:00
|
|
|
->numParams( $value )->text();
|
2017-12-16 00:26:25 +00:00
|
|
|
$link = $this->linkRenderer->makeKnownLink(
|
|
|
|
SpecialPage::getTitleFor( 'AbuseLog' ),
|
|
|
|
$count_display,
|
|
|
|
[],
|
|
|
|
[ 'wpSearchFilter' => $row->af_id ]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$link = "";
|
|
|
|
}
|
|
|
|
return $link;
|
|
|
|
case 'af_timestamp':
|
|
|
|
$userLink =
|
|
|
|
Linker::userLink(
|
|
|
|
$row->af_user,
|
|
|
|
$row->af_user_text
|
|
|
|
) .
|
|
|
|
Linker::userToolLinks(
|
|
|
|
$row->af_user,
|
|
|
|
$row->af_user_text
|
|
|
|
);
|
2016-01-21 09:57:56 +00:00
|
|
|
|
2017-12-16 00:26:25 +00:00
|
|
|
return $this->msg( 'abusefilter-edit-lastmod-text' )
|
2016-01-21 09:57:56 +00:00
|
|
|
->rawParams(
|
|
|
|
$this->mPage->getLinkToLatestDiff(
|
|
|
|
$row->af_id,
|
|
|
|
$lang->timeanddate( $value, true )
|
|
|
|
),
|
|
|
|
$userLink,
|
|
|
|
$this->mPage->getLinkToLatestDiff(
|
|
|
|
$row->af_id,
|
|
|
|
$lang->date( $value, true )
|
|
|
|
),
|
|
|
|
$this->mPage->getLinkToLatestDiff(
|
|
|
|
$row->af_id,
|
|
|
|
$lang->time( $value, true )
|
|
|
|
)
|
2018-07-04 15:04:05 +00:00
|
|
|
)->params(
|
2016-01-21 09:57:56 +00:00
|
|
|
wfEscapeWikiText( $row->af_user_text )
|
2017-12-16 00:26:25 +00:00
|
|
|
)->parse();
|
|
|
|
case 'af_group':
|
|
|
|
return AbuseFilter::nameGroup( $value );
|
|
|
|
default:
|
|
|
|
throw new MWException( "Unknown row type $name!" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getDefaultSort() {
|
2017-12-16 00:26:25 +00:00
|
|
|
return 'af_id';
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getTableClass() {
|
2019-03-22 14:52:21 +00:00
|
|
|
return parent::getTableClass() . ' mw-abusefilter-list-scrollable';
|
2018-02-17 14:09:36 +00:00
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @see TablePager::getRowClass()
|
|
|
|
* @param stdClass $row
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getRowClass( $row ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
if ( $row->af_enabled ) {
|
2018-05-03 18:32:03 +00:00
|
|
|
return $row->af_throttled ? 'mw-abusefilter-list-throttled' : 'mw-abusefilter-list-enabled';
|
2017-12-16 00:26:25 +00:00
|
|
|
} elseif ( $row->af_deleted ) {
|
|
|
|
return 'mw-abusefilter-list-deleted';
|
|
|
|
} else {
|
|
|
|
return 'mw-abusefilter-list-disabled';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-04 21:14:25 +00:00
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isFieldSortable( $name ) {
|
2017-12-16 00:26:25 +00:00
|
|
|
$sortable_fields = [
|
|
|
|
'af_id',
|
|
|
|
'af_enabled',
|
|
|
|
'af_timestamp',
|
|
|
|
'af_hidden',
|
|
|
|
'af_group',
|
|
|
|
];
|
|
|
|
if ( $this->mPage->getUser()->isAllowed( 'abusefilter-log-detail' ) ) {
|
|
|
|
$sortable_fields[] = 'af_hit_count';
|
2019-03-03 18:07:38 +00:00
|
|
|
$sortable_fields[] = 'af_public_comments';
|
2017-12-16 00:26:25 +00:00
|
|
|
}
|
|
|
|
return in_array( $name, $sortable_fields );
|
|
|
|
}
|
|
|
|
}
|