Completed
Push — master ( ad597d...f2cae3 )
by Joas
07:56 queued 07:04
created

Manager::callForSeenUsers()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 19
Code Lines 14

Duplication

Lines 11
Ratio 57.89 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 5
nop 1
dl 11
loc 19
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Michael U <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Vincent Chan <[email protected]>
16
 * @author Volkan Gezer <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OC\User;
35
36
use OC\Hooks\PublicEmitter;
37
use OCP\IUser;
38
use OCP\IUserBackend;
39
use OCP\IUserManager;
40
use OCP\IConfig;
41
42
/**
43
 * Class Manager
44
 *
45
 * Hooks available in scope \OC\User:
46
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
47
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
48
 * - preDelete(\OC\User\User $user)
49
 * - postDelete(\OC\User\User $user)
50
 * - preCreateUser(string $uid, string $password)
51
 * - postCreateUser(\OC\User\User $user, string $password)
52
 * - change(\OC\User\User $user)
53
 *
54
 * @package OC\User
55
 */
56
class Manager extends PublicEmitter implements IUserManager {
57
	/**
58
	 * @var \OCP\UserInterface[] $backends
59
	 */
60
	private $backends = array();
61
62
	/**
63
	 * @var \OC\User\User[] $cachedUsers
64
	 */
65
	private $cachedUsers = array();
66
67
	/**
68
	 * @var \OCP\IConfig $config
69
	 */
70
	private $config;
71
72
	/**
73
	 * @param \OCP\IConfig $config
74
	 */
75
	public function __construct(IConfig $config) {
76
		$this->config = $config;
77
		$cachedUsers = &$this->cachedUsers;
78
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
79
			/** @var \OC\User\User $user */
80
			unset($cachedUsers[$user->getUID()]);
81
		});
82
		$this->listen('\OC\User', 'postLogin', function ($user) {
83
			/** @var \OC\User\User $user */
84
			$user->updateLastLoginTimestamp();
85
		});
86
		$this->listen('\OC\User', 'postRememberedLogin', function ($user) {
87
			/** @var \OC\User\User $user */
88
			$user->updateLastLoginTimestamp();
89
		});
90
	}
91
92
	/**
93
	 * Get the active backends
94
	 * @return \OCP\UserInterface[]
95
	 */
96
	public function getBackends() {
97
		return $this->backends;
98
	}
99
100
	/**
101
	 * register a user backend
102
	 *
103
	 * @param \OCP\UserInterface $backend
104
	 */
105
	public function registerBackend($backend) {
106
		$this->backends[] = $backend;
107
	}
108
109
	/**
110
	 * remove a user backend
111
	 *
112
	 * @param \OCP\UserInterface $backend
113
	 */
114
	public function removeBackend($backend) {
115
		$this->cachedUsers = array();
116
		if (($i = array_search($backend, $this->backends)) !== false) {
117
			unset($this->backends[$i]);
118
		}
119
	}
120
121
	/**
122
	 * remove all user backends
123
	 */
124
	public function clearBackends() {
125
		$this->cachedUsers = array();
126
		$this->backends = array();
127
	}
128
129
	/**
130
	 * get a user by user id
131
	 *
132
	 * @param string $uid
133
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
134
	 */
135
	public function get($uid) {
136
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
137
			return $this->cachedUsers[$uid];
138
		}
139
		foreach ($this->backends as $backend) {
140
			if ($backend->userExists($uid)) {
141
				return $this->getUserObject($uid, $backend);
142
			}
143
		}
144
		return null;
145
	}
146
147
	/**
148
	 * get or construct the user object
149
	 *
150
	 * @param string $uid
151
	 * @param \OCP\UserInterface $backend
152
	 * @param bool $cacheUser If false the newly created user object will not be cached
153
	 * @return \OC\User\User
154
	 */
155
	protected function getUserObject($uid, $backend, $cacheUser = true) {
156
		if (isset($this->cachedUsers[$uid])) {
157
			return $this->cachedUsers[$uid];
158
		}
159
160
		if (method_exists($backend, 'loginName2UserName')) {
161
			$loginName = $backend->loginName2UserName($uid);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method loginName2UserName() does only exist in the following implementations of said interface: OCA\User_LDAP\User_LDAP, OCA\User_LDAP\User_Proxy, OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
162
			if ($loginName !== false) {
163
				$uid = $loginName;
164
			}
165
			if (isset($this->cachedUsers[$uid])) {
166
				return $this->cachedUsers[$uid];
167
			}
168
		}
169
170
		$user = new User($uid, $backend, $this, $this->config);
171
		if ($cacheUser) {
172
			$this->cachedUsers[$uid] = $user;
173
		}
174
		return $user;
175
	}
176
177
	/**
178
	 * check if a user exists
179
	 *
180
	 * @param string $uid
181
	 * @return bool
182
	 */
183
	public function userExists($uid) {
184
		$user = $this->get($uid);
185
		return ($user !== null);
186
	}
187
188
	/**
189
	 * Check if the password is valid for the user
190
	 *
191
	 * @param string $loginName
192
	 * @param string $password
193
	 * @return mixed the User object on success, false otherwise
194
	 */
195
	public function checkPassword($loginName, $password) {
196
		$loginName = str_replace("\0", '', $loginName);
197
		$password = str_replace("\0", '', $password);
198
		
199
		foreach ($this->backends as $backend) {
200
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
201
				$uid = $backend->checkPassword($loginName, $password);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method checkPassword() does only exist in the following implementations of said interface: OCA\User_LDAP\User_LDAP, OCA\User_LDAP\User_Proxy, OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
202
				if ($uid !== false) {
203
					return $this->getUserObject($uid, $backend);
204
				}
205
			}
206
		}
207
208
		\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
209
		return false;
210
	}
211
212
	/**
213
	 * search by user id
214
	 *
215
	 * @param string $pattern
216
	 * @param int $limit
217
	 * @param int $offset
218
	 * @return \OC\User\User[]
219
	 */
220 View Code Duplication
	public function search($pattern, $limit = null, $offset = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
		$users = array();
222
		foreach ($this->backends as $backend) {
223
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
224
			if (is_array($backendUsers)) {
225
				foreach ($backendUsers as $uid) {
226
					$users[$uid] = $this->getUserObject($uid, $backend);
227
				}
228
			}
229
		}
230
231
		uasort($users, function ($a, $b) {
232
			/**
233
			 * @var \OC\User\User $a
234
			 * @var \OC\User\User $b
235
			 */
236
			return strcmp($a->getUID(), $b->getUID());
237
		});
238
		return $users;
239
	}
240
241
	/**
242
	 * search by displayName
243
	 *
244
	 * @param string $pattern
245
	 * @param int $limit
246
	 * @param int $offset
247
	 * @return \OC\User\User[]
248
	 */
249 View Code Duplication
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
		$users = array();
251
		foreach ($this->backends as $backend) {
252
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
253
			if (is_array($backendUsers)) {
254
				foreach ($backendUsers as $uid => $displayName) {
255
					$users[] = $this->getUserObject($uid, $backend);
256
				}
257
			}
258
		}
259
260
		usort($users, function ($a, $b) {
261
			/**
262
			 * @var \OC\User\User $a
263
			 * @var \OC\User\User $b
264
			 */
265
			return strcmp($a->getDisplayName(), $b->getDisplayName());
266
		});
267
		return $users;
268
	}
269
270
	/**
271
	 * @param string $uid
272
	 * @param string $password
273
	 * @throws \Exception
274
	 * @return bool|\OC\User\User the created user or false
275
	 */
276
	public function createUser($uid, $password) {
277
		$l = \OC::$server->getL10N('lib');
278
		// Check the name for bad characters
279
		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
280
		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
281
			throw new \Exception($l->t('Only the following characters are allowed in a username:'
282
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
283
		}
284
		// No empty username
285
		if (trim($uid) == '') {
286
			throw new \Exception($l->t('A valid username must be provided'));
287
		}
288
		// No whitespace at the beginning or at the end
289
		if (strlen(trim($uid, "\t\n\r\0\x0B\xe2\x80\x8b")) !== strlen(trim($uid))) {
290
			throw new \Exception($l->t('Username contains whitespace at the beginning or at the end'));
291
		}
292
		// No empty password
293
		if (trim($password) == '') {
294
			throw new \Exception($l->t('A valid password must be provided'));
295
		}
296
297
		// Check if user already exists
298
		if ($this->userExists($uid)) {
299
			throw new \Exception($l->t('The username is already being used'));
300
		}
301
302
		$this->emit('\OC\User', 'preCreateUser', array($uid, $password));
303
		foreach ($this->backends as $backend) {
304
			if ($backend->implementsActions(Backend::CREATE_USER)) {
305
				$backend->createUser($uid, $password);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method createUser() does only exist in the following implementations of said interface: OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
306
				$user = $this->getUserObject($uid, $backend);
307
				$this->emit('\OC\User', 'postCreateUser', array($user, $password));
308
				return $user;
309
			}
310
		}
311
		return false;
312
	}
313
314
	/**
315
	 * returns how many users per backend exist (if supported by backend)
316
	 *
317
	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
318
	 *                entry in the preferences table will be affected
319
	 * @return array|int an array of backend class as key and count number as value
320
	 *                if $hasLoggedIn is true only an int is returned
321
	 */
322
	public function countUsers($hasLoggedIn = false) {
323
		if ($hasLoggedIn) {
324
			return $this->countSeenUsers();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->countSeenUsers(); (integer) is incompatible with the return type declared by the interface OCP\IUserManager::countUsers of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
325
		}
326
		$userCountStatistics = [];
327
		foreach ($this->backends as $backend) {
328
			if ($backend->implementsActions(Backend::COUNT_USERS)) {
329
				$backendUsers = $backend->countUsers();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method countUsers() does only exist in the following implementations of said interface: OCA\User_LDAP\User_LDAP, OCA\User_LDAP\User_Proxy, OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
330
				if($backendUsers !== false) {
331
					if($backend instanceof IUserBackend) {
332
						$name = $backend->getBackendName();
333
					} else {
334
						$name = get_class($backend);
335
					}
336
					if(isset($userCountStatistics[$name])) {
337
						$userCountStatistics[$name] += $backendUsers;
338
					} else {
339
						$userCountStatistics[$name] = $backendUsers;
340
					}
341
				}
342
			}
343
		}
344
		return $userCountStatistics;
345
	}
346
347
	/**
348
	 * The callback is executed for each user on each backend.
349
	 * If the callback returns false no further users will be retrieved.
350
	 *
351
	 * @param \Closure $callback
352
	 * @param string $search
353
	 * @param boolean $onlySeen when true only users that have a lastLogin entry
354
	 *                in the preferences table will be affected
355
	 * @since 9.0.0
356
	 */
357
	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
358
		if ($onlySeen) {
359
			$this->callForSeenUsers($callback);
360
		} else {
361
			foreach ($this->getBackends() as $backend) {
362
				$limit = 500;
363
				$offset = 0;
364
				do {
365
					$users = $backend->getUsers($search, $limit, $offset);
366 View Code Duplication
					foreach ($users as $uid) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
367
						if (!$backend->userExists($uid)) {
368
							continue;
369
						}
370
						$user = $this->getUserObject($uid, $backend, false);
371
						$return = $callback($user);
372
						if ($return === false) {
373
							break;
374
						}
375
					}
376
					$offset += $limit;
377
				} while (count($users) >= $limit);
378
			}
379
		}
380
	}
381
382
	/**
383
	 * returns how many users have logged in once
384
	 *
385
	 * @return int
386
	 * @since 9.2.0
387
	 */
388
	public function countSeenUsers() {
389
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
390
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
391
			->from('preferences')
392
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
393
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
394
			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
395
396
		$query = $queryBuilder->execute();
397
398
		$result = (int)$query->fetchColumn();
399
		$query->closeCursor();
400
401
		return $result;
402
	}
403
404
	/**
405
	 * @param \Closure $callback
406
	 * @since 9.2.0
407
	 */
408
	public function callForSeenUsers(\Closure $callback) {
409
		$limit = 1000;
410
		$offset = 0;
411
		do {
412
			$userIds = $this->getSeenUserIds($limit, $offset);
413
			$offset += $limit;
414 View Code Duplication
			foreach ($userIds as $userId) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
415
				foreach ($this->backends as $backend) {
416
					if ($backend->userExists($userId)) {
417
						$user = $this->getUserObject($userId, $backend, false);
418
						$return = $callback($user);
419
						if ($return === false) {
420
							return;
421
						}
422
					}
423
				}
424
			}
425
		} while (count($userIds) >= $limit);
426
	}
427
428
	/**
429
	 * Getting all userIds that have a listLogin value requires checking the
430
	 * value in php because on oracle you cannot use a clob in a where clause,
431
	 * preventing us from doing a not null or length(value) > 0 check.
432
	 * 
433
	 * @param int $limit
434
	 * @param int $offset
435
	 * @return string[] with user ids
436
	 */
437
	private function getSeenUserIds($limit = null, $offset = null) {
438
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
439
		$queryBuilder->select(['userid'])
440
			->from('preferences')
441
			->where($queryBuilder->expr()->eq(
442
				'appid', $queryBuilder->createNamedParameter('login'))
443
			)
444
			->andWhere($queryBuilder->expr()->eq(
445
				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
446
			)
447
			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
448
			);
449
450
		if ($limit !== null) {
451
			$queryBuilder->setMaxResults($limit);
452
		}
453
		if ($offset !== null) {
454
			$queryBuilder->setFirstResult($offset);
455
		}
456
		$query = $queryBuilder->execute();
457
		$result = [];
458
459
		while ($row = $query->fetch()) {
460
			$result[] = $row['userid'];
461
		}
462
463
		$query->closeCursor();
464
465
		return $result;
466
	}
467
468
	/**
469
	 * @param string $email
470
	 * @return IUser[]
471
	 * @since 9.1.0
472
	 */
473
	public function getByEmail($email) {
474
		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
475
476
		return array_map(function($uid) {
477
			return $this->get($uid);
478
		}, $userIds);
479
	}
480
}
481