Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
55 / 66
72.22% covered (warning)
72.22%
13 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserSelectQueryBuilder
83.33% covered (warning)
83.33%
55 / 66
72.22% covered (warning)
72.22%
13 / 18
29.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 whereUserIds
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 userIds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 whereUserNames
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 userNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 whereUserNamePrefix
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 userNamePrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 whereRegisteredTimestamp
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 orderByName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 orderByUserId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 registered
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 anon
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 named
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 temp
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 hidden
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 fetchUserIdentity
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 fetchUserIdentities
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 fetchUserNames
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
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 Iterator;
24use MediaWiki\Block\HideUserUtils;
25use MediaWiki\User\TempUser\TempUserConfig;
26use Wikimedia\Assert\Assert;
27use Wikimedia\Assert\PreconditionException;
28use Wikimedia\Rdbms\IExpression;
29use Wikimedia\Rdbms\IReadableDatabase;
30use Wikimedia\Rdbms\LikeValue;
31use Wikimedia\Rdbms\SelectQueryBuilder;
32
33/**
34 * @ingroup User
35 */
36class UserSelectQueryBuilder extends SelectQueryBuilder {
37
38    private ActorStore $actorStore;
39    private TempUserConfig $tempUserConfig;
40    private HideUserUtils $hideUserUtils;
41
42    private bool $userJoined = false;
43
44    /**
45     * @internal
46     */
47    public function __construct(
48        IReadableDatabase $db,
49        ActorStore $actorStore,
50        TempUserConfig $tempUserConfig,
51        HideUserUtils $hideUserUtils
52    ) {
53        parent::__construct( $db );
54
55        $this->actorStore = $actorStore;
56        $this->tempUserConfig = $tempUserConfig;
57        $this->hideUserUtils = $hideUserUtils;
58        $this->table( 'actor' );
59    }
60
61    /**
62     * Find by provided user ids.
63     *
64     * @param int|int[] $userIds
65     * @return UserSelectQueryBuilder
66     */
67    public function whereUserIds( $userIds ): self {
68        Assert::parameterType( [ 'integer', 'array' ], $userIds, '$userIds' );
69        $this->conds( [ 'actor_user' => $userIds ] );
70        return $this;
71    }
72
73    /**
74     * Find by provided user ids.
75     * @deprecated since 1.37, use whereUserIds instead
76     * @param int|int[] $userIds
77     * @return UserSelectQueryBuilder
78     */
79    public function userIds( $userIds ): self {
80        return $this->whereUserIds( $userIds );
81    }
82
83    /**
84     * Find by provided user names.
85     *
86     * @param string|string[] $userNames
87     * @return UserSelectQueryBuilder
88     */
89    public function whereUserNames( $userNames ): self {
90        Assert::parameterType( [ 'string', 'array' ], $userNames, '$userIds' );
91        $userNames = array_map( function ( $name ) {
92            return $this->actorStore->normalizeUserName( (string)$name );
93        }, (array)$userNames );
94        $this->conds( [ 'actor_name' => $userNames ] );
95        return $this;
96    }
97
98    /**
99     * Find by provided user names.
100     * @deprecated since 1.37, use whereUserNames instead
101     * @param string|string[] $userNames
102     * @return UserSelectQueryBuilder
103     */
104    public function userNames( $userNames ): self {
105        return $this->whereUserNames( $userNames );
106    }
107
108    /**
109     * Find users with names starting from the provided prefix.
110     *
111     * @note this could produce a huge number of results, like User00000 ... User99999,
112     * so you must set a limit when using this condition.
113     *
114     * @param string $prefix
115     * @return UserSelectQueryBuilder
116     */
117    public function whereUserNamePrefix( string $prefix ): self {
118        if ( !isset( $this->options['LIMIT'] ) ) {
119            throw new PreconditionException( 'Must set a limit when using a user name prefix' );
120        }
121        $this->conds(
122            $this->db->expr( 'actor_name', IExpression::LIKE, new LikeValue( $prefix, $this->db->anyString() ) )
123        );
124        return $this;
125    }
126
127    /**
128     * Find users with names starting from the provided prefix.
129     *
130     * @note this could produce a huge number of results, like User00000 ... User99999,
131     * so you must set a limit when using this condition.
132     * @deprecated since 1.37 use whereUserNamePrefix instead
133     * @param string $prefix
134     * @return UserSelectQueryBuilder
135     */
136    public function userNamePrefix( string $prefix ): self {
137        return $this->whereUserNamePrefix( $prefix );
138    }
139
140    /**
141     * Find registered users who registered
142     *
143     * @param string $timestamp
144     * @param bool $direction Direction flag (if true, user_registration must be before $timestamp)
145     * @since 1.42
146     * @return UserSelectQueryBuilder
147     */
148    public function whereRegisteredTimestamp( string $timestamp, bool $direction ): self {
149        if ( !$this->userJoined ) {
150            $this->join( 'user', null, [ "actor_user=user_id" ] );
151            $this->userJoined = true;
152        }
153
154        $this->conds(
155            $this->db->expr( 'user_registration', ( $direction ? '<' : '>' ), $this->db->timestamp( $timestamp ) )
156        );
157        return $this;
158    }
159
160    /**
161     * Order results by name in $direction
162     *
163     * @param string $dir one of self::SORT_ASC or self::SORT_DESC
164     * @return UserSelectQueryBuilder
165     */
166    public function orderByName( string $dir = self::SORT_ASC ): self {
167        $this->orderBy( 'actor_name', $dir );
168        return $this;
169    }
170
171    /**
172     * Order results by user id.
173     *
174     * @param string $dir one of self::SORT_ASC or self::SORT_DESC
175     * @return UserSelectQueryBuilder
176     */
177    public function orderByUserId( string $dir = self::SORT_ASC ): self {
178        $this->orderBy( 'actor_user', $dir );
179        return $this;
180    }
181
182    /**
183     * Only return registered users.
184     *
185     * @return UserSelectQueryBuilder
186     */
187    public function registered(): self {
188        $this->conds( $this->db->expr( 'actor_user', '!=', null ) );
189        return $this;
190    }
191
192    /**
193     * Only return anonymous users.
194     *
195     * @return UserSelectQueryBuilder
196     */
197    public function anon(): self {
198        $this->conds( [ 'actor_user' => null ] );
199        return $this;
200    }
201
202    /**
203     * Only return named users.
204     *
205     * @return UserSelectQueryBuilder
206     */
207    public function named(): self {
208        // All named accounts must be registered
209        $this->registered();
210
211        if ( !$this->tempUserConfig->isKnown() ) {
212            // nothing to do: getMatchCondition throws if temp accounts aren't known
213            return $this;
214        }
215        $this->conds( $this->tempUserConfig->getMatchCondition( $this->db, 'actor_name', IExpression::NOT_LIKE ) );
216        return $this;
217    }
218
219    /**
220     * Only return temp users
221     *
222     * @return UserSelectQueryBuilder
223     */
224    public function temp(): self {
225        if ( !$this->tempUserConfig->isKnown() ) {
226            $this->conds( '1=0' );
227            return $this;
228        }
229        $this->conds( $this->tempUserConfig->getMatchCondition( $this->db, 'actor_name', IExpression::LIKE ) );
230        return $this;
231    }
232
233    /**
234     * Filter based on user hidden status
235     *
236     * @since 1.38
237     * @param bool $hidden True - only hidden users, false - no hidden users
238     * @return $this
239     */
240    public function hidden( bool $hidden ): self {
241        $this->conds( $this->hideUserUtils->getExpression(
242            $this->db,
243            'actor_user',
244            $hidden ? HideUserUtils::HIDDEN_USERS : HideUserUtils::SHOWN_USERS
245        ) );
246        return $this;
247    }
248
249    /**
250     * Fetch a single UserIdentity that matches specified criteria.
251     *
252     * @return UserIdentity|null
253     */
254    public function fetchUserIdentity(): ?UserIdentity {
255        $this->fields( [ 'actor_id', 'actor_name', 'actor_user' ] );
256        $row = $this->fetchRow();
257        if ( !$row ) {
258            return null;
259        }
260        return $this->actorStore->newActorFromRow( $row );
261    }
262
263    /**
264     * Fetch UserIdentities for the specified query.
265     *
266     * @return Iterator<UserIdentity>
267     */
268    public function fetchUserIdentities(): Iterator {
269        $this->fields( [ 'actor_id', 'actor_name', 'actor_user' ] );
270
271        $result = $this->fetchResultSet();
272        foreach ( $result as $row ) {
273            yield $this->actorStore->newActorFromRow( $row );
274        }
275        $result->free();
276    }
277
278    /**
279     * Returns an array of user names matching the query.
280     *
281     * @return string[]
282     */
283    public function fetchUserNames(): array {
284        $this->field( 'actor_name' );
285        return $this->fetchFieldValues();
286    }
287}