Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
51 / 51 |
|
100.00% |
9 / 9 |
CRAP | |
100.00% |
1 / 1 |
ChangeTagger | |
100.00% |
51 / 51 |
|
100.00% |
9 / 9 |
15 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
clearBuffer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addConditionsLimitTag | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addTags | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
bufferTagsToSetByAction | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getTagsForID | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getTagsForRecentChange | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getIDFromRecentChange | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
4 | |||
getActionID | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter\ChangeTags; |
4 | |
5 | use MediaWiki\Extension\AbuseFilter\ActionSpecifier; |
6 | use MediaWiki\Title\TitleValue; |
7 | use MediaWiki\User\UserIdentityValue; |
8 | use RecentChange; |
9 | |
10 | /** |
11 | * Class that collects change tags to be later applied |
12 | * @internal This interface should be improved and is not ready for external use |
13 | */ |
14 | class ChangeTagger { |
15 | public const SERVICE_NAME = 'AbuseFilterChangeTagger'; |
16 | |
17 | /** @var array (Persistent) map of (action ID => string[]) */ |
18 | private static $tagsToSet = []; |
19 | |
20 | /** |
21 | * @var ChangeTagsManager |
22 | */ |
23 | private $changeTagsManager; |
24 | |
25 | /** |
26 | * @param ChangeTagsManager $changeTagsManager |
27 | */ |
28 | public function __construct( ChangeTagsManager $changeTagsManager ) { |
29 | $this->changeTagsManager = $changeTagsManager; |
30 | } |
31 | |
32 | /** |
33 | * Clear any buffered tag |
34 | */ |
35 | public function clearBuffer(): void { |
36 | self::$tagsToSet = []; |
37 | } |
38 | |
39 | /** |
40 | * @param ActionSpecifier $specifier |
41 | */ |
42 | public function addConditionsLimitTag( ActionSpecifier $specifier ): void { |
43 | $this->addTags( $specifier, [ $this->changeTagsManager->getCondsLimitTag() ] ); |
44 | } |
45 | |
46 | /** |
47 | * @param ActionSpecifier $specifier |
48 | * @param array $tags |
49 | */ |
50 | public function addTags( ActionSpecifier $specifier, array $tags ): void { |
51 | $id = $this->getActionID( $specifier ); |
52 | $this->bufferTagsToSetByAction( [ $id => $tags ] ); |
53 | } |
54 | |
55 | /** |
56 | * @param string[][] $tagsByAction Map of (string => string[]) |
57 | */ |
58 | private function bufferTagsToSetByAction( array $tagsByAction ): void { |
59 | foreach ( $tagsByAction as $actionID => $tags ) { |
60 | self::$tagsToSet[ $actionID ] = array_unique( |
61 | array_merge( self::$tagsToSet[ $actionID ] ?? [], $tags ) |
62 | ); |
63 | } |
64 | } |
65 | |
66 | /** |
67 | * @param string $id |
68 | * @param bool $clear |
69 | * @return array |
70 | */ |
71 | private function getTagsForID( string $id, bool $clear = true ): array { |
72 | $val = self::$tagsToSet[$id] ?? []; |
73 | if ( $clear ) { |
74 | unset( self::$tagsToSet[$id] ); |
75 | } |
76 | return $val; |
77 | } |
78 | |
79 | /** |
80 | * @param RecentChange $recentChange |
81 | * @param bool $clear |
82 | * @return array |
83 | */ |
84 | public function getTagsForRecentChange( RecentChange $recentChange, bool $clear = true ): array { |
85 | $id = $this->getIDFromRecentChange( $recentChange ); |
86 | return $this->getTagsForID( $id, $clear ); |
87 | } |
88 | |
89 | /** |
90 | * @param RecentChange $recentChange |
91 | * @return string |
92 | */ |
93 | private function getIDFromRecentChange( RecentChange $recentChange ): string { |
94 | $title = new TitleValue( |
95 | $recentChange->getAttribute( 'rc_namespace' ), |
96 | $recentChange->getAttribute( 'rc_title' ) |
97 | ); |
98 | |
99 | $logType = $recentChange->getAttribute( 'rc_log_type' ) ?: 'edit'; |
100 | if ( $logType === 'newusers' ) { |
101 | $action = $recentChange->getAttribute( 'rc_log_action' ) === 'autocreate' ? |
102 | 'autocreateaccount' : |
103 | 'createaccount'; |
104 | } else { |
105 | $action = $logType; |
106 | } |
107 | $user = new UserIdentityValue( |
108 | $recentChange->getAttribute( 'rc_user' ), |
109 | $recentChange->getAttribute( 'rc_user_text' ) |
110 | ); |
111 | $specifier = new ActionSpecifier( |
112 | $action, |
113 | $title, |
114 | $user, |
115 | $recentChange->getAttribute( 'rc_ip' ) ?? '', |
116 | $user->getName() |
117 | ); |
118 | return $this->getActionID( $specifier ); |
119 | } |
120 | |
121 | /** |
122 | * Get a unique identifier for the given action |
123 | * |
124 | * @param ActionSpecifier $specifier |
125 | * @return string |
126 | */ |
127 | private function getActionID( ActionSpecifier $specifier ): string { |
128 | $username = $specifier->getUser()->getName(); |
129 | $title = $specifier->getTitle(); |
130 | if ( strpos( $specifier->getAction(), 'createaccount' ) !== false ) { |
131 | // TODO Move this to ActionSpecifier? |
132 | $username = $specifier->getAccountName(); |
133 | '@phan-var string $username'; |
134 | $title = new TitleValue( NS_USER, $username ); |
135 | } |
136 | |
137 | // Use a character that's not allowed in titles and usernames |
138 | $glue = '|'; |
139 | return implode( |
140 | $glue, |
141 | [ |
142 | $title->getNamespace() . ':' . $title->getText(), |
143 | $username, |
144 | $specifier->getAction() |
145 | ] |
146 | ); |
147 | } |
148 | } |