Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5957447
AbuseFilterPager.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
AbuseFilterPager.php
View Options
<?php
namespace
MediaWiki\Extension\AbuseFilter\Pager
;
use
LogicException
;
use
MediaWiki\Cache\LinkBatchFactory
;
use
MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager
;
use
MediaWiki\Extension\AbuseFilter\FilterUtils
;
use
MediaWiki\Extension\AbuseFilter\SpecsFormatter
;
use
MediaWiki\Extension\AbuseFilter\View\AbuseFilterViewList
;
use
MediaWiki\Linker\Linker
;
use
MediaWiki\Linker\LinkRenderer
;
use
MediaWiki\Pager\TablePager
;
use
MediaWiki\SpecialPage\SpecialPage
;
use
stdClass
;
use
UnexpectedValueException
;
use
Wikimedia\Rdbms\FakeResultWrapper
;
use
Wikimedia\Rdbms\IResultWrapper
;
/**
* Class to build paginated filter list
*/
class
AbuseFilterPager
extends
TablePager
{
/**
* The unique sort fields for the sort options for unique paginate
*/
private
const
INDEX_FIELDS
=
[
'af_id'
=>
[
'af_id'
],
'af_enabled'
=>
[
'af_enabled'
,
'af_deleted'
,
'af_id'
],
'af_timestamp'
=>
[
'af_timestamp'
,
'af_id'
],
'af_hidden'
=>
[
'af_hidden'
,
'af_id'
],
'af_group'
=>
[
'af_group'
,
'af_id'
],
'af_hit_count'
=>
[
'af_hit_count'
,
'af_id'
],
'af_public_comments'
=>
[
'af_public_comments'
,
'af_id'
],
];
/** @var ?LinkBatchFactory */
private
$linkBatchFactory
;
/** @var AbuseFilterPermissionManager */
private
$afPermManager
;
/** @var SpecsFormatter */
protected
$specsFormatter
;
/**
* @var AbuseFilterViewList The associated page
*/
private
$mPage
;
/**
* @var array Query WHERE conditions
*/
private
$conds
;
/**
* @var string|null The pattern being searched
*/
private
$searchPattern
;
/**
* @var string|null The pattern search mode (LIKE, RLIKE or IRLIKE)
*/
private
$searchMode
;
/**
* @param AbuseFilterViewList $page
* @param LinkRenderer $linkRenderer
* @param ?LinkBatchFactory $linkBatchFactory
* @param AbuseFilterPermissionManager $afPermManager
* @param SpecsFormatter $specsFormatter
* @param array $conds
* @param ?string $searchPattern Null if no pattern was specified
* @param ?string $searchMode
*/
public
function
__construct
(
AbuseFilterViewList
$page
,
LinkRenderer
$linkRenderer
,
?
LinkBatchFactory
$linkBatchFactory
,
AbuseFilterPermissionManager
$afPermManager
,
SpecsFormatter
$specsFormatter
,
array
$conds
,
?
string
$searchPattern
,
?
string
$searchMode
)
{
// needed by parent's constructor call
$this
->
afPermManager
=
$afPermManager
;
$this
->
specsFormatter
=
$specsFormatter
;
parent
::
__construct
(
$page
->
getContext
(),
$linkRenderer
);
$this
->
mPage
=
$page
;
$this
->
linkBatchFactory
=
$linkBatchFactory
;
$this
->
conds
=
$conds
;
$this
->
searchPattern
=
$searchPattern
;
$this
->
searchMode
=
$searchMode
;
}
/**
* @return array
*/
public
function
getQueryInfo
()
{
return
[
'tables'
=>
[
'abuse_filter'
,
'actor'
],
'fields'
=>
[
// All columns but af_comments
'af_id'
,
'af_enabled'
,
'af_deleted'
,
'af_pattern'
,
'af_global'
,
'af_public_comments'
,
'af_user'
=>
'actor_user'
,
'af_user_text'
=>
'actor_name'
,
'af_hidden'
,
'af_hit_count'
,
'af_timestamp'
,
'af_actions'
,
'af_group'
,
'af_throttled'
],
'conds'
=>
$this
->
conds
,
'join_conds'
=>
[
'actor'
=>
[
'JOIN'
,
'actor_id = af_actor'
],
]
];
}
/**
* @param IResultWrapper $result
*/
protected
function
preprocessResults
(
$result
)
{
// LinkBatchFactory only provided and needed for local wiki results
if
(
$this
->
linkBatchFactory
===
null
||
$this
->
getNumRows
()
===
0
)
{
return
;
}
$lb
=
$this
->
linkBatchFactory
->
newLinkBatch
();
$lb
->
setCaller
(
__METHOD__
);
foreach
(
$result
as
$row
)
{
$lb
->
add
(
NS_USER
,
$row
->
af_user_text
);
$lb
->
add
(
NS_USER_TALK
,
$row
->
af_user_text
);
}
$lb
->
execute
();
$result
->
seek
(
0
);
}
/**
* @inheritDoc
* This is the same as the parent implementation if no search pattern was specified.
* Otherwise, it does a query with no limit and then slices the results à la ContribsPager.
*/
public
function
reallyDoQuery
(
$offset
,
$limit
,
$order
)
{
if
(
$this
->
searchMode
===
null
)
{
return
parent
::
reallyDoQuery
(
$offset
,
$limit
,
$order
);
}
[
$tables
,
$fields
,
$conds
,
$fname
,
$options
,
$join_conds
]
=
$this
->
buildQueryInfo
(
$offset
,
$limit
,
$order
);
unset
(
$options
[
'LIMIT'
]
);
$res
=
$this
->
mDb
->
newSelectQueryBuilder
()
->
tables
(
$tables
)
->
fields
(
$fields
)
->
conds
(
$conds
)
->
caller
(
$fname
)
->
options
(
$options
)
->
joinConds
(
$join_conds
)
->
fetchResultSet
();
$filtered
=
[];
foreach
(
$res
as
$row
)
{
if
(
$this
->
matchesPattern
(
$row
->
af_pattern
)
)
{
$filtered
[
$row
->
af_id
]
=
$row
;
}
}
// sort results and enforce limit like ContribsPager
if
(
$order
===
self
::
QUERY_ASCENDING
)
{
ksort
(
$filtered
);
}
else
{
krsort
(
$filtered
);
}
$filtered
=
array_slice
(
$filtered
,
0
,
$limit
);
$filtered
=
array_values
(
$filtered
);
return
new
FakeResultWrapper
(
$filtered
);
}
/**
* Check whether $subject matches the given $pattern.
*
* @param string $subject
* @return bool
* @throws LogicException
*/
private
function
matchesPattern
(
$subject
)
{
$pattern
=
$this
->
searchPattern
;
switch
(
$this
->
searchMode
)
{
case
'RLIKE'
:
return
(
bool
)
preg_match
(
"/$pattern/u"
,
$subject
);
case
'IRLIKE'
:
return
(
bool
)
preg_match
(
"/$pattern/ui"
,
$subject
);
case
'LIKE'
:
return
mb_stripos
(
$subject
,
$pattern
)
!==
false
;
default
:
throw
new
LogicException
(
"Unknown search type {$this->searchMode}"
);
}
}
/**
* Note: this method is called by parent::__construct
* @return array
* @see MediaWiki\Pager\Pager::getFieldNames()
*/
public
function
getFieldNames
()
{
$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'
,
];
$performer
=
$this
->
getAuthority
();
if
(
$this
->
afPermManager
->
canSeeLogDetails
(
$performer
)
)
{
$headers
[
'af_hit_count'
]
=
'abusefilter-list-hitcount'
;
}
if
(
$this
->
afPermManager
->
canViewPrivateFilters
(
$performer
)
&&
$this
->
searchMode
!==
null
)
{
// This is also excluded in the default view
$headers
[
'af_pattern'
]
=
'abusefilter-list-pattern'
;
}
if
(
count
(
$this
->
getConfig
()->
get
(
'AbuseFilterValidGroups'
)
)
>
1
)
{
$headers
[
'af_group'
]
=
'abusefilter-list-group'
;
}
foreach
(
$headers
as
&
$msg
)
{
$msg
=
$this
->
msg
(
$msg
)->
text
();
}
return
$headers
;
}
/**
* @param string $name
* @param string|null $value
* @return string
*/
public
function
formatValue
(
$name
,
$value
)
{
$lang
=
$this
->
getLanguage
();
$user
=
$this
->
getUser
();
$linkRenderer
=
$this
->
getLinkRenderer
();
$row
=
$this
->
mCurrentRow
;
switch
(
$name
)
{
case
'af_id'
:
return
$linkRenderer
->
makeLink
(
SpecialPage
::
getTitleFor
(
'AbuseFilter'
,
$value
),
$lang
->
formatNum
(
intval
(
$value
)
)
);
case
'af_pattern'
:
return
$this
->
getHighlightedPattern
(
$row
);
case
'af_public_comments'
:
return
$linkRenderer
->
makeLink
(
SpecialPage
::
getTitleFor
(
'AbuseFilter'
,
$row
->
af_id
),
$value
);
case
'af_actions'
:
$actions
=
explode
(
','
,
$value
);
$displayActions
=
[];
foreach
(
$actions
as
$action
)
{
$displayActions
[]
=
$this
->
specsFormatter
->
getActionDisplay
(
$action
);
}
return
$lang
->
commaList
(
$displayActions
);
case
'af_enabled'
:
$statuses
=
[];
if
(
$row
->
af_deleted
)
{
$statuses
[]
=
$this
->
msg
(
'abusefilter-deleted'
)->
parse
();
}
elseif
(
$row
->
af_enabled
)
{
$statuses
[]
=
$this
->
msg
(
'abusefilter-enabled'
)->
parse
();
if
(
$row
->
af_throttled
)
{
$statuses
[]
=
$this
->
msg
(
'abusefilter-throttled'
)->
parse
();
}
}
else
{
$statuses
[]
=
$this
->
msg
(
'abusefilter-disabled'
)->
parse
();
}
if
(
$row
->
af_global
&&
$this
->
getConfig
()->
get
(
'AbuseFilterIsCentral'
)
)
{
$statuses
[]
=
$this
->
msg
(
'abusefilter-status-global'
)->
parse
();
}
return
$lang
->
commaList
(
$statuses
);
case
'af_hidden'
:
$flagMsgs
=
[];
if
(
FilterUtils
::
isHidden
(
(
int
)
$value
)
)
{
$flagMsgs
[]
=
$this
->
msg
(
'abusefilter-hidden'
)->
parse
();
}
if
(
FilterUtils
::
isProtected
(
(
int
)
$value
)
)
{
$flagMsgs
[]
=
$this
->
msg
(
'abusefilter-protected'
)->
parse
();
}
if
(
!
$flagMsgs
)
{
return
$this
->
msg
(
'abusefilter-unhidden'
)->
parse
();
}
return
$lang
->
commaList
(
$flagMsgs
);
case
'af_hit_count'
:
if
(
$this
->
afPermManager
->
canSeeLogDetailsForFilter
(
$user
,
$row
->
af_hidden
)
)
{
$count_display
=
$this
->
msg
(
'abusefilter-hitcount'
)
->
numParams
(
$value
)->
text
();
$link
=
$linkRenderer
->
makeKnownLink
(
SpecialPage
::
getTitleFor
(
'AbuseLog'
),
$count_display
,
[],
[
'wpSearchFilter'
=>
$row
->
af_id
]
);
}
else
{
$link
=
""
;
}
return
$link
;
case
'af_timestamp'
:
$userLink
=
Linker
::
userLink
(
$row
->
af_user
??
0
,
$row
->
af_user_text
)
.
Linker
::
userToolLinks
(
$row
->
af_user
??
0
,
$row
->
af_user_text
);
return
$this
->
msg
(
'abusefilter-edit-lastmod-text'
)
->
rawParams
(
$this
->
mPage
->
getLinkToLatestDiff
(
$row
->
af_id
,
$lang
->
userTimeAndDate
(
$value
,
$user
)
),
$userLink
,
$this
->
mPage
->
getLinkToLatestDiff
(
$row
->
af_id
,
$lang
->
userDate
(
$value
,
$user
)
),
$this
->
mPage
->
getLinkToLatestDiff
(
$row
->
af_id
,
$lang
->
userTime
(
$value
,
$user
)
)
)->
params
(
wfEscapeWikiText
(
$row
->
af_user_text
)
)->
parse
();
case
'af_group'
:
return
$this
->
specsFormatter
->
nameGroup
(
$value
);
default
:
throw
new
UnexpectedValueException
(
"Unknown row type $name!"
);
}
}
/**
* Get the filter pattern with <b> elements surrounding the searched pattern
*
* @param stdClass $row
* @return string
*/
private
function
getHighlightedPattern
(
stdClass
$row
)
{
if
(
$this
->
searchMode
===
null
)
{
throw
new
LogicException
(
'Cannot search without a mode.'
);
}
$maxLen
=
50
;
if
(
$this
->
searchMode
===
'LIKE'
)
{
$position
=
mb_stripos
(
$row
->
af_pattern
,
$this
->
searchPattern
);
$length
=
mb_strlen
(
$this
->
searchPattern
);
}
else
{
$regex
=
'/'
.
$this
->
searchPattern
.
'/u'
;
if
(
$this
->
searchMode
===
'IRLIKE'
)
{
$regex
.=
'i'
;
}
$matches
=
[];
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$check
=
@
preg_match
(
$regex
,
$row
->
af_pattern
,
$matches
);
// This may happen in case of catastrophic backtracking, or regexps matching
// the empty string.
if
(
$check
===
false
||
strlen
(
$matches
[
0
]
)
===
0
)
{
return
htmlspecialchars
(
mb_substr
(
$row
->
af_pattern
,
0
,
50
)
);
}
$length
=
mb_strlen
(
$matches
[
0
]
);
$position
=
mb_strpos
(
$row
->
af_pattern
,
$matches
[
0
]
);
}
$remaining
=
$maxLen
-
$length
;
if
(
$remaining
<=
0
)
{
$pattern
=
'<b>'
.
htmlspecialchars
(
mb_substr
(
$row
->
af_pattern
,
$position
,
$maxLen
)
)
.
'</b>'
;
}
else
{
// Center the snippet on the matched string
$minoffset
=
max
(
$position
-
round
(
$remaining
/
2
),
0
);
$pattern
=
mb_substr
(
$row
->
af_pattern
,
$minoffset
,
$maxLen
);
$pattern
=
htmlspecialchars
(
mb_substr
(
$pattern
,
0
,
$position
-
$minoffset
)
)
.
'<b>'
.
htmlspecialchars
(
mb_substr
(
$pattern
,
$position
-
$minoffset
,
$length
)
)
.
'</b>'
.
htmlspecialchars
(
mb_substr
(
$pattern
,
$position
-
$minoffset
+
$length
,
$remaining
-
(
$position
-
$minoffset
+
$length
)
)
);
}
return
$pattern
;
}
/**
* @codeCoverageIgnore Merely declarative
* @inheritDoc
*/
public
function
getDefaultSort
()
{
return
'af_id'
;
}
/**
* @codeCoverageIgnore Merely declarative
* @inheritDoc
*/
public
function
getTableClass
()
{
return
parent
::
getTableClass
()
.
' mw-abusefilter-list-scrollable'
;
}
/**
* @param stdClass $row
* @return string
* @see TablePager::getRowClass()
*/
public
function
getRowClass
(
$row
)
{
if
(
$row
->
af_enabled
)
{
return
$row
->
af_throttled
?
'mw-abusefilter-list-throttled'
:
'mw-abusefilter-list-enabled'
;
}
elseif
(
$row
->
af_deleted
)
{
return
'mw-abusefilter-list-deleted'
;
}
else
{
return
'mw-abusefilter-list-disabled'
;
}
}
/**
* @inheritDoc
*/
public
function
getIndexField
()
{
return
[
self
::
INDEX_FIELDS
[
$this
->
mSort
]
];
}
/**
* @param string $field
*
* @return bool
*/
public
function
isFieldSortable
(
$field
)
{
if
(
(
$field
===
'af_hit_count'
||
$field
===
'af_public_comments'
)
&&
!
$this
->
afPermManager
->
canSeeLogDetails
(
$this
->
getAuthority
()
)
)
{
return
false
;
}
return
isset
(
self
::
INDEX_FIELDS
[
$field
]
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 05:07 (2 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e8/48/5e29579f16a7cb0d8a72e78d98d8
Default Alt Text
AbuseFilterPager.php (12 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment