Completed
Push — master ( febe01...f1ddb9 )
by Morris
13:54
created

Manager::createUser()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 9
nop 2
dl 0
loc 41
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	}
83
84
	/**
85
	 * Get the active backends
86
	 * @return \OCP\UserInterface[]
87
	 */
88
	public function getBackends() {
89
		return $this->backends;
90
	}
91
92
	/**
93
	 * register a user backend
94
	 *
95
	 * @param \OCP\UserInterface $backend
96
	 */
97
	public function registerBackend($backend) {
98
		$this->backends[] = $backend;
99
	}
100
101
	/**
102
	 * remove a user backend
103
	 *
104
	 * @param \OCP\UserInterface $backend
105
	 */
106
	public function removeBackend($backend) {
107
		$this->cachedUsers = array();
108
		if (($i = array_search($backend, $this->backends)) !== false) {
109
			unset($this->backends[$i]);
110
		}
111
	}
112
113
	/**
114
	 * remove all user backends
115
	 */
116
	public function clearBackends() {
117
		$this->cachedUsers = array();
118
		$this->backends = array();
119
	}
120
121
	/**
122
	 * get a user by user id
123
	 *
124
	 * @param string $uid
125
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
126
	 */
127
	public function get($uid) {
128
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
129
			return $this->cachedUsers[$uid];
130
		}
131
		foreach ($this->backends as $backend) {
132
			if ($backend->userExists($uid)) {
133
				return $this->getUserObject($uid, $backend);
134
			}
135
		}
136
		return null;
137
	}
138
139
	/**
140
	 * get or construct the user object
141
	 *
142
	 * @param string $uid
143
	 * @param \OCP\UserInterface $backend
144
	 * @param bool $cacheUser If false the newly created user object will not be cached
145
	 * @return \OC\User\User
146
	 */
147
	protected function getUserObject($uid, $backend, $cacheUser = true) {
148
		if (isset($this->cachedUsers[$uid])) {
149
			return $this->cachedUsers[$uid];
150
		}
151
152
		if (method_exists($backend, 'loginName2UserName')) {
153
			$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\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...
154
			if ($loginName !== false) {
155
				$uid = $loginName;
156
			}
157
			if (isset($this->cachedUsers[$uid])) {
158
				return $this->cachedUsers[$uid];
159
			}
160
		}
161
162
		$user = new User($uid, $backend, $this, $this->config);
163
		if ($cacheUser) {
164
			$this->cachedUsers[$uid] = $user;
165
		}
166
		return $user;
167
	}
168
169
	/**
170
	 * check if a user exists
171
	 *
172
	 * @param string $uid
173
	 * @return bool
174
	 */
175
	public function userExists($uid) {
176
		$user = $this->get($uid);
177
		return ($user !== null);
178
	}
179
180
	/**
181
	 * Check if the password is valid for the user
182
	 *
183
	 * @param string $loginName
184
	 * @param string $password
185
	 * @return mixed the User object on success, false otherwise
186
	 */
187
	public function checkPassword($loginName, $password) {
188
		$result = $this->checkPasswordNoLogging($loginName, $password);
189
190
		if ($result === false) {
191
			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
192
		}
193
194
		return $result;
195
	}
196
197
	/**
198
	 * Check if the password is valid for the user
199
	 *
200
	 * @internal
201
	 * @param string $loginName
202
	 * @param string $password
203
	 * @return mixed the User object on success, false otherwise
204
	 */
205
	public function checkPasswordNoLogging($loginName, $password) {
206
		$loginName = str_replace("\0", '', $loginName);
207
		$password = str_replace("\0", '', $password);
208
209
		foreach ($this->backends as $backend) {
210
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
211
				$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...
212
				if ($uid !== false) {
213
					return $this->getUserObject($uid, $backend);
214
				}
215
			}
216
		}
217
218
		return false;
219
	}
220
221
	/**
222
	 * search by user id
223
	 *
224
	 * @param string $pattern
225
	 * @param int $limit
226
	 * @param int $offset
227
	 * @return \OC\User\User[]
228
	 */
229 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...
230
		$users = array();
231
		foreach ($this->backends as $backend) {
232
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
233
			if (is_array($backendUsers)) {
234
				foreach ($backendUsers as $uid) {
235
					$users[$uid] = $this->getUserObject($uid, $backend);
236
				}
237
			}
238
		}
239
240
		uasort($users, function ($a, $b) {
241
			/**
242
			 * @var \OC\User\User $a
243
			 * @var \OC\User\User $b
244
			 */
245
			return strcmp($a->getUID(), $b->getUID());
246
		});
247
		return $users;
248
	}
249
250
	/**
251
	 * search by displayName
252
	 *
253
	 * @param string $pattern
254
	 * @param int $limit
255
	 * @param int $offset
256
	 * @return \OC\User\User[]
257
	 */
258 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...
259
		$users = array();
260
		foreach ($this->backends as $backend) {
261
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
262
			if (is_array($backendUsers)) {
263
				foreach ($backendUsers as $uid => $displayName) {
264
					$users[] = $this->getUserObject($uid, $backend);
265
				}
266
			}
267
		}
268
269
		usort($users, function ($a, $b) {
270
			/**
271
			 * @var \OC\User\User $a
272
			 * @var \OC\User\User $b
273
			 */
274
			return strcmp(strtolower($a->getDisplayName()), strtolower($b->getDisplayName()));
275
		});
276
		return $users;
277
	}
278
279
	/**
280
	 * @param string $uid
281
	 * @param string $password
282
	 * @throws \Exception
283
	 * @return bool|\OC\User\User the created user or false
284
	 */
285
	public function createUser($uid, $password) {
286
		$l = \OC::$server->getL10N('lib');
287
		// Check the name for bad characters
288
		// 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...
289
		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
290
			throw new \Exception($l->t('Only the following characters are allowed in a username:'
291
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
292
		}
293
		// No empty username
294
		if (trim($uid) == '') {
295
			throw new \Exception($l->t('A valid username must be provided'));
296
		}
297
		// No whitespace at the beginning or at the end
298
		if (trim($uid) !== $uid) {
299
			throw new \Exception($l->t('Username contains whitespace at the beginning or at the end'));
300
		}
301
		// Username only consists of 1 or 2 dots (directory traversal)
302
		if ($uid === '.' || $uid === '..') {
303
			throw new \Exception($l->t('Username must not consist of dots only'));
304
		}
305
		// No empty password
306
		if (trim($password) == '') {
307
			throw new \Exception($l->t('A valid password must be provided'));
308
		}
309
310
		// Check if user already exists
311
		if ($this->userExists($uid)) {
312
			throw new \Exception($l->t('The username is already being used'));
313
		}
314
315
		$this->emit('\OC\User', 'preCreateUser', array($uid, $password));
316
		foreach ($this->backends as $backend) {
317
			if ($backend->implementsActions(Backend::CREATE_USER)) {
318
				$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, 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...
319
				$user = $this->getUserObject($uid, $backend);
320
				$this->emit('\OC\User', 'postCreateUser', array($user, $password));
321
				return $user;
322
			}
323
		}
324
		return false;
325
	}
326
327
	/**
328
	 * returns how many users per backend exist (if supported by backend)
329
	 *
330
	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
331
	 *                entry in the preferences table will be affected
332
	 * @return array|int an array of backend class as key and count number as value
333
	 *                if $hasLoggedIn is true only an int is returned
334
	 */
335
	public function countUsers($hasLoggedIn = false) {
336
		if ($hasLoggedIn) {
337
			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...
338
		}
339
		$userCountStatistics = [];
340
		foreach ($this->backends as $backend) {
341
			if ($backend->implementsActions(Backend::COUNT_USERS)) {
342
				$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...
343
				if($backendUsers !== false) {
344
					if($backend instanceof IUserBackend) {
345
						$name = $backend->getBackendName();
346
					} else {
347
						$name = get_class($backend);
348
					}
349
					if(isset($userCountStatistics[$name])) {
350
						$userCountStatistics[$name] += $backendUsers;
351
					} else {
352
						$userCountStatistics[$name] = $backendUsers;
353
					}
354
				}
355
			}
356
		}
357
		return $userCountStatistics;
358
	}
359
360
	/**
361
	 * The callback is executed for each user on each backend.
362
	 * If the callback returns false no further users will be retrieved.
363
	 *
364
	 * @param \Closure $callback
365
	 * @param string $search
366
	 * @param boolean $onlySeen when true only users that have a lastLogin entry
367
	 *                in the preferences table will be affected
368
	 * @since 9.0.0
369
	 */
370
	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
371
		if ($onlySeen) {
372
			$this->callForSeenUsers($callback);
373
		} else {
374
			foreach ($this->getBackends() as $backend) {
375
				$limit = 500;
376
				$offset = 0;
377
				do {
378
					$users = $backend->getUsers($search, $limit, $offset);
379 View Code Duplication
					foreach ($users as $uid) {
380
						if (!$backend->userExists($uid)) {
381
							continue;
382
						}
383
						$user = $this->getUserObject($uid, $backend, false);
384
						$return = $callback($user);
385
						if ($return === false) {
386
							break;
387
						}
388
					}
389
					$offset += $limit;
390
				} while (count($users) >= $limit);
391
			}
392
		}
393
	}
394
395
	/**
396
	 * returns how many users have logged in once
397
	 *
398
	 * @return int
399
	 * @since 11.0.0
400
	 */
401
	public function countSeenUsers() {
402
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
403
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
404
			->from('preferences')
405
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
406
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
407
			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
408
409
		$query = $queryBuilder->execute();
410
411
		$result = (int)$query->fetchColumn();
412
		$query->closeCursor();
413
414
		return $result;
415
	}
416
417
	/**
418
	 * @param \Closure $callback
419
	 * @since 11.0.0
420
	 */
421
	public function callForSeenUsers(\Closure $callback) {
422
		$limit = 1000;
423
		$offset = 0;
424
		do {
425
			$userIds = $this->getSeenUserIds($limit, $offset);
426
			$offset += $limit;
427 View Code Duplication
			foreach ($userIds as $userId) {
428
				foreach ($this->backends as $backend) {
429
					if ($backend->userExists($userId)) {
430
						$user = $this->getUserObject($userId, $backend, false);
431
						$return = $callback($user);
432
						if ($return === false) {
433
							return;
434
						}
435
					}
436
				}
437
			}
438
		} while (count($userIds) >= $limit);
439
	}
440
441
	/**
442
	 * Getting all userIds that have a listLogin value requires checking the
443
	 * value in php because on oracle you cannot use a clob in a where clause,
444
	 * preventing us from doing a not null or length(value) > 0 check.
445
	 * 
446
	 * @param int $limit
447
	 * @param int $offset
448
	 * @return string[] with user ids
449
	 */
450
	private function getSeenUserIds($limit = null, $offset = null) {
451
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
452
		$queryBuilder->select(['userid'])
453
			->from('preferences')
454
			->where($queryBuilder->expr()->eq(
455
				'appid', $queryBuilder->createNamedParameter('login'))
456
			)
457
			->andWhere($queryBuilder->expr()->eq(
458
				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
459
			)
460
			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
461
			);
462
463
		if ($limit !== null) {
464
			$queryBuilder->setMaxResults($limit);
465
		}
466
		if ($offset !== null) {
467
			$queryBuilder->setFirstResult($offset);
468
		}
469
		$query = $queryBuilder->execute();
470
		$result = [];
471
472
		while ($row = $query->fetch()) {
473
			$result[] = $row['userid'];
474
		}
475
476
		$query->closeCursor();
477
478
		return $result;
479
	}
480
481
	/**
482
	 * @param string $email
483
	 * @return IUser[]
484
	 * @since 9.1.0
485
	 */
486
	public function getByEmail($email) {
487
		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
488
489
		return array_map(function($uid) {
490
			return $this->get($uid);
491
		}, $userIds);
492
	}
493
}
494