Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.62% covered (success)
97.62%
41 / 42
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ExternalUserNames
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
6 / 6
21
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getUserLinkTitle
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
7
 applyPrefix
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
9
 addPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isExternal
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLocal
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\User;
8
9use MediaWiki\HookContainer\HookRunner;
10use MediaWiki\MediaWikiServices;
11use MediaWiki\SpecialPage\SpecialPage;
12use MediaWiki\Title\Title;
13use Wikimedia\Rdbms\IDBAccessObject;
14
15/**
16 * Class to parse and build external user names
17 * @since 1.31
18 */
19class ExternalUserNames {
20
21    private string $usernamePrefix;
22    private bool $assignKnownUsers;
23
24    /**
25     * @var bool[]
26     */
27    private $triedCreations = [];
28
29    /**
30     * @param string $usernamePrefix Prefix to apply to unknown (and possibly also known) usernames
31     * @param bool $assignKnownUsers Whether to apply the prefix to usernames that exist locally
32     */
33    public function __construct( $usernamePrefix, $assignKnownUsers ) {
34        $this->usernamePrefix = rtrim( (string)$usernamePrefix, ':>' );
35        $this->assignKnownUsers = (bool)$assignKnownUsers;
36    }
37
38    /**
39     * Get a target Title to link a username.
40     *
41     * @param string $userName Username to link
42     *
43     * @return null|Title
44     */
45    public static function getUserLinkTitle( $userName ) {
46        $pos = strpos( $userName, '>' );
47        $services = MediaWikiServices::getInstance();
48        if ( $pos !== false ) {
49            $iw = explode( ':', substr( $userName, 0, $pos ) );
50            $firstIw = array_shift( $iw );
51            $interwikiLookup = $services->getInterwikiLookup();
52            if ( $interwikiLookup->isValidInterwiki( $firstIw ) ) {
53                $title = $services->getNamespaceInfo()->getCanonicalName( NS_USER ) .
54                    ':' . substr( $userName, $pos + 1 );
55                if ( $iw ) {
56                    $title = implode( ':', $iw ) . ':' . $title;
57                }
58                return Title::makeTitle( NS_MAIN, $title, '', $firstIw );
59            }
60            return null;
61        } else {
62            // Protect against invalid user names from old corrupt database rows, T232451
63            if (
64                $services->getUserNameUtils()->isIP( $userName )
65                || $services->getUserNameUtils()->isValidIPRange( $userName )
66                || $services->getUserNameUtils()->isValid( $userName )
67            ) {
68                return SpecialPage::getTitleFor( 'Contributions', $userName );
69            } else {
70                // Bad user name, no link
71                return null;
72            }
73        }
74    }
75
76    /**
77     * Add an interwiki prefix to the username, if appropriate
78     *
79     * This method does have a side-effect on SUL (single user login) wikis: Calling this calls the
80     * ImportHandleUnknownUser hook from the CentralAuth extension, which assigns a local ID to the
81     * global user name, if possible. No prefix is applied if this is successful.
82     *
83     * @see https://meta.wikimedia.org/wiki/Help:Unified_login
84     * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImportHandleUnknownUser
85     *
86     * @param string $name
87     * @return string Either the unchanged username if it's a known local user (or not a valid
88     *  username), otherwise the name with the prefix prepended.
89     */
90    public function applyPrefix( $name ) {
91        $services = MediaWikiServices::getInstance();
92        $userNameUtils = $services->getUserNameUtils();
93        if ( $userNameUtils->getCanonical( $name, UserRigorOptions::RIGOR_USABLE ) === false ) {
94            return $name;
95        }
96
97        if ( $this->assignKnownUsers ) {
98            $userIdentityLookup = $services->getUserIdentityLookup();
99            $userIdentity = $userIdentityLookup->getUserIdentityByName( $name );
100            if ( $userIdentity && $userIdentity->isRegistered() ) {
101                return $name;
102            }
103
104            // See if any extension wants to create it.
105            if ( !isset( $this->triedCreations[$name] ) ) {
106                $this->triedCreations[$name] = true;
107                if ( !( new HookRunner( $services->getHookContainer() ) )->onImportHandleUnknownUser( $name ) ) {
108                    $userIdentity = $userIdentityLookup->getUserIdentityByName( $name, IDBAccessObject::READ_LATEST );
109                    if ( $userIdentity && $userIdentity->isRegistered() ) {
110                        return $name;
111                    }
112                }
113            }
114        }
115
116        return $this->addPrefix( $name );
117    }
118
119    /**
120     * Add an interwiki prefix to the username regardless of circumstances
121     *
122     * @param string $name
123     * @return string Prefixed username, using ">" as separator
124     */
125    public function addPrefix( $name ) {
126        return substr( $this->usernamePrefix . '>' . $name, 0, 255 );
127    }
128
129    /**
130     * Tells whether the username is external or not
131     *
132     * @param string $username Username to check
133     * @return bool true if it's external, false otherwise.
134     */
135    public static function isExternal( $username ) {
136        return str_contains( $username, '>' );
137    }
138
139    /**
140     * Get local part of the user name
141     *
142     * @param string $username Username to get
143     * @return string
144     */
145    public static function getLocal( $username ) {
146        if ( !self::isExternal( $username ) ) {
147            return $username;
148        }
149
150        return substr( $username, strpos( $username, '>' ) + 1 );
151    }
152
153}
154
155/** @deprecated class alias since 1.41 */
156class_alias( ExternalUserNames::class, 'ExternalUserNames' );