Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.09% covered (warning)
69.09%
38 / 55
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Util
69.09% covered (warning)
69.09%
38 / 55
42.86% covered (danger)
42.86%
3 / 7
27.57
0.00% covered (danger)
0.00%
0 / 1
 getWikiID
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getAutoModeratorUser
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 getJsonUrl
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
4.41
 getRawUrl
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
4.18
 getRevertThreshold
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 initializeLiftWingClient
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 getLanguageConfiguration
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
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 2 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 along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 *
17 * @file
18 */
19
20namespace AutoModerator;
21
22use MediaWiki\Config\Config;
23use MediaWiki\Context\RequestContext;
24use MediaWiki\Http\HttpRequestFactory;
25use MediaWiki\Json\FormatJson;
26use MediaWiki\Linker\LinkTarget;
27use MediaWiki\Logger\LoggerFactory;
28use MediaWiki\Title\TitleFactory;
29use MediaWiki\User\User;
30use MediaWiki\User\UserGroupManager;
31use MediaWiki\Utils\UrlUtils;
32use MediaWiki\WikiMap\WikiMap;
33use RuntimeException;
34use StatusValue;
35use UnexpectedValueException;
36
37class Util {
38
39    /**
40     * @param Config $config
41     *
42     * @return string Wiki ID used by AutoModerator.
43     */
44    public static function getWikiID( $config ): string {
45        $autoModeratorWikiId = $config->get( 'AutoModeratorWikiId' );
46        if ( $autoModeratorWikiId ) {
47            return $autoModeratorWikiId;
48        }
49        return WikiMap::getCurrentWikiId();
50    }
51
52    /**
53     * Get a user to perform moderation actions.
54     * @param Config $config
55     * @param UserGroupManager $userGroupManager
56     *
57     * @return User
58     */
59    public static function getAutoModeratorUser( $config, $userGroupManager ): User {
60        $username = $config->get( 'AutoModeratorUsername' );
61        $autoModeratorUser = User::newSystemUser( $username, [ 'steal' => true ] );
62        '@phan-var User $autoModeratorUser';
63        if ( !$autoModeratorUser ) {
64            throw new UnexpectedValueException(
65                "{$username} is invalid. Please change it."
66            );
67        }
68        // Assign the 'bot' group to the user, so that it looks like a bot
69        if ( !in_array( 'bot', $userGroupManager->getUserGroups( $autoModeratorUser ) ) ) {
70            $userGroupManager->addUserToGroup( $autoModeratorUser, 'bot' );
71        }
72        return $autoModeratorUser;
73    }
74
75    /**
76     * Fetch JSON data from a remote URL, parse it and return the results.
77     * @param HttpRequestFactory $requestFactory
78     * @param string $url
79     * @param bool $isSameFarm Is the URL on the same wiki farm we are making the request from?
80     * @return StatusValue A status object with the parsed JSON value, or any errors.
81     *   (Warnings coming from the HTTP library will be logged and not included here.)
82     */
83    public static function getJsonUrl(
84        HttpRequestFactory $requestFactory, $url, $isSameFarm = false
85    ): StatusValue {
86        $options = [
87            'method' => 'GET',
88            'userAgent' => $requestFactory->getUserAgent() . ' AutoModerator',
89        ];
90        if ( $isSameFarm ) {
91            $options['originalRequest'] = RequestContext::getMain()->getRequest();
92        }
93        $request = $requestFactory->create( $url, $options, __METHOD__ );
94        $status = $request->execute();
95        if ( $status->isOK() ) {
96            $status->merge( FormatJson::parse( $request->getContent(), FormatJson::FORCE_ASSOC ), true );
97        }
98        // Log warnings here. The caller is expected to handle errors so do not double-log them.
99        [ $errorStatus, $warningStatus ] = $status->splitByErrorType();
100        if ( !$warningStatus->isGood() ) {
101            // @todo replace 'en' with correct language configuration
102            LoggerFactory::getInstance( 'AutoModerator' )->warning(
103                $warningStatus->getWikiText( false, false, 'en' ),
104                [ 'exception' => new RuntimeException ]
105            );
106        }
107        return $errorStatus;
108    }
109
110    /**
111     * Get the action=raw URL for a (probably remote) title.
112     * Normal title methods would return nice URLs, which are usually disallowed for action=raw.
113     * We assume both wikis use the same URL structure.
114     * @param LinkTarget $title
115     * @param TitleFactory $titleFactory
116     * @return string
117     */
118    public static function getRawUrl(
119        LinkTarget $title,
120        TitleFactory $titleFactory,
121        UrlUtils $urlUtils
122    ): string {
123        // Use getFullURL to get the interwiki domain.
124        $url = $titleFactory->newFromLinkTarget( $title )->getFullURL();
125        $parts = $urlUtils->parse( (string)$urlUtils->expand( $url, PROTO_CANONICAL ) );
126        if ( !$parts ) {
127            throw new UnexpectedValueException( 'URL is expected to be valid' );
128        }
129        $baseUrl = $parts['scheme'] . $parts['delimiter'] . $parts['host'];
130        if ( isset( $parts['port'] ) && $parts['port'] ) {
131            $baseUrl .= ':' . $parts['port'];
132        }
133
134        $localPageTitle = $titleFactory->makeTitle( $title->getNamespace(), $title->getDBkey() );
135        return $baseUrl . $localPageTitle->getLocalURL( [ 'action' => 'raw' ] );
136    }
137
138    /**
139     * If the AutoModeratorRevertProbability configuration
140     * field is set below 0.95 we default to 0.95 to prevent
141     * large numbers of false positives.
142     * @param Config $config
143     * @return float AutoModeratorRevertProbability threshold
144     */
145    public static function getRevertThreshold( Config $config ): float {
146        $minimumThreshold = 0.95;
147        $revertThreshold = $config->get( 'AutoModeratorRevertProbability' );
148        if ( $revertThreshold < $minimumThreshold ) {
149            return $minimumThreshold;
150        }
151        return $revertThreshold;
152    }
153
154    /**
155     * @param Config $config
156     * @return LiftWingClient
157     */
158    public static function initializeLiftWingClient( Config $config ): LiftWingClient {
159        $model = 'revertrisk-language-agnostic';
160        $lang = self::getLanguageConfiguration( $config );
161        $hostHeader = $config->get( 'AutoModeratorLiftWingAddHostHeader' ) ?
162            $config->get( 'AutoModeratorLiftWingRevertRiskHostHeader' ) : null;
163        return new LiftWingClient(
164            $model,
165            $lang,
166            $config->get( 'AutoModeratorLiftWingBaseUrl' ),
167            $hostHeader );
168    }
169
170    /**
171     * @param Config $config
172     * @return false|string
173     */
174    public static function getLanguageConfiguration( Config $config ) {
175        $wikiId = self::getWikiID( $config );
176        return substr( $wikiId, 0, strpos( $wikiId, "wiki" ) );
177    }
178}