Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
54.29% |
38 / 70 |
|
28.57% |
4 / 14 |
CRAP | |
0.00% |
0 / 1 |
Helpers | |
54.29% |
38 / 70 |
|
28.57% |
4 / 14 |
137.04 | |
0.00% |
0 / 1 |
hideNonDamagingFilter | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
joinWithOresTables | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
4 | |||
addRowData | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
isModelEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
isDamagingFlagEnabled | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
isHighlightEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isRCPage | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
oresUiEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getThreshold | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
getDamagingLevelPreference | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
isWLPage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDamagingThresholds | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
isRCStructuredUiEnabled | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
isWLStructuredUiEnabled | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software: you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation, either version 3 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License |
14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | */ |
16 | |
17 | namespace ORES\Hooks; |
18 | |
19 | use Exception; |
20 | use IContextSource; |
21 | use InvalidArgumentException; |
22 | use MediaWiki\MediaWikiServices; |
23 | use MediaWiki\Specials\SpecialRecentChanges; |
24 | use MediaWiki\Specials\SpecialWatchlist; |
25 | use MediaWiki\Title\Title; |
26 | use MediaWiki\User\UserIdentity; |
27 | use ORES\Services\ORESServices; |
28 | |
29 | class Helpers { |
30 | |
31 | /** |
32 | * @var string[] The oresDamagingPref preference uses these names for historical reasons |
33 | */ |
34 | public static $damagingPrefMap = [ |
35 | 'hard' => 'maybebad', |
36 | 'soft' => 'likelybad', |
37 | 'softest' => 'verylikelybad', |
38 | ]; |
39 | |
40 | public static function hideNonDamagingFilter( |
41 | array &$fields, array &$conds, $hidenondamaging, UserIdentity $user, Title $title = null |
42 | ) { |
43 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
44 | // Add user-based threshold |
45 | $threshold = self::getThreshold( 'damaging', $user, $title ); |
46 | if ( $threshold === null ) { |
47 | return; |
48 | } |
49 | // FIXME: This is not a "filter" but an undocumented side effect of this function. |
50 | $fields['ores_damaging_threshold'] = $threshold; |
51 | |
52 | if ( $hidenondamaging ) { |
53 | // Filter out non-damaging edits. |
54 | $conds[] = $dbr->expr( 'ores_damaging_cls.oresc_probability', '>', $threshold ); |
55 | } |
56 | } |
57 | |
58 | public static function joinWithOresTables( |
59 | $type, $revIdField, array &$tables, array &$fields, array &$join_conds |
60 | ) { |
61 | if ( !ctype_lower( $type ) || strpos( $type, '_' ) || strpos( $type, '-' ) ) { |
62 | throw new InvalidArgumentException( "Invalid value for parameter 'type': '$type'. " . |
63 | 'Restricted to one lower case word to prevent accidental injection.' ); |
64 | } |
65 | |
66 | $modelId = ORESServices::getModelLookup()->getModelId( $type ); |
67 | $tables["ores_{$type}_cls"] = 'ores_classification'; |
68 | |
69 | $fields["ores_{$type}_score"] = "ores_{$type}_cls.oresc_probability"; |
70 | |
71 | $join_conds["ores_{$type}_cls"] = [ |
72 | 'LEFT JOIN', |
73 | [ |
74 | "ores_{$type}_cls.oresc_model" => $modelId, |
75 | "ores_{$type}_cls.oresc_rev=$revIdField", |
76 | "ores_{$type}_cls.oresc_class" => 1 |
77 | ] |
78 | ]; |
79 | } |
80 | |
81 | /** |
82 | * @param IContextSource $context |
83 | * @param int $revisionId |
84 | * @param float $score |
85 | * @param string $model |
86 | */ |
87 | public static function addRowData( IContextSource $context, $revisionId, $score, $model ) { |
88 | $out = $context->getOutput(); |
89 | $data = $out->getProperty( 'oresData' ); |
90 | if ( !isset( $data[$revisionId] ) ) { |
91 | $data[$revisionId] = []; |
92 | } |
93 | $data[$revisionId][$model] = $score; |
94 | $out->setProperty( 'oresData', $data ); |
95 | } |
96 | |
97 | /** |
98 | * Check whether a given model is enabled in the config |
99 | * @param string $model |
100 | * @return bool |
101 | */ |
102 | public static function isModelEnabled( $model ) { |
103 | global $wgOresModels; |
104 | |
105 | return isset( $wgOresModels[$model]['enabled'] ) && $wgOresModels[$model]['enabled']; |
106 | } |
107 | |
108 | /** |
109 | * @param IContextSource $context |
110 | * @return bool Whether the damaging flag ("r") should be shown |
111 | */ |
112 | public static function isDamagingFlagEnabled( IContextSource $context ) { |
113 | $user = $context->getUser(); |
114 | |
115 | if ( !self::oresUiEnabled() ) { |
116 | return false; |
117 | } |
118 | |
119 | $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); |
120 | |
121 | if ( self::isRCPage( $context->getTitle() ) ) { |
122 | return !self::isRCStructuredUiEnabled( $context ) && |
123 | $userOptionsLookup->getBoolOption( $user, 'ores-damaging-flag-rc' ); |
124 | } |
125 | |
126 | if ( self::isWLPage( $context->getTitle() ) ) { |
127 | return !self::isWLStructuredUiEnabled( $context ) && |
128 | $userOptionsLookup->getBoolOption( $user, 'oresHighlight' ); |
129 | } |
130 | |
131 | return $userOptionsLookup->getBoolOption( $user, 'oresHighlight' ); |
132 | } |
133 | |
134 | /** |
135 | * @param IContextSource $context |
136 | * @return bool Whether highlights should be shown |
137 | */ |
138 | public static function isHighlightEnabled( IContextSource $context ) { |
139 | // Was previously controlled by different preferences than the "r", but they're currently |
140 | // the same. |
141 | return self::isDamagingFlagEnabled( $context ); |
142 | } |
143 | |
144 | /** |
145 | * @param Title $title |
146 | * @return bool Whether $title is a RecentChanges page |
147 | */ |
148 | private static function isRCPage( Title $title ) { |
149 | return $title->isSpecial( 'Recentchanges' ) || |
150 | $title->isSpecial( 'Recentchangeslinked' ); |
151 | } |
152 | |
153 | /** |
154 | * Check whether ores is enabled |
155 | * |
156 | * @return bool |
157 | */ |
158 | public static function oresUiEnabled() { |
159 | global $wgOresUiEnabled; |
160 | |
161 | return (bool)$wgOresUiEnabled; |
162 | } |
163 | |
164 | /** |
165 | * Internal helper to get threshold |
166 | * @param string $type |
167 | * @param UserIdentity $user |
168 | * @param Title|null $title |
169 | * @return float|null Threshold, or null if not set |
170 | * @throws Exception When $type is not recognized |
171 | */ |
172 | public static function getThreshold( $type, UserIdentity $user, Title $title = null ) { |
173 | if ( $type === 'damaging' ) { |
174 | $pref = self::getDamagingLevelPreference( $user, $title ); |
175 | $thresholds = self::getDamagingThresholds(); |
176 | if ( isset( $thresholds[$pref] ) ) { |
177 | return $thresholds[$pref]; |
178 | } |
179 | |
180 | return null; |
181 | } |
182 | throw new InvalidArgumentException( "Unknown ORES test: '$type'" ); |
183 | } |
184 | |
185 | /** |
186 | * Internal helper to get damaging level preference |
187 | * with backward compatibility for old level names |
188 | * @param UserIdentity $user |
189 | * @param Title|null $title |
190 | * @return string 'maybebad', 'likelybad', or 'verylikelybad' |
191 | */ |
192 | public static function getDamagingLevelPreference( UserIdentity $user, Title $title = null ) { |
193 | $option = !$title || self::isWLPage( $title ) ? 'oresDamagingPref' : 'rcOresDamagingPref'; |
194 | $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); |
195 | $pref = $userOptionsLookup->getOption( $user, $option ); |
196 | if ( isset( self::$damagingPrefMap[$pref] ) ) { |
197 | $pref = self::$damagingPrefMap[$pref]; |
198 | } |
199 | |
200 | return $pref; |
201 | } |
202 | |
203 | /** |
204 | * @param Title $title |
205 | * @return bool Whether $title is the Watchlist page |
206 | */ |
207 | private static function isWLPage( Title $title ) { |
208 | return $title->isSpecial( 'Watchlist' ); |
209 | } |
210 | |
211 | public static function getDamagingThresholds() { |
212 | $thresholds = []; |
213 | foreach ( ORESServices::getThresholdLookup()->getThresholds( 'damaging' ) as $name => $bounds ) { |
214 | $thresholds[$name] = $bounds['min']; |
215 | } |
216 | unset( $thresholds['likelygood'] ); |
217 | |
218 | return $thresholds; |
219 | } |
220 | |
221 | public static function isRCStructuredUiEnabled( IContextSource $context ) { |
222 | /** @var SpecialRecentChanges $page */ |
223 | $page = MediaWikiServices::getInstance()->getSpecialPageFactory() |
224 | ->getPage( 'Recentchanges' ); |
225 | '@phan-var SpecialRecentChanges $page'; |
226 | $page->setContext( $context ); |
227 | |
228 | return $page->isStructuredFilterUiEnabled(); |
229 | } |
230 | |
231 | public static function isWLStructuredUiEnabled( IContextSource $context ) { |
232 | /** @var SpecialWatchlist $page */ |
233 | $page = MediaWikiServices::getInstance()->getSpecialPageFactory() |
234 | ->getPage( 'Watchlist' ); |
235 | '@phan-var SpecialWatchlist $page'; |
236 | $page->setContext( $context ); |
237 | |
238 | return $page->isStructuredFilterUiEnabled(); |
239 | } |
240 | |
241 | } |