Completed
Push — master ( d4a0af...760b28 )
by Blizzz
25:12 queued 24:34
created

Manager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 8
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\IGroup;
38
use OCP\IUserBackend;
39
use OCP\IUserManager;
40
use OCP\IConfig;
41
use OCP\UserInterface;
42
43
/**
44
 * Class Manager
45
 *
46
 * Hooks available in scope \OC\User:
47
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
48
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
49
 * - preDelete(\OC\User\User $user)
50
 * - postDelete(\OC\User\User $user)
51
 * - preCreateUser(string $uid, string $password)
52
 * - postCreateUser(\OC\User\User $user, string $password)
53
 * - change(\OC\User\User $user)
54
 * - assignedUserId(string $uid)
55
 * - preUnassignedUserId(string $uid)
56
 * - postUnassignedUserId(string $uid)
57
 *
58
 * @package OC\User
59
 */
60
class Manager extends PublicEmitter implements IUserManager {
61
	/**
62
	 * @var \OCP\UserInterface[] $backends
63
	 */
64
	private $backends = array();
65
66
	/**
67
	 * @var \OC\User\User[] $cachedUsers
68
	 */
69
	private $cachedUsers = array();
70
71
	/**
72
	 * @var \OCP\IConfig $config
73
	 */
74
	private $config;
75
76
	/**
77
	 * @param \OCP\IConfig $config
78
	 */
79
	public function __construct(IConfig $config) {
80
		$this->config = $config;
81
		$cachedUsers = &$this->cachedUsers;
82
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
83
			/** @var \OC\User\User $user */
84
			unset($cachedUsers[$user->getUID()]);
85
		});
86
	}
87
88
	/**
89
	 * Get the active backends
90
	 * @return \OCP\UserInterface[]
91
	 */
92
	public function getBackends() {
93
		return $this->backends;
94
	}
95
96
	/**
97
	 * register a user backend
98
	 *
99
	 * @param \OCP\UserInterface $backend
100
	 */
101
	public function registerBackend($backend) {
102
		$this->backends[] = $backend;
103
	}
104
105
	/**
106
	 * remove a user backend
107
	 *
108
	 * @param \OCP\UserInterface $backend
109
	 */
110
	public function removeBackend($backend) {
111
		$this->cachedUsers = array();
112
		if (($i = array_search($backend, $this->backends)) !== false) {
113
			unset($this->backends[$i]);
114
		}
115
	}
116
117
	/**
118
	 * remove all user backends
119
	 */
120
	public function clearBackends() {
121
		$this->cachedUsers = array();
122
		$this->backends = array();
123
	}
124
125
	/**
126
	 * get a user by user id
127
	 *
128
	 * @param string $uid
129
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
130
	 */
131
	public function get($uid) {
132
		if (is_null($uid) || $uid === '' || $uid === false) {
133
			return null;
134
		}
135
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
136
			return $this->cachedUsers[$uid];
137
		}
138
		foreach ($this->backends as $backend) {
139
			if ($backend->userExists($uid)) {
140
				return $this->getUserObject($uid, $backend);
141
			}
142
		}
143
		return null;
144
	}
145
146
	/**
147
	 * get or construct the user object
148
	 *
149
	 * @param string $uid
150
	 * @param \OCP\UserInterface $backend
151
	 * @param bool $cacheUser If false the newly created user object will not be cached
152
	 * @return \OC\User\User
153
	 */
154
	protected function getUserObject($uid, $backend, $cacheUser = true) {
155
		if (isset($this->cachedUsers[$uid])) {
156
			return $this->cachedUsers[$uid];
157
		}
158
159
		$user = new User($uid, $backend, $this, $this->config);
160
		if ($cacheUser) {
161
			$this->cachedUsers[$uid] = $user;
162
		}
163
		return $user;
164
	}
165
166
	/**
167
	 * check if a user exists
168
	 *
169
	 * @param string $uid
170
	 * @return bool
171
	 */
172
	public function userExists($uid) {
173
		$user = $this->get($uid);
174
		return ($user !== null);
175
	}
176
177
	/**
178
	 * Check if the password is valid for the user
179
	 *
180
	 * @param string $loginName
181
	 * @param string $password
182
	 * @return mixed the User object on success, false otherwise
183
	 */
184
	public function checkPassword($loginName, $password) {
185
		$result = $this->checkPasswordNoLogging($loginName, $password);
186
187
		if ($result === false) {
188
			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
189
		}
190
191
		return $result;
192
	}
193
194
	/**
195
	 * Check if the password is valid for the user
196
	 *
197
	 * @internal
198
	 * @param string $loginName
199
	 * @param string $password
200
	 * @return mixed the User object on success, false otherwise
201
	 */
202
	public function checkPasswordNoLogging($loginName, $password) {
203
		$loginName = str_replace("\0", '', $loginName);
204
		$password = str_replace("\0", '', $password);
205
206 View Code Duplication
		foreach ($this->backends as $backend) {
207
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
208
				$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...
209
				if ($uid !== false) {
210
					return $this->getUserObject($uid, $backend);
211
				}
212
			}
213
		}
214
215
		return false;
216
	}
217
218
	/**
219
	 * search by user id
220
	 *
221
	 * @param string $pattern
222
	 * @param int $limit
223
	 * @param int $offset
224
	 * @return \OC\User\User[]
225
	 */
226 View Code Duplication
	public function search($pattern, $limit = null, $offset = null) {
227
		$users = array();
228
		foreach ($this->backends as $backend) {
229
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
230
			if (is_array($backendUsers)) {
231
				foreach ($backendUsers as $uid) {
232
					$users[$uid] = $this->getUserObject($uid, $backend);
233
				}
234
			}
235
		}
236
237
		uasort($users, function ($a, $b) {
238
			/**
239
			 * @var \OC\User\User $a
240
			 * @var \OC\User\User $b
241
			 */
242
			return strcmp($a->getUID(), $b->getUID());
243
		});
244
		return $users;
245
	}
246
247
	/**
248
	 * search by displayName
249
	 *
250
	 * @param string $pattern
251
	 * @param int $limit
252
	 * @param int $offset
253
	 * @return \OC\User\User[]
254
	 */
255 View Code Duplication
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
256
		$users = array();
257
		foreach ($this->backends as $backend) {
258
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
259
			if (is_array($backendUsers)) {
260
				foreach ($backendUsers as $uid => $displayName) {
261
					$users[] = $this->getUserObject($uid, $backend);
262
				}
263
			}
264
		}
265
266
		usort($users, function ($a, $b) {
267
			/**
268
			 * @var \OC\User\User $a
269
			 * @var \OC\User\User $b
270
			 */
271
			return strcmp(strtolower($a->getDisplayName()), strtolower($b->getDisplayName()));
272
		});
273
		return $users;
274
	}
275
276
	/**
277
	 * @param string $uid
278
	 * @param string $password
279
	 * @throws \InvalidArgumentException
280
	 * @return bool|IUser the created user or false
281
	 */
282
	public function createUser($uid, $password) {
283
		$localBackends = [];
284 View Code Duplication
		foreach ($this->backends as $backend) {
285
			if ($backend instanceof Database) {
286
				// First check if there is another user backend
287
				$localBackends[] = $backend;
288
				continue;
289
			}
290
291
			if ($backend->implementsActions(Backend::CREATE_USER)) {
292
				return $this->createUserFromBackend($uid, $password, $backend);
293
			}
294
		}
295
296
		foreach ($localBackends as $backend) {
297
			if ($backend->implementsActions(Backend::CREATE_USER)) {
298
				return $this->createUserFromBackend($uid, $password, $backend);
299
			}
300
		}
301
302
		return false;
303
	}
304
305
	/**
306
	 * @param string $uid
307
	 * @param string $password
308
	 * @param UserInterface $backend
309
	 * @return IUser|null
310
	 * @throws \InvalidArgumentException
311
	 */
312
	public function createUserFromBackend($uid, $password, UserInterface $backend) {
313
		$l = \OC::$server->getL10N('lib');
314
315
		// Check the name for bad characters
316
		// 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...
317
		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
318
			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
319
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
320
		}
321
		// No empty username
322
		if (trim($uid) === '') {
323
			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
324
		}
325
		// No whitespace at the beginning or at the end
326
		if (trim($uid) !== $uid) {
327
			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
328
		}
329
		// Username only consists of 1 or 2 dots (directory traversal)
330
		if ($uid === '.' || $uid === '..') {
331
			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
332
		}
333
		// No empty password
334
		if (trim($password) === '') {
335
			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
336
		}
337
338
		// Check if user already exists
339
		if ($this->userExists($uid)) {
340
			throw new \InvalidArgumentException($l->t('The username is already being used'));
341
		}
342
343
		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
344
		$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...
345
		if($state === false) {
346
			throw new \InvalidArgumentException($l->t('Could not create user'));
347
		}
348
		$user = $this->getUserObject($uid, $backend);
349
		if ($user instanceof IUser) {
350
			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
351
		}
352
		return $user;
353
	}
354
355
	/**
356
	 * returns how many users per backend exist (if supported by backend)
357
	 *
358
	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
359
	 *                entry in the preferences table will be affected
360
	 * @return array|int an array of backend class as key and count number as value
361
	 *                if $hasLoggedIn is true only an int is returned
362
	 */
363
	public function countUsers($hasLoggedIn = false) {
364
		if ($hasLoggedIn) {
365
			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...
366
		}
367
		$userCountStatistics = [];
368
		foreach ($this->backends as $backend) {
369
			if ($backend->implementsActions(Backend::COUNT_USERS)) {
370
				$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...
371
				if($backendUsers !== false) {
372
					if($backend instanceof IUserBackend) {
373
						$name = $backend->getBackendName();
374
					} else {
375
						$name = get_class($backend);
376
					}
377
					if(isset($userCountStatistics[$name])) {
378
						$userCountStatistics[$name] += $backendUsers;
379
					} else {
380
						$userCountStatistics[$name] = $backendUsers;
381
					}
382
				}
383
			}
384
		}
385
		return $userCountStatistics;
386
	}
387
388
	/**
389
	 * returns how many users per backend exist in the requested groups (if supported by backend)
390
	 *
391
	 * @param IGroup[] $groups an array of gid to search in
392
	 * @return array|int an array of backend class as key and count number as value
393
	 *                if $hasLoggedIn is true only an int is returned
394
	 */
395
	public function countUsersOfGroups(array $groups) {
396
		$users = [];
397
		foreach($groups as $group) {
398
			$usersIds = array_map(function($user) {
399
				return $user->getUID();
400
			}, $group->getUsers());
401
			$users = array_merge($users, $usersIds);
402
		}
403
		return count(array_unique($users));
404
	}
405
406
	/**
407
	 * The callback is executed for each user on each backend.
408
	 * If the callback returns false no further users will be retrieved.
409
	 *
410
	 * @param \Closure $callback
411
	 * @param string $search
412
	 * @param boolean $onlySeen when true only users that have a lastLogin entry
413
	 *                in the preferences table will be affected
414
	 * @since 9.0.0
415
	 */
416
	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
417
		if ($onlySeen) {
418
			$this->callForSeenUsers($callback);
419
		} else {
420
			foreach ($this->getBackends() as $backend) {
421
				$limit = 500;
422
				$offset = 0;
423
				do {
424
					$users = $backend->getUsers($search, $limit, $offset);
425 View Code Duplication
					foreach ($users as $uid) {
426
						if (!$backend->userExists($uid)) {
427
							continue;
428
						}
429
						$user = $this->getUserObject($uid, $backend, false);
430
						$return = $callback($user);
431
						if ($return === false) {
432
							break;
433
						}
434
					}
435
					$offset += $limit;
436
				} while (count($users) >= $limit);
437
			}
438
		}
439
	}
440
441
	/**
442
	 * returns how many users are disabled
443
	 *
444
	 * @return int
445
	 * @since 12.0.0
446
	 */
447
	public function countDisabledUsers(): int {
448
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
449
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
450
			->from('preferences')
451
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
452
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
453
			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
454
455
		
456
		$result = $queryBuilder->execute();
457
		$count = $result->fetchColumn();
458
		$result->closeCursor();
459
		
460
		if ($count !== false) {
461
			$count = (int)$count;
462
		} else {
463
			$count = 0;
464
		}
465
466
		return $count;
467
	}
468
469
	/**
470
	 * returns how many users are disabled in the requested groups
471
	 *
472
	 * @param array $groups groupids to search
473
	 * @return int
474
	 * @since 14.0.0
475
	 */
476 View Code Duplication
	public function countDisabledUsersOfGroups(array $groups): int {
477
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
478
		$queryBuilder->select($queryBuilder->createFunction('COUNT(Distinct uid)'))
479
			->from('preferences', 'p')
480
			->innerJoin('p', 'group_user', 'g', 'p.userid = g.uid')
481
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
482
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
483
			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
484
			->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
485
486
		$result = $queryBuilder->execute();
487
		$count = $result->fetchColumn();
488
		$result->closeCursor();
489
		
490
		if ($count !== false) {
491
			$count = (int)$count;
492
		} else {
493
			$count = 0;
494
		}
495
496
		return $count;
497
	}
498
499
	/**
500
	 * returns how many users have logged in once
501
	 *
502
	 * @return int
503
	 * @since 11.0.0
504
	 */
505
	public function countSeenUsers() {
506
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
507
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
508
			->from('preferences')
509
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
510
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
511
			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
512
513
		$query = $queryBuilder->execute();
514
515
		$result = (int)$query->fetchColumn();
516
		$query->closeCursor();
517
518
		return $result;
519
	}
520
521
	/**
522
	 * @param \Closure $callback
523
	 * @since 11.0.0
524
	 */
525
	public function callForSeenUsers(\Closure $callback) {
526
		$limit = 1000;
527
		$offset = 0;
528
		do {
529
			$userIds = $this->getSeenUserIds($limit, $offset);
530
			$offset += $limit;
531 View Code Duplication
			foreach ($userIds as $userId) {
532
				foreach ($this->backends as $backend) {
533
					if ($backend->userExists($userId)) {
534
						$user = $this->getUserObject($userId, $backend, false);
535
						$return = $callback($user);
536
						if ($return === false) {
537
							return;
538
						}
539
						break;
540
					}
541
				}
542
			}
543
		} while (count($userIds) >= $limit);
544
	}
545
546
	/**
547
	 * Getting all userIds that have a listLogin value requires checking the
548
	 * value in php because on oracle you cannot use a clob in a where clause,
549
	 * preventing us from doing a not null or length(value) > 0 check.
550
	 *
551
	 * @param int $limit
552
	 * @param int $offset
553
	 * @return string[] with user ids
554
	 */
555
	private function getSeenUserIds($limit = null, $offset = null) {
556
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
557
		$queryBuilder->select(['userid'])
558
			->from('preferences')
559
			->where($queryBuilder->expr()->eq(
560
				'appid', $queryBuilder->createNamedParameter('login'))
561
			)
562
			->andWhere($queryBuilder->expr()->eq(
563
				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
564
			)
565
			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
566
			);
567
568
		if ($limit !== null) {
569
			$queryBuilder->setMaxResults($limit);
570
		}
571
		if ($offset !== null) {
572
			$queryBuilder->setFirstResult($offset);
573
		}
574
		$query = $queryBuilder->execute();
575
		$result = [];
576
577
		while ($row = $query->fetch()) {
578
			$result[] = $row['userid'];
579
		}
580
581
		$query->closeCursor();
582
583
		return $result;
584
	}
585
586
	/**
587
	 * @param string $email
588
	 * @return IUser[]
589
	 * @since 9.1.0
590
	 */
591
	public function getByEmail($email) {
592
		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
593
594
		return array_map(function($uid) {
595
			return $this->get($uid);
596
		}, $userIds);
597
	}
598
}
599