Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
70 / 70 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
1 / 1 |
VariablesManager | |
100.00% |
70 / 70 |
|
100.00% |
7 / 7 |
29 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
translateDeprecatedVars | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getVar | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
8 | |||
dumpAllVars | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
9 | |||
computeDBVars | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
3 | |||
exportAllVars | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
exportNonLazyVars | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter\Variables; |
4 | |
5 | use LogicException; |
6 | use MediaWiki\Extension\AbuseFilter\KeywordsManager; |
7 | use MediaWiki\Extension\AbuseFilter\Parser\AFPData; |
8 | |
9 | /** |
10 | * Service that allows manipulating a VariableHolder |
11 | */ |
12 | class VariablesManager { |
13 | public const SERVICE_NAME = 'AbuseFilterVariablesManager'; |
14 | /** |
15 | * Used in self::getVar() to determine what to do if the requested variable is missing. See |
16 | * the docs of that method for an explanation. |
17 | */ |
18 | public const GET_LAX = 0; |
19 | public const GET_STRICT = 1; |
20 | public const GET_BC = 2; |
21 | |
22 | /** @var KeywordsManager */ |
23 | private $keywordsManager; |
24 | /** @var LazyVariableComputer */ |
25 | private $lazyComputer; |
26 | |
27 | /** |
28 | * @param KeywordsManager $keywordsManager |
29 | * @param LazyVariableComputer $lazyComputer |
30 | */ |
31 | public function __construct( |
32 | KeywordsManager $keywordsManager, |
33 | LazyVariableComputer $lazyComputer |
34 | ) { |
35 | $this->keywordsManager = $keywordsManager; |
36 | $this->lazyComputer = $lazyComputer; |
37 | } |
38 | |
39 | /** |
40 | * Checks whether any deprecated variable is stored with the old name, and replaces it with |
41 | * the new name. This should normally only happen when a DB dump is retrieved from the DB. |
42 | * |
43 | * @param VariableHolder $holder |
44 | */ |
45 | public function translateDeprecatedVars( VariableHolder $holder ): void { |
46 | $deprecatedVars = $this->keywordsManager->getDeprecatedVariables(); |
47 | foreach ( $holder->getVars() as $name => $value ) { |
48 | if ( array_key_exists( $name, $deprecatedVars ) ) { |
49 | $holder->setVar( $deprecatedVars[$name], $value ); |
50 | $holder->removeVar( $name ); |
51 | } |
52 | } |
53 | } |
54 | |
55 | /** |
56 | * Get a variable from the current object |
57 | * |
58 | * @param VariableHolder $holder |
59 | * @param string $varName The variable name |
60 | * @param int $mode One of the self::GET_* constants, determines how to behave when the variable is unset: |
61 | * - GET_STRICT -> In the future, this will throw an exception. For now it returns a DUNDEFINED and logs a warning |
62 | * - GET_LAX -> Return a DUNDEFINED AFPData |
63 | * - GET_BC -> Return a DNULL AFPData (this should only be used for BC, see T230256) |
64 | * @return AFPData |
65 | */ |
66 | public function getVar( |
67 | VariableHolder $holder, |
68 | string $varName, |
69 | $mode = self::GET_STRICT |
70 | ): AFPData { |
71 | $varName = strtolower( $varName ); |
72 | if ( $holder->varIsSet( $varName ) ) { |
73 | /** @var $variable LazyLoadedVariable|AFPData */ |
74 | $variable = $holder->getVarThrow( $varName ); |
75 | if ( $variable instanceof LazyLoadedVariable ) { |
76 | $getVarCB = function ( string $varName ) use ( $holder ): AFPData { |
77 | return $this->getVar( $holder, $varName ); |
78 | }; |
79 | $value = $this->lazyComputer->compute( $variable, $holder, $getVarCB ); |
80 | $holder->setVar( $varName, $value ); |
81 | return $value; |
82 | } elseif ( $variable instanceof AFPData ) { |
83 | return $variable; |
84 | } else { |
85 | // @codeCoverageIgnoreStart |
86 | throw new \UnexpectedValueException( |
87 | "Variable $varName has unexpected type " . gettype( $variable ) |
88 | ); |
89 | // @codeCoverageIgnoreEnd |
90 | } |
91 | } |
92 | |
93 | // The variable is not set. |
94 | switch ( $mode ) { |
95 | case self::GET_STRICT: |
96 | throw new UnsetVariableException( $varName ); |
97 | case self::GET_LAX: |
98 | return new AFPData( AFPData::DUNDEFINED ); |
99 | case self::GET_BC: |
100 | // Old behaviour, which can sometimes lead to unexpected results (e.g. |
101 | // `edit_delta < -5000` will match any non-edit action). |
102 | return new AFPData( AFPData::DNULL ); |
103 | default: |
104 | // @codeCoverageIgnoreStart |
105 | throw new LogicException( "Mode '$mode' not recognized." ); |
106 | // @codeCoverageIgnoreEnd |
107 | } |
108 | } |
109 | |
110 | /** |
111 | * Dump all variables stored in the holder in their native types. |
112 | * If you want a not yet set variable to be included in the results you can |
113 | * either set $compute to an array with the name of the variable or set |
114 | * $compute to true to compute all not yet set variables. |
115 | * |
116 | * @param VariableHolder $holder |
117 | * @param array|bool $compute Variables we should compute if not yet set |
118 | * @param bool $includeUserVars Include user set variables |
119 | * @return array |
120 | */ |
121 | public function dumpAllVars( |
122 | VariableHolder $holder, |
123 | $compute = [], |
124 | bool $includeUserVars = false |
125 | ): array { |
126 | $coreVariables = []; |
127 | |
128 | if ( !$includeUserVars ) { |
129 | // Compile a list of all variables set by the extension to be able |
130 | // to filter user set ones by name |
131 | $activeVariables = array_keys( $this->keywordsManager->getVarsMappings() ); |
132 | $deprecatedVariables = array_keys( $this->keywordsManager->getDeprecatedVariables() ); |
133 | $disabledVariables = array_keys( $this->keywordsManager->getDisabledVariables() ); |
134 | $coreVariables = array_merge( $activeVariables, $deprecatedVariables, $disabledVariables ); |
135 | $coreVariables = array_map( 'strtolower', $coreVariables ); |
136 | } |
137 | |
138 | $exported = []; |
139 | foreach ( array_keys( $holder->getVars() ) as $varName ) { |
140 | $computeThis = ( is_array( $compute ) && in_array( $varName, $compute ) ) || $compute === true; |
141 | if ( |
142 | ( $includeUserVars || in_array( strtolower( $varName ), $coreVariables ) ) && |
143 | // Only include variables set in the extension in case $includeUserVars is false |
144 | ( $computeThis || $holder->getVarThrow( $varName ) instanceof AFPData ) |
145 | ) { |
146 | $exported[$varName] = $this->getVar( $holder, $varName )->toNative(); |
147 | } |
148 | } |
149 | |
150 | return $exported; |
151 | } |
152 | |
153 | /** |
154 | * Compute all vars which need DB access. Useful for vars which are going to be saved |
155 | * cross-wiki or used for offline analysis. |
156 | * |
157 | * @param VariableHolder $holder |
158 | */ |
159 | public function computeDBVars( VariableHolder $holder ): void { |
160 | static $dbTypes = [ |
161 | 'links-from-database', |
162 | 'links-from-update', |
163 | 'links-from-wikitext-or-database', |
164 | 'load-recent-authors', |
165 | 'page-age', |
166 | 'get-page-restrictions', |
167 | 'user-editcount', |
168 | 'user-emailconfirm', |
169 | 'user-groups', |
170 | 'user-rights', |
171 | 'user-age', |
172 | 'user-block', |
173 | 'revision-text-by-id', |
174 | 'content-model-by-id', |
175 | ]; |
176 | |
177 | /** @var LazyLoadedVariable[] $missingVars */ |
178 | $missingVars = array_filter( $holder->getVars(), static function ( $el ) { |
179 | return ( $el instanceof LazyLoadedVariable ); |
180 | } ); |
181 | foreach ( $missingVars as $name => $var ) { |
182 | if ( in_array( $var->getMethod(), $dbTypes ) ) { |
183 | $holder->setVar( $name, $this->getVar( $holder, $name ) ); |
184 | } |
185 | } |
186 | } |
187 | |
188 | /** |
189 | * Export all variables stored in this object with their native (PHP) types. |
190 | * |
191 | * @param VariableHolder $holder |
192 | * @return array |
193 | */ |
194 | public function exportAllVars( VariableHolder $holder ): array { |
195 | $exported = []; |
196 | foreach ( array_keys( $holder->getVars() ) as $varName ) { |
197 | $exported[ $varName ] = $this->getVar( $holder, $varName )->toNative(); |
198 | } |
199 | |
200 | return $exported; |
201 | } |
202 | |
203 | /** |
204 | * Export all non-lazy variables stored in this object as string |
205 | * |
206 | * @param VariableHolder $holder |
207 | * @return string[] |
208 | */ |
209 | public function exportNonLazyVars( VariableHolder $holder ): array { |
210 | $exported = []; |
211 | foreach ( $holder->getVars() as $varName => $data ) { |
212 | if ( !( $data instanceof LazyLoadedVariable ) ) { |
213 | $exported[$varName] = $holder->getComputedVariable( $varName )->toString(); |
214 | } |
215 | } |
216 | |
217 | return $exported; |
218 | } |
219 | } |