Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.57% covered (warning)
58.57%
41 / 70
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FetchScoreJob
58.57% covered (warning)
58.57%
41 / 70
16.67% covered (danger)
16.67%
1 / 6
48.44
0.00% covered (danger)
0.00%
0 / 1
 __construct
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 setScoreFetcher
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
62.50% covered (warning)
62.50%
20 / 32
0.00% covered (danger)
0.00%
0 / 1
7.90
 findDuplicates
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 getCleanupModels
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
6.07
 fireORESRecentChangeScoreSavedHook
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
4.13
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\Services;
18
19use Job;
20use MediaWiki\Logger\LoggerFactory;
21use MediaWiki\MediaWikiServices;
22use MediaWiki\Title\Title;
23use ORES\Hooks\HookRunner;
24use RuntimeException;
25
26class FetchScoreJob extends Job {
27
28    /**
29     * @var ScoreFetcher
30     */
31    private $scoreFetcher;
32
33    /**
34     * @param Title $title
35     * @param array $params
36     *   - 'revid': (int|int[]) revision IDs for which to fetch the score
37     *   - 'originalRequest': (string[]) request data to forward to the upstream API;
38     *       see MwHttpRequest::setOriginalRequest()
39     */
40    public function __construct( Title $title, array $params ) {
41        $expensive = is_array( $params['revid'] );
42
43        if ( $expensive ) {
44            sort( $params['revid'] );
45        }
46
47        parent::__construct( 'ORESFetchScoreJob', $title, $params );
48
49        $this->removeDuplicates = $expensive;
50        $this->scoreFetcher = ScoreFetcher::instance();
51    }
52
53    /**
54     * ScoreFetcher service override for testing
55     *
56     * @param ScoreFetcher $scoreFetcher
57     */
58    public function setScoreFetcher( ScoreFetcher $scoreFetcher ) {
59        $this->scoreFetcher = $scoreFetcher;
60    }
61
62    public function run() {
63        $logger = LoggerFactory::getInstance( 'ORES' );
64
65        if ( $this->removeDuplicates ) {
66            $revids = $this->findDuplicates();
67            if ( !$revids ) {
68                $logger->debug( 'Skipping fetch, no revisions need scores: ' . json_encode( $this->params ) );
69                return true;
70            }
71            $this->params['revid'] = $revids;
72        }
73
74        $logger->info( 'Fetching scores for revision ' . json_encode( $this->params ) );
75
76        try {
77            $scores = $this->scoreFetcher->getScores(
78                $this->params['revid'],
79                $this->params['models'] ?? null,
80                $this->params['precache'],
81                $this->params['originalRequest'] ?? null
82            );
83        } catch ( RuntimeException $exception ) {
84            $mssg = $exception->getMessage();
85            $logger->warning( "Service failed to respond properly: $mssg\n" );
86            return false;
87        }
88
89        $success = true;
90        ORESServices::getScoreStorage()->storeScores(
91            $scores,
92            static function ( $mssg, $revision ) use ( &$success, $logger ) {
93                $logger->warning( "ScoreFetcher errored for $revision$mssg\n" );
94                if ( $mssg !== 'RevisionNotFound' ) {
95                    $success = false;
96                }
97            },
98            $this->getCleanupModels()
99        );
100        if ( $success === true ) {
101            $logger->debug( 'Stored scores: ' . json_encode( $scores ) );
102        }
103
104        $this->fireORESRecentChangeScoreSavedHook( (array)$this->params['revid'], $scores );
105
106        return $success;
107    }
108
109    private function findDuplicates() {
110        $revids = (array)$this->params['revid'];
111        $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
112        $revids = array_diff(
113            $revids,
114            $dbr->selectFieldValues(
115                'ores_classification',
116                'oresc_rev',
117                [ 'oresc_rev' => $revids ],
118                __METHOD__,
119                [ 'DISTINCT' ]
120            )
121        );
122
123        return $revids;
124    }
125
126    private function getCleanupModels() {
127        global $wgOresModels;
128        $models = [];
129        foreach ( $wgOresModels as $modelName => $model ) {
130            if ( !isset( $model['enabled'] ) || !$model['enabled'] ) {
131                continue;
132            }
133
134            if ( !isset( $model['cleanParent'] ) || !$model['cleanParent'] ) {
135                continue;
136            }
137
138            $models[] = $modelName;
139        }
140
141        return $models;
142    }
143
144    private function fireORESRecentChangeScoreSavedHook( array $revids, array $scores ) {
145        $services = MediaWikiServices::getInstance();
146        $revisionStore = $services->getRevisionStore();
147        $hookRunner = new HookRunner( $services->getHookContainer() );
148        foreach ( $revids as $revid ) {
149            if ( !isset( $scores[$revid] ) ) {
150                continue;
151            }
152
153            $revision = $revisionStore->getRevisionById( (int)$revid );
154            if ( $revision === null ) {
155                continue;
156            }
157
158            $hookRunner->onORESRecentChangeScoreSavedHook( $revision, $scores );
159        }
160    }
161
162}