Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.29% covered (warning)
54.29%
38 / 70
28.57% covered (danger)
28.57%
4 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Helpers
54.29% covered (warning)
54.29%
38 / 70
28.57% covered (danger)
28.57%
4 / 14
137.04
0.00% covered (danger)
0.00%
0 / 1
 hideNonDamagingFilter
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 joinWithOresTables
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 addRowData
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 isModelEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 isDamagingFlagEnabled
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 isHighlightEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isRCPage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 oresUiEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getThreshold
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 getDamagingLevelPreference
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 isWLPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDamagingThresholds
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 isRCStructuredUiEnabled
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 isWLStructuredUiEnabled
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
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
17namespace ORES\Hooks;
18
19use Exception;
20use IContextSource;
21use InvalidArgumentException;
22use MediaWiki\MediaWikiServices;
23use MediaWiki\Specials\SpecialRecentChanges;
24use MediaWiki\Specials\SpecialWatchlist;
25use MediaWiki\Title\Title;
26use MediaWiki\User\UserIdentity;
27use ORES\Services\ORESServices;
28
29class 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}