Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.74% covered (success)
94.74%
36 / 38
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
WatchedItemQueryServiceExtension
94.74% covered (success)
94.74%
36 / 38
50.00% covered (danger)
50.00%
1 / 2
20.06
0.00% covered (danger)
0.00%
0 / 1
 modifyWatchedItemsWithRCInfoQuery
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
9
 modifyWatchedItemsWithRCInfo
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
11.29
1<?php
2/**
3 * Copyright (C) 2016 Brad Jorsch <bjorsch@wikimedia.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19namespace ORES\Hooks\Api;
20
21use MediaWiki\User\UserIdentity;
22use ORES\Hooks\Helpers;
23use ORES\Services\ORESServices;
24use Wikimedia\Rdbms\IReadableDatabase;
25use Wikimedia\Rdbms\IResultWrapper;
26
27class WatchedItemQueryServiceExtension implements \WatchedItemQueryServiceExtension {
28
29    /**
30     * Modify the query
31     *
32     * This adds the joins and conditions necessary to implement the
33     * 'oresreview' and '!oresreview' filters, and ensures that query includes
34     * the fields necessary to handle the 'oresscores' value in 'includeFields'.
35     *
36     * @warning Any joins added *must* join on a unique key of the target table
37     *  unless you really know what you're doing.
38     * @param UserIdentity $user
39     * @param array $options Options from
40     *  WatchedItemQueryService::getWatchedItemsWithRecentChangeInfo()
41     * @param IReadableDatabase $db Database connection being used for the query
42     * @param string[] &$tables Tables for Database::select()
43     * @param string[] &$fields Fields for Database::select()
44     * @param array &$conds Conditions for Database::select()
45     * @param array &$dbOptions Options for Database::select()
46     * @param array &$joinConds Join conditions for Database::select()
47     */
48    public function modifyWatchedItemsWithRCInfoQuery( UserIdentity $user, array $options,
49        IReadableDatabase $db, array &$tables, array &$fields, array &$conds, array &$dbOptions,
50        array &$joinConds
51    ) {
52        if ( !$options['usedInGenerator'] && in_array( 'oresscores', $options['includeFields'], true ) ) {
53            if ( !in_array( 'rc_this_oldid', $fields, true ) ) {
54                $fields[] = 'rc_this_oldid';
55            }
56            if ( !in_array( 'rc_type', $fields, true ) ) {
57                $fields[] = 'rc_type';
58            }
59        }
60
61        $show = Helpers::isModelEnabled( 'damaging' )
62            ? array_flip( $options['filters'] ?? [] )
63            : [];
64        if ( isset( $show['oresreview'] ) || isset( $show['!oresreview'] ) ) {
65            $threshold = Helpers::getThreshold( 'damaging', $user );
66            $tables[] = 'ores_classification';
67
68            if ( isset( $show['oresreview'] ) ) {
69                $join = 'INNER JOIN';
70
71                // Filter out non-damaging and unscored edits.
72                $conds[] = $db->expr( 'oresc_probability', '>', $threshold );
73            } else {
74                $join = 'LEFT JOIN';
75
76                // Filter out damaging edits.
77                $conds[] = $db->expr( 'oresc_probability', '<=', $threshold )
78                            ->or( 'oresc_probability', '=', null );
79            }
80
81            $modelId = ORESServices::getModelLookup()->getModelId( 'damaging' );
82            $joinConds['ores_classification'] = [ $join, [
83                'rc_this_oldid=oresc_rev',
84                'oresc_model' => $modelId,
85                'oresc_class' => 1
86            ] ];
87        }
88    }
89
90    /**
91     * Modify the result
92     *
93     * This handles the 'oresscores' value in 'includeFields': it collects the
94     * applicable revision IDs, loads scores for them (using
95     * ApiHooksHandler::loadScoresForRevisions()), and adds the scoring data to the
96     * $recentChangeInfo portion of $items. If all scores were not available
97     * and the API is able to fetch them later, it truncates $items and adjusts
98     * $startFrom accordingly.
99     *
100     * @param UserIdentity $user
101     * @param array $options Options from
102     *  WatchedItemQueryService::getWatchedItemsWithRecentChangeInfo()
103     * @param IReadableDatabase $db Database connection being used for the query
104     * @param array[] &$items Array of pairs ( WatchedItem $watchedItem, string[] $recentChangeInfo )
105     * @param IResultWrapper|bool $res Database query result
106     * @param array|null &$startFrom Continuation value
107     */
108    public function modifyWatchedItemsWithRCInfo( UserIdentity $user, array $options,
109        IReadableDatabase $db, array &$items, $res, &$startFrom
110    ) {
111        if ( $options['usedInGenerator'] || !in_array( 'oresscores', $options['includeFields'], true ) ) {
112            return;
113        }
114
115        $revids = [];
116        foreach ( $items as [ $watchedItem, $rcInfo ] ) {
117            if ( (int)$rcInfo['rc_type'] === RC_EDIT || (int)$rcInfo['rc_type'] === RC_NEW ) {
118                $revids[] = $rcInfo['rc_this_oldid'];
119            }
120        }
121
122        if ( $revids ) {
123            $scores = ApiHooksHandler::loadScoresForRevisions( $revids );
124            foreach ( $items as &$item ) {
125                $rcInfo = &$item[1];
126                if ( (int)$rcInfo['rc_type'] !== RC_EDIT && (int)$rcInfo['rc_type'] !== RC_NEW ) {
127                    continue;
128                }
129
130                $revid = $rcInfo['rc_this_oldid'];
131                if ( isset( $scores[$revid] ) ) {
132                    $rcInfo['oresScores'] = $scores[$revid];
133                }
134            }
135        }
136    }
137
138}