Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
UserNamePrefixSearch
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 search
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2/**
3 * Prefix search of user names.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23namespace MediaWiki\User;
24
25use InvalidArgumentException;
26use MediaWiki\Block\HideUserUtils;
27use MediaWiki\Permissions\Authority;
28use Wikimedia\Rdbms\IConnectionProvider;
29use Wikimedia\Rdbms\IExpression;
30use Wikimedia\Rdbms\LikeValue;
31
32/**
33 * Handles searching prefixes of user names
34 *
35 * @note There are two classes called UserNamePrefixSearch.  You should use this first one, in
36 * namespace MediaWiki\User, which is a service.  \UserNamePrefixSearch is a deprecated static wrapper
37 * that forwards to the global service.
38 *
39 * @since 1.36 as a service in the current namespace
40 * @author DannyS712
41 */
42class UserNamePrefixSearch {
43
44    /** @var string */
45    public const AUDIENCE_PUBLIC = 'public';
46
47    private IConnectionProvider $dbProvider;
48    private UserNameUtils $userNameUtils;
49    private HideUserUtils $hideUserUtils;
50
51    /**
52     * @param IConnectionProvider $dbProvider
53     * @param UserNameUtils $userNameUtils
54     * @param HideUserUtils $hideUserUtils
55     */
56    public function __construct(
57        IConnectionProvider $dbProvider,
58        UserNameUtils $userNameUtils,
59        HideUserUtils $hideUserUtils
60    ) {
61        $this->dbProvider = $dbProvider;
62        $this->userNameUtils = $userNameUtils;
63        $this->hideUserUtils = $hideUserUtils;
64    }
65
66    /**
67     * Do a prefix search of user names and return a list of matching user names.
68     *
69     * @param string|Authority $audience Either AUDIENCE_PUBLIC or a user to
70     *    show the search for
71     * @param string $search
72     * @param int $limit
73     * @param int $offset How many results to offset from the beginning
74     * @return string[]
75     * @throws InvalidArgumentException if $audience is invalid
76     */
77    public function search( $audience, string $search, int $limit, int $offset = 0 ): array {
78        if ( $audience !== self::AUDIENCE_PUBLIC &&
79            !( $audience instanceof Authority )
80        ) {
81            throw new InvalidArgumentException(
82                '$audience must be AUDIENCE_PUBLIC or an Authority object'
83            );
84        }
85
86        // Invalid user names are treated as empty strings
87        $prefix = $this->userNameUtils->getCanonical( $search ) ?: '';
88
89        $dbr = $this->dbProvider->getReplicaDatabase();
90        $queryBuilder = $dbr->newSelectQueryBuilder()
91            ->select( 'user_name' )
92            ->from( 'user' )
93            ->where( $dbr->expr( 'user_name', IExpression::LIKE, new LikeValue( $prefix, $dbr->anyString() ) ) )
94            ->orderBy( 'user_name' )
95            ->limit( $limit )
96            ->offset( $offset );
97
98        // Filter out hidden user names
99        if ( $audience === self::AUDIENCE_PUBLIC || !$audience->isAllowed( 'hideuser' ) ) {
100            $queryBuilder->andWhere( $this->hideUserUtils->getExpression( $dbr ) );
101        }
102
103        return $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
104    }
105}