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