Completed
Push — master ( 1ac6ea...e71b9f )
by Joas
17:09
created

Manager::search()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 20
Ratio 100 %

Importance

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