Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockUtils
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 2
132
0.00% covered (danger)
0.00%
0 / 1
 getBlockErrorMsgs
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 logBlockedEditAttempt
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace WikimediaEvents;
4
5use MediaWiki\Block\Block;
6use MediaWiki\Context\RequestContext;
7use MediaWiki\Extension\EventLogging\EventLogging;
8use MediaWiki\MainConfigNames;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\Message\Message;
11use MediaWiki\Title\Title;
12use MediaWiki\User\User;
13
14class BlockUtils {
15    // Possible block error keys from Block\BlockErrorFormatter::getBlockErrorMessageKey()
16    public const LOCAL_ERROR_KEYS = [
17        'blockedtext',
18        'autoblockedtext',
19        'blockedtext-partial',
20        'systemblockedtext',
21        'blockedtext-composite'
22    ];
23    // Possible block error keys from GlobalBlocking extension
24    public const GLOBAL_ERROR_KEYS = [
25        'globalblocking-blockedtext-ip',
26        'globalblocking-blockedtext-range',
27        'globalblocking-blockedtext-xff',
28        'globalblocking-blockedtext-user',
29        'globalblocking-blockedtext-autoblock',
30        'globalblocking-blockedtext-autoblock-xff',
31        // WikimediaMessages versions
32        'wikimedia-globalblocking-blockedtext-ip',
33        'wikimedia-globalblocking-blockedtext-range',
34        'wikimedia-globalblocking-blockedtext-xff',
35        'wikimedia-globalblocking-blockedtext-user',
36        'wikimedia-globalblocking-blockedtext-autoblock',
37        'wikimedia-globalblocking-blockedtext-autoblock-xff',
38    ];
39
40    /**
41     * Build error messages for error keys
42     *
43     * @param array[] $errors from PermissionManager getPermissionErrors
44     * @return array<string, Message[]>
45     */
46    public static function getBlockErrorMsgs( $errors ) {
47        $blockedErrorMsgs = $globalBlockedErrorMsgs = [];
48        foreach ( $errors as $error ) {
49            $errorMsg = Message::newFromSpecifier( $error );
50            $errorKey = $errorMsg->getKey();
51            if ( in_array( $errorKey, self::LOCAL_ERROR_KEYS, true ) ) {
52                $blockedErrorMsgs[] = $errorMsg;
53            } elseif ( in_array( $errorKey, self::GLOBAL_ERROR_KEYS, true ) ) {
54                $globalBlockedErrorMsgs[] = $errorMsg;
55            }
56        }
57        $allErrorMsgs = array_merge( $blockedErrorMsgs, $globalBlockedErrorMsgs );
58
59        return [
60            'local' => $blockedErrorMsgs,
61            'global' => $globalBlockedErrorMsgs,
62            'all' => $allErrorMsgs,
63        ];
64    }
65
66    /**
67     * Log a blocked edit attempt
68     *
69     * @param User $user
70     * @param Title $title
71     * @param string $interface
72     * @param string $platform
73     */
74    public static function logBlockedEditAttempt( $user, $title, $interface, $platform ) {
75        // Prefer the local block over the global one if both are set. This is
76        // somewhat arbitrary, but is consistent with account creation block
77        // logging.
78        $local = MediaWikiServices::getInstance()->getPermissionManager()->isBlockedFrom( $user, $title, true );
79        if ( $local ) {
80            $block = $user->getBlock();
81        } else {
82            $block = $user->getGlobalBlock();
83        }
84
85        if ( !$block ) {
86            return;
87        }
88
89        $rawExpiry = $block->getExpiry();
90        if ( wfIsInfinity( $rawExpiry ) ) {
91            $expiry = 'infinity';
92        } else {
93            $expiry = wfTimestamp( TS_ISO_8601, $rawExpiry );
94        }
95
96        $request = RequestContext::getMain()->getRequest();
97        // Avoid accessing the service and its dependencies if we can by checking
98        // first if we can get the country code from the GeoIP cookie.
99        $countryCode = WikimediaEventsCountryCodeLookup::getFromCookie( $request );
100        if ( !$countryCode ) {
101            /** @var WikimediaEventsCountryCodeLookup $countryCodeLookup */
102            $countryCodeLookup = MediaWikiServices::getInstance()->get( 'WikimediaEventsCountryCodeLookup' );
103            $countryCode = $countryCodeLookup->getFromGeoIP( $request );
104        }
105
106        $event = [
107            '$schema' => '/analytics/mediawiki/editattemptsblocked/1.0.0',
108            'block_id' => json_encode( $block->getIdentifier() ),
109            // @phan-suppress-next-line PhanTypeMismatchDimFetchNullable
110            'block_type' => Block::BLOCK_TYPES[ $block->getType() ] ?? 'other',
111            'block_expiry' => $expiry,
112            'block_scope' => $local ? 'local' : 'global',
113            'platform' => $platform,
114            'interface' => $interface,
115            'country_code' => WikimediaEventsCountryCodeLookup::getCountryCodeFormattedForEvent( $countryCode ),
116            // http.client_ip is handled by eventgate-wikimedia
117            'database' => MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::DBname ),
118            'page_id' => $title->getId(),
119            'page_namespace' => $title->getNamespace(),
120            'rev_id' => $title->getLatestRevID(),
121            'performer' => [
122                'user_id' => $user->getId(),
123                'user_edit_count' => $user->getEditCount() ?: 0,
124            ],
125        ];
126        EventLogging::submit( 'mediawiki.editattempt_block', $event );
127    }
128
129}