Completed
Pull Request — master (#9261)
by John
34:10 queued 10:11
created

Manager::countDisabledUsersOfGroups()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 1
dl 0
loc 17
rs 9.4285
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 Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Vincent Chan <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\User;
33
34
use OC\Hooks\PublicEmitter;
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCP\IUser;
37
use OCP\IUserBackend;
38
use OCP\IUserManager;
39
use OCP\IConfig;
40
use OCP\UserInterface;
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
 * - assignedUserId(string $uid)
54
 * - preUnassignedUserId(string $uid)
55
 * - postUnassignedUserId(string $uid)
56
 *
57
 * @package OC\User
58
 */
59
class Manager extends PublicEmitter implements IUserManager {
0 ignored issues
show
Bug introduced by
There is one abstract method countDisabledUsers in this class; you could implement it, or declare this class as abstract.
Loading history...
60
	/**
61
	 * @var \OCP\UserInterface[] $backends
62
	 */
63
	private $backends = array();
64
65
	/**
66
	 * @var \OC\User\User[] $cachedUsers
67
	 */
68
	private $cachedUsers = array();
69
70
	/**
71
	 * @var \OCP\IConfig $config
72
	 */
73
	private $config;
74
75
	/**
76
	 * @param \OCP\IConfig $config
77
	 */
78
	public function __construct(IConfig $config) {
79
		$this->config = $config;
80
		$cachedUsers = &$this->cachedUsers;
81
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
82
			/** @var \OC\User\User $user */
83
			unset($cachedUsers[$user->getUID()]);
84
		});
85
	}
86
87
	/**
88
	 * Get the active backends
89
	 * @return \OCP\UserInterface[]
90
	 */
91
	public function getBackends() {
92
		return $this->backends;
93
	}
94
95
	/**
96
	 * register a user backend
97
	 *
98
	 * @param \OCP\UserInterface $backend
99
	 */
100
	public function registerBackend($backend) {
101
		$this->backends[] = $backend;
102
	}
103
104
	/**
105
	 * remove a user backend
106
	 *
107
	 * @param \OCP\UserInterface $backend
108
	 */
109
	public function removeBackend($backend) {
110
		$this->cachedUsers = array();
111
		if (($i = array_search($backend, $this->backends)) !== false) {
112
			unset($this->backends[$i]);
113
		}
114
	}
115
116
	/**
117
	 * remove all user backends
118
	 */
119
	public function clearBackends() {
120
		$this->cachedUsers = array();
121
		$this->backends = array();
122
	}
123
124
	/**
125
	 * get a user by user id
126
	 *
127
	 * @param string $uid
128
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
129
	 */
130
	public function get($uid) {
131
		if (is_null($uid) || $uid === '' || $uid === false) {
132
			return null;
133
		}
134
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
135
			return $this->cachedUsers[$uid];
136
		}
137
		foreach ($this->backends as $backend) {
138
			if ($backend->userExists($uid)) {
139
				return $this->getUserObject($uid, $backend);
140
			}
141
		}
142
		return null;
143
	}
144
145
	/**
146
	 * get or construct the user object
147
	 *
148
	 * @param string $uid
149
	 * @param \OCP\UserInterface $backend
150
	 * @param bool $cacheUser If false the newly created user object will not be cached
151
	 * @return \OC\User\User
152
	 */
153
	protected function getUserObject($uid, $backend, $cacheUser = true) {
154
		if (isset($this->cachedUsers[$uid])) {
155
			return $this->cachedUsers[$uid];
156
		}
157
158
		$user = new User($uid, $backend, $this, $this->config);
159
		if ($cacheUser) {
160
			$this->cachedUsers[$uid] = $user;
161
		}
162
		return $user;
163
	}
164
165
	/**
166
	 * check if a user exists
167
	 *
168
	 * @param string $uid
169
	 * @return bool
170
	 */
171
	public function userExists($uid) {
172
		$user = $this->get($uid);
173
		return ($user !== null);
174
	}
175
176
	/**
177
	 * Check if the password is valid for the user
178
	 *
179
	 * @param string $loginName
180
	 * @param string $password
181
	 * @return mixed the User object on success, false otherwise
182
	 */
183
	public function checkPassword($loginName, $password) {
184
		$result = $this->checkPasswordNoLogging($loginName, $password);
185
186
		if ($result === false) {
187
			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
188
		}
189
190
		return $result;
191
	}
192
193
	/**
194
	 * Check if the password is valid for the user
195
	 *
196
	 * @internal
197
	 * @param string $loginName
198
	 * @param string $password
199
	 * @return mixed the User object on success, false otherwise
200
	 */
201
	public function checkPasswordNoLogging($loginName, $password) {
202
		$loginName = str_replace("\0", '', $loginName);
203
		$password = str_replace("\0", '', $password);
204
205 View Code Duplication
		foreach ($this->backends as $backend) {
206
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
207
				$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\Testing\AlternativeHomeUserBackend, 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...
208
				if ($uid !== false) {
209
					return $this->getUserObject($uid, $backend);
210
				}
211
			}
212
		}
213
214
		return false;
215
	}
216
217
	/**
218
	 * search by user id
219
	 *
220
	 * @param string $pattern
221
	 * @param int $limit
222
	 * @param int $offset
223
	 * @return \OC\User\User[]
224
	 */
225 View Code Duplication
	public function search($pattern, $limit = null, $offset = null) {
226
		$users = array();
227
		foreach ($this->backends as $backend) {
228
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
229
			if (is_array($backendUsers)) {
230
				foreach ($backendUsers as $uid) {
231
					$users[$uid] = $this->getUserObject($uid, $backend);
232
				}
233
			}
234
		}
235
236
		uasort($users, function ($a, $b) {
237
			/**
238
			 * @var \OC\User\User $a
239
			 * @var \OC\User\User $b
240
			 */
241
			return strcmp($a->getUID(), $b->getUID());
242
		});
243
		return $users;
244
	}
245
246
	/**
247
	 * search by displayName
248
	 *
249
	 * @param string $pattern
250
	 * @param int $limit
251
	 * @param int $offset
252
	 * @return \OC\User\User[]
253
	 */
254 View Code Duplication
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
255
		$users = array();
256
		foreach ($this->backends as $backend) {
257
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
258
			if (is_array($backendUsers)) {
259
				foreach ($backendUsers as $uid => $displayName) {
260
					$users[] = $this->getUserObject($uid, $backend);
261
				}
262
			}
263
		}
264
265
		usort($users, function ($a, $b) {
266
			/**
267
			 * @var \OC\User\User $a
268
			 * @var \OC\User\User $b
269
			 */
270
			return strcmp(strtolower($a->getDisplayName()), strtolower($b->getDisplayName()));
271
		});
272
		return $users;
273
	}
274
275
	/**
276
	 * @param string $uid
277
	 * @param string $password
278
	 * @throws \InvalidArgumentException
279
	 * @return bool|IUser the created user or false
280
	 */
281
	public function createUser($uid, $password) {
282
		$localBackends = [];
283 View Code Duplication
		foreach ($this->backends as $backend) {
284
			if ($backend instanceof Database) {
285
				// First check if there is another user backend
286
				$localBackends[] = $backend;
287
				continue;
288
			}
289
290
			if ($backend->implementsActions(Backend::CREATE_USER)) {
291
				return $this->createUserFromBackend($uid, $password, $backend);
292
			}
293
		}
294
295
		foreach ($localBackends as $backend) {
296
			if ($backend->implementsActions(Backend::CREATE_USER)) {
297
				return $this->createUserFromBackend($uid, $password, $backend);
298
			}
299
		}
300
301
		return false;
302
	}
303
304
	/**
305
	 * @param string $uid
306
	 * @param string $password
307
	 * @param UserInterface $backend
308
	 * @return IUser|null
309
	 * @throws \InvalidArgumentException
310
	 */
311
	public function createUserFromBackend($uid, $password, UserInterface $backend) {
312
		$l = \OC::$server->getL10N('lib');
313
314
		// Check the name for bad characters
315
		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
316
		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
317
			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
318
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
319
		}
320
		// No empty username
321
		if (trim($uid) === '') {
322
			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
323
		}
324
		// No whitespace at the beginning or at the end
325
		if (trim($uid) !== $uid) {
326
			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
327
		}
328
		// Username only consists of 1 or 2 dots (directory traversal)
329
		if ($uid === '.' || $uid === '..') {
330
			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
331
		}
332
		// No empty password
333
		if (trim($password) === '') {
334
			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
335
		}
336
337
		// Check if user already exists
338
		if ($this->userExists($uid)) {
339
			throw new \InvalidArgumentException($l->t('The username is already being used'));
340
		}
341
342
		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
343
		$state = $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: OCA\Testing\AlternativeHomeUserBackend, 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...
344
		if($state === false) {
345
			throw new \InvalidArgumentException($l->t('Could not create user'));
346
		}
347
		$user = $this->getUserObject($uid, $backend);
348
		if ($user instanceof IUser) {
349
			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
350
		}
351
		return $user;
352
	}
353
354
	/**
355
	 * returns how many users per backend exist (if supported by backend)
356
	 *
357
	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
358
	 *                entry in the preferences table will be affected
359
	 * @return array|int an array of backend class as key and count number as value
360
	 *                if $hasLoggedIn is true only an int is returned
361
	 */
362
	public function countUsers($hasLoggedIn = false) {
363
		if ($hasLoggedIn) {
364
			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...
365
		}
366
		$userCountStatistics = [];
367
		foreach ($this->backends as $backend) {
368
			if ($backend->implementsActions(Backend::COUNT_USERS)) {
369
				$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\Testing\AlternativeHomeUserBackend, 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...
370
				if($backendUsers !== false) {
371
					if($backend instanceof IUserBackend) {
372
						$name = $backend->getBackendName();
373
					} else {
374
						$name = get_class($backend);
375
					}
376
					if(isset($userCountStatistics[$name])) {
377
						$userCountStatistics[$name] += $backendUsers;
378
					} else {
379
						$userCountStatistics[$name] = $backendUsers;
380
					}
381
				}
382
			}
383
		}
384
		return $userCountStatistics;
385
	}
386
387
	/**
388
	 * The callback is executed for each user on each backend.
389
	 * If the callback returns false no further users will be retrieved.
390
	 *
391
	 * @param \Closure $callback
392
	 * @param string $search
393
	 * @param boolean $onlySeen when true only users that have a lastLogin entry
394
	 *                in the preferences table will be affected
395
	 * @since 9.0.0
396
	 */
397
	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
398
		if ($onlySeen) {
399
			$this->callForSeenUsers($callback);
400
		} else {
401
			foreach ($this->getBackends() as $backend) {
402
				$limit = 500;
403
				$offset = 0;
404
				do {
405
					$users = $backend->getUsers($search, $limit, $offset);
406 View Code Duplication
					foreach ($users as $uid) {
407
						if (!$backend->userExists($uid)) {
408
							continue;
409
						}
410
						$user = $this->getUserObject($uid, $backend, false);
411
						$return = $callback($user);
412
						if ($return === false) {
413
							break;
414
						}
415
					}
416
					$offset += $limit;
417
				} while (count($users) >= $limit);
418
			}
419
		}
420
	}
421
422
	/**
423
	 * returns how many users are disabled in the requested groups
424
	 *
425
	 * @param array $groups groupids to search
426
	 * @return int
427
	 * @since 14.0.0
428
	 */
429
	public function countDisabledUsersOfGroups(array $groups):int {
430
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
431
		$queryBuilder->select($queryBuilder->createFunction('COUNT(Distinct uid)'))
432
			->from('preferences', 'p')
433
			->innerJoin('p', 'group_user', 'g', 'p.userid = g.uid')
434
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
435
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
436
			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
437
			->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
438
439
		$query = $queryBuilder->execute();
440
441
		$result = (int)$query->fetchColumn();
442
		$query->closeCursor();
443
444
		return $result;
445
	}
446
447
	/**
448
	 * returns how many users have logged in once
449
	 *
450
	 * @return int
451
	 * @since 11.0.0
452
	 */
453
	public function countSeenUsers() {
454
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
455
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
456
			->from('preferences')
457
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
458
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
459
			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
460
461
		$query = $queryBuilder->execute();
462
463
		$result = (int)$query->fetchColumn();
464
		$query->closeCursor();
465
466
		return $result;
467
	}
468
469
	/**
470
	 * @param \Closure $callback
471
	 * @since 11.0.0
472
	 */
473
	public function callForSeenUsers(\Closure $callback) {
474
		$limit = 1000;
475
		$offset = 0;
476
		do {
477
			$userIds = $this->getSeenUserIds($limit, $offset);
478
			$offset += $limit;
479 View Code Duplication
			foreach ($userIds as $userId) {
480
				foreach ($this->backends as $backend) {
481
					if ($backend->userExists($userId)) {
482
						$user = $this->getUserObject($userId, $backend, false);
483
						$return = $callback($user);
484
						if ($return === false) {
485
							return;
486
						}
487
						break;
488
					}
489
				}
490
			}
491
		} while (count($userIds) >= $limit);
492
	}
493
494
	/**
495
	 * Getting all userIds that have a listLogin value requires checking the
496
	 * value in php because on oracle you cannot use a clob in a where clause,
497
	 * preventing us from doing a not null or length(value) > 0 check.
498
	 *
499
	 * @param int $limit
500
	 * @param int $offset
501
	 * @return string[] with user ids
502
	 */
503
	private function getSeenUserIds($limit = null, $offset = null) {
504
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
505
		$queryBuilder->select(['userid'])
506
			->from('preferences')
507
			->where($queryBuilder->expr()->eq(
508
				'appid', $queryBuilder->createNamedParameter('login'))
509
			)
510
			->andWhere($queryBuilder->expr()->eq(
511
				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
512
			)
513
			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
514
			);
515
516
		if ($limit !== null) {
517
			$queryBuilder->setMaxResults($limit);
518
		}
519
		if ($offset !== null) {
520
			$queryBuilder->setFirstResult($offset);
521
		}
522
		$query = $queryBuilder->execute();
523
		$result = [];
524
525
		while ($row = $query->fetch()) {
526
			$result[] = $row['userid'];
527
		}
528
529
		$query->closeCursor();
530
531
		return $result;
532
	}
533
534
	/**
535
	 * @param string $email
536
	 * @return IUser[]
537
	 * @since 9.1.0
538
	 */
539
	public function getByEmail($email) {
540
		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
541
542
		return array_map(function($uid) {
543
			return $this->get($uid);
544
		}, $userIds);
545
	}
546
}
547