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