Completed
Push — master ( 130780...854dfe )
by Morris
26s
created

Manager   C

Complexity

Total Complexity 70

Size/Duplication

Total Lines 475
Duplicated Lines 19.16 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 70
lcom 1
cbo 11
dl 91
loc 475
rs 5.6163
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getBackends() 0 3 1
A registerBackend() 0 3 1
A removeBackend() 0 6 2
A clearBackends() 0 4 1
A get() 0 11 4
B getUserObject() 0 21 6
A userExists() 0 4 1
A checkPassword() 0 9 2
A checkPasswordNoLogging() 0 15 4
A search() 20 20 4
A searchDisplayName() 20 20 4
A createUser() 0 9 3
D createUserFromBackend() 0 39 9
C countUsers() 0 24 7
C callForAllUsers() 10 24 7
A countDisabledUsers() 15 15 1
A countSeenUsers() 15 15 1
B callForSeenUsers() 11 19 6
B getSeenUserIds() 0 30 4
A getByEmail() 0 7 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Manager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manager, and based on these observations, apply Extract Interface, too.

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