Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4153115
FilterStore.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
FilterStore.php
View Options
<?php
namespace
MediaWiki\Extension\AbuseFilter
;
use
ManualLogEntry
;
use
MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagsManager
;
use
MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry
;
use
MediaWiki\Extension\AbuseFilter\Filter\Filter
;
use
MediaWiki\Extension\AbuseFilter\Filter\Flags
;
use
MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseFilter
;
use
MediaWiki\Permissions\Authority
;
use
MediaWiki\Status\Status
;
use
MediaWiki\User\ActorNormalization
;
use
MediaWiki\User\UserIdentity
;
use
Wikimedia\Rdbms\LBFactory
;
/**
* @internal
*/
class
FilterStore
{
public
const
SERVICE_NAME
=
'AbuseFilterFilterStore'
;
/** @var ConsequencesRegistry */
private
$consequencesRegistry
;
/** @var LBFactory */
private
$lbFactory
;
/** @var ActorNormalization */
private
$actorNormalization
;
/** @var FilterProfiler */
private
$filterProfiler
;
/** @var FilterLookup */
private
$filterLookup
;
/** @var ChangeTagsManager */
private
$tagsManager
;
/** @var FilterValidator */
private
$filterValidator
;
/** @var FilterCompare */
private
$filterCompare
;
/** @var EmergencyCache */
private
$emergencyCache
;
/**
* @param ConsequencesRegistry $consequencesRegistry
* @param LBFactory $lbFactory
* @param ActorNormalization $actorNormalization
* @param FilterProfiler $filterProfiler
* @param FilterLookup $filterLookup
* @param ChangeTagsManager $tagsManager
* @param FilterValidator $filterValidator
* @param FilterCompare $filterCompare
* @param EmergencyCache $emergencyCache
*/
public
function
__construct
(
ConsequencesRegistry
$consequencesRegistry
,
LBFactory
$lbFactory
,
ActorNormalization
$actorNormalization
,
FilterProfiler
$filterProfiler
,
FilterLookup
$filterLookup
,
ChangeTagsManager
$tagsManager
,
FilterValidator
$filterValidator
,
FilterCompare
$filterCompare
,
EmergencyCache
$emergencyCache
)
{
$this
->
consequencesRegistry
=
$consequencesRegistry
;
$this
->
lbFactory
=
$lbFactory
;
$this
->
actorNormalization
=
$actorNormalization
;
$this
->
filterProfiler
=
$filterProfiler
;
$this
->
filterLookup
=
$filterLookup
;
$this
->
tagsManager
=
$tagsManager
;
$this
->
filterValidator
=
$filterValidator
;
$this
->
filterCompare
=
$filterCompare
;
$this
->
emergencyCache
=
$emergencyCache
;
}
/**
* Checks whether user input for the filter editing form is valid and if so saves the filter.
* Returns a Status object which can be:
* - Good with [ new_filter_id, history_id ] as value if the filter was successfully saved
* - Good with value = false if everything went fine but the filter is unchanged
* - OK with errors if a validation error occurred
* - Fatal in case of a permission-related error
*
* @param Authority $performer
* @param int|null $filterId
* @param Filter $newFilter
* @param Filter $originalFilter
* @return Status
*/
public
function
saveFilter
(
Authority
$performer
,
?
int
$filterId
,
Filter
$newFilter
,
Filter
$originalFilter
):
Status
{
$validationStatus
=
$this
->
filterValidator
->
checkAll
(
$newFilter
,
$originalFilter
,
$performer
);
if
(
!
$validationStatus
->
isGood
()
)
{
return
$validationStatus
;
}
// Check for non-changes
$differences
=
$this
->
filterCompare
->
compareVersions
(
$newFilter
,
$originalFilter
);
if
(
!
$differences
)
{
return
Status
::
newGood
(
false
);
}
// Everything went fine, so let's save the filter
$wasGlobal
=
$originalFilter
->
isGlobal
();
[
$newID
,
$historyID
]
=
$this
->
doSaveFilter
(
$performer
->
getUser
(),
$newFilter
,
$originalFilter
,
$differences
,
$filterId
,
$wasGlobal
);
return
Status
::
newGood
(
[
$newID
,
$historyID
]
);
}
/**
* Saves new filter's info to DB
*
* @param UserIdentity $userIdentity
* @param Filter $newFilter
* @param Filter $originalFilter
* @param array $differences
* @param int|null $filterId
* @param bool $wasGlobal
* @return int[] first element is new ID, second is history ID
*/
private
function
doSaveFilter
(
UserIdentity
$userIdentity
,
Filter
$newFilter
,
Filter
$originalFilter
,
array
$differences
,
?
int
$filterId
,
bool
$wasGlobal
):
array
{
$dbw
=
$this
->
lbFactory
->
getPrimaryDatabase
();
$newRow
=
$this
->
filterToDatabaseRow
(
$newFilter
,
$originalFilter
);
// Set last modifier.
$newRow
[
'af_timestamp'
]
=
$dbw
->
timestamp
();
$newRow
[
'af_actor'
]
=
$this
->
actorNormalization
->
acquireActorId
(
$userIdentity
,
$dbw
);
$isNew
=
$filterId
===
null
;
// Preserve the old throttled status (if any) only if disabling the filter.
// TODO: It might make more sense to check what was actually changed
$newRow
[
'af_throttled'
]
=
(
$newRow
[
'af_throttled'
]
??
false
)
&&
!
$newRow
[
'af_enabled'
];
// This is null when creating a new filter, but the DB field is NOT NULL
$newRow
[
'af_hit_count'
]
??=
0
;
$rowForInsert
=
array_diff_key
(
$newRow
,
[
'af_id'
=>
true
]
);
$dbw
->
startAtomic
(
__METHOD__
);
if
(
$filterId
===
null
)
{
$dbw
->
newInsertQueryBuilder
()
->
insertInto
(
'abuse_filter'
)
->
row
(
$rowForInsert
)
->
caller
(
__METHOD__
)
->
execute
();
$filterId
=
$dbw
->
insertId
();
}
else
{
$dbw
->
newUpdateQueryBuilder
()
->
update
(
'abuse_filter'
)
->
set
(
$rowForInsert
)
->
where
(
[
'af_id'
=>
$filterId
]
)
->
caller
(
__METHOD__
)
->
execute
();
}
$newRow
[
'af_id'
]
=
$filterId
;
$actions
=
$newFilter
->
getActions
();
$actionsRows
=
[];
foreach
(
$this
->
consequencesRegistry
->
getAllEnabledActionNames
()
as
$action
)
{
if
(
!
isset
(
$actions
[
$action
]
)
)
{
continue
;
}
$parameters
=
$actions
[
$action
];
if
(
$action
===
'throttle'
&&
$parameters
[
0
]
===
null
)
{
// FIXME: Do we really need to keep the filter ID inside throttle parameters?
// We'd save space, keep things simpler and avoid this hack. Note: if removing
// it, a maintenance script will be necessary to clean up the table.
$parameters
[
0
]
=
$filterId
;
}
$actionsRows
[]
=
[
'afa_filter'
=>
$filterId
,
'afa_consequence'
=>
$action
,
'afa_parameters'
=>
implode
(
"
\n
"
,
$parameters
),
];
}
// Create a history row
$afhRow
=
[];
foreach
(
AbuseFilter
::
HISTORY_MAPPINGS
as
$afCol
=>
$afhCol
)
{
// Some fields are expected to be missing during actor migration
if
(
isset
(
$newRow
[
$afCol
]
)
)
{
$afhRow
[
$afhCol
]
=
$newRow
[
$afCol
];
}
}
$afhRow
[
'afh_actions'
]
=
serialize
(
$actions
);
$afhRow
[
'afh_changed_fields'
]
=
implode
(
','
,
$differences
);
$flags
=
[];
if
(
FilterUtils
::
isHidden
(
$newRow
[
'af_hidden'
]
)
)
{
$flags
[]
=
'hidden'
;
}
if
(
FilterUtils
::
isProtected
(
$newRow
[
'af_hidden'
]
)
)
{
$flags
[]
=
'protected'
;
}
if
(
$newRow
[
'af_enabled'
]
)
{
$flags
[]
=
'enabled'
;
}
if
(
$newRow
[
'af_deleted'
]
)
{
$flags
[]
=
'deleted'
;
}
if
(
$newRow
[
'af_global'
]
)
{
$flags
[]
=
'global'
;
}
$afhRow
[
'afh_flags'
]
=
implode
(
','
,
$flags
);
$afhRow
[
'afh_filter'
]
=
$filterId
;
// Do the update
$dbw
->
newInsertQueryBuilder
()
->
insertInto
(
'abuse_filter_history'
)
->
row
(
$afhRow
)
->
caller
(
__METHOD__
)
->
execute
();
$historyID
=
$dbw
->
insertId
();
if
(
!
$isNew
)
{
$dbw
->
newDeleteQueryBuilder
()
->
deleteFrom
(
'abuse_filter_action'
)
->
where
(
[
'afa_filter'
=>
$filterId
]
)
->
caller
(
__METHOD__
)
->
execute
();
}
if
(
$actionsRows
)
{
$dbw
->
newInsertQueryBuilder
()
->
insertInto
(
'abuse_filter_action'
)
->
rows
(
$actionsRows
)
->
caller
(
__METHOD__
)
->
execute
();
}
$dbw
->
endAtomic
(
__METHOD__
);
// Invalidate cache if this was a global rule
if
(
$wasGlobal
||
$newRow
[
'af_global'
]
)
{
$this
->
filterLookup
->
purgeGroupWANCache
(
$newRow
[
'af_group'
]
);
}
// Logging
$logEntry
=
new
ManualLogEntry
(
'abusefilter'
,
$isNew
?
'create'
:
'modify'
);
$logEntry
->
setPerformer
(
$userIdentity
);
$logEntry
->
setTarget
(
SpecialAbuseFilter
::
getTitleForSubpage
(
(
string
)
$filterId
)
);
$logEntry
->
setParameters
(
[
'historyId'
=>
$historyID
,
'newId'
=>
$filterId
]
);
$logid
=
$logEntry
->
insert
(
$dbw
);
$logEntry
->
publish
(
$logid
);
// Purge the tag list cache so the fetchAllTags hook applies tag changes
if
(
isset
(
$actions
[
'tag'
]
)
)
{
$this
->
tagsManager
->
purgeTagCache
();
}
$this
->
filterProfiler
->
resetFilterProfile
(
$filterId
);
if
(
$newRow
[
'af_enabled'
]
)
{
$this
->
emergencyCache
->
setNewForFilter
(
$filterId
,
$newRow
[
'af_group'
]
);
}
return
[
$filterId
,
$historyID
];
}
/**
* @todo Perhaps add validation to ensure no null values remained.
* @note For simplicity, data about the last editor are omitted.
* @param Filter $filter
* @return array
*/
private
function
filterToDatabaseRow
(
Filter
$filter
,
Filter
$originalFilter
):
array
{
// T67807: integer 1's & 0's might be better understood than booleans
// If the filter is already protected, it must remain protected even if
// the current filter doesn't use a protected variable anymore
$privacyLevel
=
$filter
->
getPrivacyLevel
();
if
(
$originalFilter
->
isProtected
()
)
{
$privacyLevel
|=
Flags
::
FILTER_USES_PROTECTED_VARS
;
}
return
[
'af_id'
=>
$filter
->
getID
(),
'af_pattern'
=>
$filter
->
getRules
(),
'af_public_comments'
=>
$filter
->
getName
(),
'af_comments'
=>
$filter
->
getComments
(),
'af_group'
=>
$filter
->
getGroup
(),
'af_actions'
=>
implode
(
','
,
$filter
->
getActionsNames
()
),
'af_enabled'
=>
(
int
)
$filter
->
isEnabled
(),
'af_deleted'
=>
(
int
)
$filter
->
isDeleted
(),
'af_hidden'
=>
$privacyLevel
,
'af_global'
=>
(
int
)
$filter
->
isGlobal
(),
'af_timestamp'
=>
$filter
->
getTimestamp
(),
'af_hit_count'
=>
$filter
->
getHitCount
(),
'af_throttled'
=>
(
int
)
$filter
->
isThrottled
(),
];
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 19 2026, 20:37 (7 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
4c/22/5d7457dde7975ff95eb766416634
Default Alt Text
FilterStore.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment