Completed
Push — master ( 3d6699...623b8e )
by
unknown
21:01
created
lib/public/IUserManager.php 1 patch
Indentation   +221 added lines, -221 removed lines patch added patch discarded remove patch
@@ -24,225 +24,225 @@
 block discarded – undo
24 24
  * @since 8.0.0
25 25
  */
26 26
 interface IUserManager {
27
-	/**
28
-	 * @since 26.0.0
29
-	 */
30
-	public const MAX_PASSWORD_LENGTH = 469;
31
-
32
-	/**
33
-	 * register a user backend
34
-	 *
35
-	 * @since 8.0.0
36
-	 * @return void
37
-	 */
38
-	public function registerBackend(UserInterface $backend);
39
-
40
-	/**
41
-	 * Get the active backends
42
-	 * @return UserInterface[]
43
-	 * @since 8.0.0
44
-	 */
45
-	public function getBackends();
46
-
47
-	/**
48
-	 * remove a user backend
49
-	 *
50
-	 * @since 8.0.0
51
-	 * @return void
52
-	 */
53
-	public function removeBackend(UserInterface $backend);
54
-
55
-	/**
56
-	 * remove all user backends
57
-	 * @since 8.0.0
58
-	 * @return void
59
-	 */
60
-	public function clearBackends();
61
-
62
-	/**
63
-	 * get a user by user id
64
-	 *
65
-	 * @param string $uid
66
-	 * @return \OCP\IUser|null Either the user or null if the specified user does not exist
67
-	 * @since 8.0.0
68
-	 */
69
-	public function get($uid);
70
-
71
-	/**
72
-	 * Get the display name of a user
73
-	 *
74
-	 * @param string $uid
75
-	 * @return string|null
76
-	 * @since 25.0.0
77
-	 */
78
-	public function getDisplayName(string $uid): ?string;
79
-
80
-	/**
81
-	 * check if a user exists
82
-	 *
83
-	 * @param string $uid
84
-	 * @return bool
85
-	 * @since 8.0.0
86
-	 */
87
-	public function userExists($uid);
88
-
89
-	/**
90
-	 * Check if the password is valid for the user
91
-	 *
92
-	 * @param string $loginName
93
-	 * @param string $password
94
-	 * @return IUser|false the User object on success, false otherwise
95
-	 * @since 8.0.0
96
-	 */
97
-	public function checkPassword($loginName, $password);
98
-
99
-	/**
100
-	 * search by user id
101
-	 *
102
-	 * @param string $pattern
103
-	 * @param int $limit
104
-	 * @param int $offset
105
-	 * @return \OCP\IUser[]
106
-	 * @since 8.0.0
107
-	 * @deprecated 27.0.0, use searchDisplayName instead
108
-	 */
109
-	public function search($pattern, $limit = null, $offset = null);
110
-
111
-	/**
112
-	 * search by displayName
113
-	 *
114
-	 * @param string $pattern
115
-	 * @param int $limit
116
-	 * @param int $offset
117
-	 * @return \OCP\IUser[]
118
-	 * @since 8.0.0
119
-	 */
120
-	public function searchDisplayName($pattern, $limit = null, $offset = null);
121
-
122
-	/**
123
-	 * @return IUser[]
124
-	 * @since 28.0.0
125
-	 * @since 30.0.0 $search parameter added
126
-	 */
127
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
128
-
129
-	/**
130
-	 * Search known users (from phonebook sync) by displayName
131
-	 *
132
-	 * @param string $searcher
133
-	 * @param string $pattern
134
-	 * @param int|null $limit
135
-	 * @param int|null $offset
136
-	 * @return IUser[]
137
-	 * @since 21.0.1
138
-	 */
139
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
140
-
141
-	/**
142
-	 * @param string $uid
143
-	 * @param string $password
144
-	 * @throws \InvalidArgumentException
145
-	 * @return false|\OCP\IUser the created user or false
146
-	 * @since 8.0.0
147
-	 */
148
-	public function createUser($uid, $password);
149
-
150
-	/**
151
-	 * @param string $uid
152
-	 * @param string $password
153
-	 * @param UserInterface $backend
154
-	 * @return IUser|null
155
-	 * @throws \InvalidArgumentException
156
-	 * @since 12.0.0
157
-	 */
158
-	public function createUserFromBackend($uid, $password, UserInterface $backend);
159
-
160
-	/**
161
-	 * Get how many users per backend exist (if supported by backend)
162
-	 *
163
-	 * @return array<string, int> an array of backend class name as key and count number as value
164
-	 * @since 8.0.0
165
-	 */
166
-	public function countUsers();
167
-
168
-	/**
169
-	 * Get how many users exists in total, whithin limit
170
-	 *
171
-	 * @param int $limit Limit the count to avoid resource waste. 0 to disable
172
-	 * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends
173
-	 *
174
-	 * @since 31.0.0
175
-	 */
176
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false;
177
-
178
-	/**
179
-	 * @param \Closure $callback
180
-	 * @psalm-param \Closure(\OCP\IUser):void $callback
181
-	 * @param string $search
182
-	 * @since 9.0.0
183
-	 */
184
-	public function callForAllUsers(\Closure $callback, $search = '');
185
-
186
-	/**
187
-	 * returns how many users have logged in once
188
-	 *
189
-	 * @return int
190
-	 * @since 11.0.0
191
-	 */
192
-	public function countDisabledUsers();
193
-
194
-	/**
195
-	 * returns how many users have logged in once
196
-	 *
197
-	 * @return int
198
-	 * @since 11.0.0
199
-	 */
200
-	public function countSeenUsers();
201
-
202
-	/**
203
-	 * @param \Closure $callback
204
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
205
-	 * @since 11.0.0
206
-	 */
207
-	public function callForSeenUsers(\Closure $callback);
208
-
209
-	/**
210
-	 * returns all users having the provided email set as system email address
211
-	 *
212
-	 * @param string $email
213
-	 * @return IUser[]
214
-	 * @since 9.1.0
215
-	 */
216
-	public function getByEmail($email);
217
-
218
-	/**
219
-	 * @param string $uid The user ID to validate
220
-	 * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
221
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
222
-	 * @since 26.0.0
223
-	 */
224
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
225
-
226
-	/**
227
-	 * Gets the list of users sorted by lastLogin, from most recent to least recent
228
-	 *
229
-	 * @param int|null $limit how many records to fetch
230
-	 * @param int $offset from which offset to fetch
231
-	 * @param string $search search users based on search params
232
-	 * @return list<string> list of user IDs
233
-	 * @since 30.0.0
234
-	 */
235
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
236
-
237
-	/**
238
-	 * Gets the list of users.
239
-	 * An iterator is returned allowing the caller to stop the iteration at any time.
240
-	 * The offset argument allows the caller to continue the iteration at a specific offset.
241
-	 *
242
-	 * @param int $offset from which offset to fetch
243
-	 * @param int|null $limit maximum number of records to fetch
244
-	 * @return \Iterator<IUser> list of IUser object
245
-	 * @since 32.0.0
246
-	 */
247
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
27
+    /**
28
+     * @since 26.0.0
29
+     */
30
+    public const MAX_PASSWORD_LENGTH = 469;
31
+
32
+    /**
33
+     * register a user backend
34
+     *
35
+     * @since 8.0.0
36
+     * @return void
37
+     */
38
+    public function registerBackend(UserInterface $backend);
39
+
40
+    /**
41
+     * Get the active backends
42
+     * @return UserInterface[]
43
+     * @since 8.0.0
44
+     */
45
+    public function getBackends();
46
+
47
+    /**
48
+     * remove a user backend
49
+     *
50
+     * @since 8.0.0
51
+     * @return void
52
+     */
53
+    public function removeBackend(UserInterface $backend);
54
+
55
+    /**
56
+     * remove all user backends
57
+     * @since 8.0.0
58
+     * @return void
59
+     */
60
+    public function clearBackends();
61
+
62
+    /**
63
+     * get a user by user id
64
+     *
65
+     * @param string $uid
66
+     * @return \OCP\IUser|null Either the user or null if the specified user does not exist
67
+     * @since 8.0.0
68
+     */
69
+    public function get($uid);
70
+
71
+    /**
72
+     * Get the display name of a user
73
+     *
74
+     * @param string $uid
75
+     * @return string|null
76
+     * @since 25.0.0
77
+     */
78
+    public function getDisplayName(string $uid): ?string;
79
+
80
+    /**
81
+     * check if a user exists
82
+     *
83
+     * @param string $uid
84
+     * @return bool
85
+     * @since 8.0.0
86
+     */
87
+    public function userExists($uid);
88
+
89
+    /**
90
+     * Check if the password is valid for the user
91
+     *
92
+     * @param string $loginName
93
+     * @param string $password
94
+     * @return IUser|false the User object on success, false otherwise
95
+     * @since 8.0.0
96
+     */
97
+    public function checkPassword($loginName, $password);
98
+
99
+    /**
100
+     * search by user id
101
+     *
102
+     * @param string $pattern
103
+     * @param int $limit
104
+     * @param int $offset
105
+     * @return \OCP\IUser[]
106
+     * @since 8.0.0
107
+     * @deprecated 27.0.0, use searchDisplayName instead
108
+     */
109
+    public function search($pattern, $limit = null, $offset = null);
110
+
111
+    /**
112
+     * search by displayName
113
+     *
114
+     * @param string $pattern
115
+     * @param int $limit
116
+     * @param int $offset
117
+     * @return \OCP\IUser[]
118
+     * @since 8.0.0
119
+     */
120
+    public function searchDisplayName($pattern, $limit = null, $offset = null);
121
+
122
+    /**
123
+     * @return IUser[]
124
+     * @since 28.0.0
125
+     * @since 30.0.0 $search parameter added
126
+     */
127
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
128
+
129
+    /**
130
+     * Search known users (from phonebook sync) by displayName
131
+     *
132
+     * @param string $searcher
133
+     * @param string $pattern
134
+     * @param int|null $limit
135
+     * @param int|null $offset
136
+     * @return IUser[]
137
+     * @since 21.0.1
138
+     */
139
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
140
+
141
+    /**
142
+     * @param string $uid
143
+     * @param string $password
144
+     * @throws \InvalidArgumentException
145
+     * @return false|\OCP\IUser the created user or false
146
+     * @since 8.0.0
147
+     */
148
+    public function createUser($uid, $password);
149
+
150
+    /**
151
+     * @param string $uid
152
+     * @param string $password
153
+     * @param UserInterface $backend
154
+     * @return IUser|null
155
+     * @throws \InvalidArgumentException
156
+     * @since 12.0.0
157
+     */
158
+    public function createUserFromBackend($uid, $password, UserInterface $backend);
159
+
160
+    /**
161
+     * Get how many users per backend exist (if supported by backend)
162
+     *
163
+     * @return array<string, int> an array of backend class name as key and count number as value
164
+     * @since 8.0.0
165
+     */
166
+    public function countUsers();
167
+
168
+    /**
169
+     * Get how many users exists in total, whithin limit
170
+     *
171
+     * @param int $limit Limit the count to avoid resource waste. 0 to disable
172
+     * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends
173
+     *
174
+     * @since 31.0.0
175
+     */
176
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false;
177
+
178
+    /**
179
+     * @param \Closure $callback
180
+     * @psalm-param \Closure(\OCP\IUser):void $callback
181
+     * @param string $search
182
+     * @since 9.0.0
183
+     */
184
+    public function callForAllUsers(\Closure $callback, $search = '');
185
+
186
+    /**
187
+     * returns how many users have logged in once
188
+     *
189
+     * @return int
190
+     * @since 11.0.0
191
+     */
192
+    public function countDisabledUsers();
193
+
194
+    /**
195
+     * returns how many users have logged in once
196
+     *
197
+     * @return int
198
+     * @since 11.0.0
199
+     */
200
+    public function countSeenUsers();
201
+
202
+    /**
203
+     * @param \Closure $callback
204
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
205
+     * @since 11.0.0
206
+     */
207
+    public function callForSeenUsers(\Closure $callback);
208
+
209
+    /**
210
+     * returns all users having the provided email set as system email address
211
+     *
212
+     * @param string $email
213
+     * @return IUser[]
214
+     * @since 9.1.0
215
+     */
216
+    public function getByEmail($email);
217
+
218
+    /**
219
+     * @param string $uid The user ID to validate
220
+     * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
221
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
222
+     * @since 26.0.0
223
+     */
224
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
225
+
226
+    /**
227
+     * Gets the list of users sorted by lastLogin, from most recent to least recent
228
+     *
229
+     * @param int|null $limit how many records to fetch
230
+     * @param int $offset from which offset to fetch
231
+     * @param string $search search users based on search params
232
+     * @return list<string> list of user IDs
233
+     * @since 30.0.0
234
+     */
235
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
236
+
237
+    /**
238
+     * Gets the list of users.
239
+     * An iterator is returned allowing the caller to stop the iteration at any time.
240
+     * The offset argument allows the caller to continue the iteration at a specific offset.
241
+     *
242
+     * @param int $offset from which offset to fetch
243
+     * @param int|null $limit maximum number of records to fetch
244
+     * @return \Iterator<IUser> list of IUser object
245
+     * @since 32.0.0
246
+     */
247
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
248 248
 }
Please login to merge, or discard this patch.
lib/private/User/Manager.php 1 patch
Indentation   +776 added lines, -776 removed lines patch added patch discarded remove patch
@@ -53,780 +53,780 @@
 block discarded – undo
53 53
  * @package OC\User
54 54
  */
55 55
 class Manager extends PublicEmitter implements IUserManager {
56
-	/**
57
-	 * @var UserInterface[] $backends
58
-	 */
59
-	private array $backends = [];
60
-
61
-	/**
62
-	 * @var array<string,\OC\User\User> $cachedUsers
63
-	 */
64
-	private array $cachedUsers = [];
65
-
66
-	private ICache $cache;
67
-
68
-	private DisplayNameCache $displayNameCache;
69
-
70
-	public function __construct(
71
-		private IConfig $config,
72
-		ICacheFactory $cacheFactory,
73
-		private IEventDispatcher $eventDispatcher,
74
-		private LoggerInterface $logger,
75
-	) {
76
-		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
77
-		$this->listen('\OC\User', 'postDelete', function (IUser $user): void {
78
-			unset($this->cachedUsers[$user->getUID()]);
79
-		});
80
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
81
-	}
82
-
83
-	/**
84
-	 * Get the active backends
85
-	 * @return UserInterface[]
86
-	 */
87
-	public function getBackends(): array {
88
-		return $this->backends;
89
-	}
90
-
91
-	public function registerBackend(UserInterface $backend): void {
92
-		$this->backends[] = $backend;
93
-	}
94
-
95
-	public function removeBackend(UserInterface $backend): void {
96
-		$this->cachedUsers = [];
97
-		if (($i = array_search($backend, $this->backends)) !== false) {
98
-			unset($this->backends[$i]);
99
-		}
100
-	}
101
-
102
-	public function clearBackends(): void {
103
-		$this->cachedUsers = [];
104
-		$this->backends = [];
105
-	}
106
-
107
-	/**
108
-	 * get a user by user id
109
-	 *
110
-	 * @param string $uid
111
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
112
-	 */
113
-	public function get($uid) {
114
-		if (is_null($uid) || $uid === '' || $uid === false) {
115
-			return null;
116
-		}
117
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
118
-			return $this->cachedUsers[$uid];
119
-		}
120
-
121
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
122
-			return null;
123
-		}
124
-
125
-		$cachedBackend = $this->cache->get(sha1($uid));
126
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
127
-			// Cache has the info of the user backend already, so ask that one directly
128
-			$backend = $this->backends[$cachedBackend];
129
-			if ($backend->userExists($uid)) {
130
-				return $this->getUserObject($uid, $backend);
131
-			}
132
-		}
133
-
134
-		foreach ($this->backends as $i => $backend) {
135
-			if ($i === $cachedBackend) {
136
-				// Tried that one already
137
-				continue;
138
-			}
139
-
140
-			if ($backend->userExists($uid)) {
141
-				// Hash $uid to ensure that only valid characters are used for the cache key
142
-				$this->cache->set(sha1($uid), $i, 300);
143
-				return $this->getUserObject($uid, $backend);
144
-			}
145
-		}
146
-		return null;
147
-	}
148
-
149
-	public function getDisplayName(string $uid): ?string {
150
-		return $this->displayNameCache->getDisplayName($uid);
151
-	}
152
-
153
-	/**
154
-	 * get or construct the user object
155
-	 *
156
-	 * @param string $uid
157
-	 * @param \OCP\UserInterface $backend
158
-	 * @param bool $cacheUser If false the newly created user object will not be cached
159
-	 * @return \OC\User\User
160
-	 */
161
-	public function getUserObject($uid, $backend, $cacheUser = true) {
162
-		if ($backend instanceof IGetRealUIDBackend) {
163
-			$uid = $backend->getRealUID($uid);
164
-		}
165
-
166
-		if (isset($this->cachedUsers[$uid])) {
167
-			return $this->cachedUsers[$uid];
168
-		}
169
-
170
-		$user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
171
-		if ($cacheUser) {
172
-			$this->cachedUsers[$uid] = $user;
173
-		}
174
-		return $user;
175
-	}
176
-
177
-	/**
178
-	 * check if a user exists
179
-	 *
180
-	 * @param string $uid
181
-	 * @return bool
182
-	 */
183
-	public function userExists($uid) {
184
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
185
-			return false;
186
-		}
187
-
188
-		$user = $this->get($uid);
189
-		return ($user !== null);
190
-	}
191
-
192
-	/**
193
-	 * Check if the password is valid for the user
194
-	 *
195
-	 * @param string $loginName
196
-	 * @param string $password
197
-	 * @return IUser|false the User object on success, false otherwise
198
-	 */
199
-	public function checkPassword($loginName, $password) {
200
-		$result = $this->checkPasswordNoLogging($loginName, $password);
201
-
202
-		if ($result === false) {
203
-			$this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
204
-		}
205
-
206
-		return $result;
207
-	}
208
-
209
-	/**
210
-	 * Check if the password is valid for the user
211
-	 *
212
-	 * @internal
213
-	 * @param string $loginName
214
-	 * @param string $password
215
-	 * @return IUser|false the User object on success, false otherwise
216
-	 */
217
-	public function checkPasswordNoLogging($loginName, $password) {
218
-		$loginName = str_replace("\0", '', $loginName);
219
-		$password = str_replace("\0", '', $password);
220
-
221
-		$cachedBackend = $this->cache->get($loginName);
222
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
223
-			$backends = [$this->backends[$cachedBackend]];
224
-		} else {
225
-			$backends = $this->backends;
226
-		}
227
-		foreach ($backends as $backend) {
228
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
229
-				/** @var ICheckPasswordBackend $backend */
230
-				$uid = $backend->checkPassword($loginName, $password);
231
-				if ($uid !== false) {
232
-					return $this->getUserObject($uid, $backend);
233
-				}
234
-			}
235
-		}
236
-
237
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
238
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
239
-		// to contain urlencoded patterns by "accident".
240
-		$password = urldecode($password);
241
-
242
-		foreach ($backends as $backend) {
243
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
244
-				/** @var ICheckPasswordBackend|UserInterface $backend */
245
-				$uid = $backend->checkPassword($loginName, $password);
246
-				if ($uid !== false) {
247
-					return $this->getUserObject($uid, $backend);
248
-				}
249
-			}
250
-		}
251
-
252
-		return false;
253
-	}
254
-
255
-	public function search($pattern, $limit = null, $offset = null) {
256
-		$users = [];
257
-		foreach ($this->backends as $backend) {
258
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
259
-			if (is_array($backendUsers)) {
260
-				foreach ($backendUsers as $uid) {
261
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
262
-				}
263
-			}
264
-		}
265
-
266
-		uasort($users, function (IUser $a, IUser $b) {
267
-			return strcasecmp($a->getUID(), $b->getUID());
268
-		});
269
-		return $users;
270
-	}
271
-
272
-	/**
273
-	 * Search by displayName
274
-	 *
275
-	 * @param string $pattern
276
-	 * @param int $limit
277
-	 * @param int $offset
278
-	 * @return IUser[]
279
-	 */
280
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
281
-		$users = [];
282
-		foreach ($this->backends as $backend) {
283
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
284
-			if (is_array($backendUsers)) {
285
-				foreach ($backendUsers as $uid => $displayName) {
286
-					$users[] = new LazyUser($uid, $this, $displayName, $backend);
287
-				}
288
-			}
289
-		}
290
-
291
-		usort($users, function (IUser $a, IUser $b) {
292
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
293
-		});
294
-		return $users;
295
-	}
296
-
297
-	/**
298
-	 * @return IUser[]
299
-	 */
300
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
301
-		$users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
302
-		$users = array_combine(
303
-			$users,
304
-			array_map(
305
-				fn (string $uid): IUser => new LazyUser($uid, $this),
306
-				$users
307
-			)
308
-		);
309
-		if ($search !== '') {
310
-			$users = array_filter(
311
-				$users,
312
-				function (IUser $user) use ($search): bool {
313
-					try {
314
-						return mb_stripos($user->getUID(), $search) !== false
315
-						|| mb_stripos($user->getDisplayName(), $search) !== false
316
-						|| mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
317
-					} catch (NoUserException $ex) {
318
-						$this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
319
-						return false;
320
-					}
321
-				});
322
-		}
323
-
324
-		$tempLimit = ($limit === null ? null : $limit + $offset);
325
-		foreach ($this->backends as $backend) {
326
-			if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
327
-				break;
328
-			}
329
-			if ($backend instanceof IProvideEnabledStateBackend) {
330
-				$backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
331
-				foreach ($backendUsers as $uid) {
332
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
333
-				}
334
-			}
335
-		}
336
-
337
-		return array_slice($users, $offset, $limit);
338
-	}
339
-
340
-	/**
341
-	 * Search known users (from phonebook sync) by displayName
342
-	 *
343
-	 * @param string $searcher
344
-	 * @param string $pattern
345
-	 * @param int|null $limit
346
-	 * @param int|null $offset
347
-	 * @return IUser[]
348
-	 */
349
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
350
-		$users = [];
351
-		foreach ($this->backends as $backend) {
352
-			if ($backend instanceof ISearchKnownUsersBackend) {
353
-				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
354
-			} else {
355
-				// Better than nothing, but filtering after pagination can remove lots of results.
356
-				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
357
-			}
358
-			if (is_array($backendUsers)) {
359
-				foreach ($backendUsers as $uid => $displayName) {
360
-					$users[] = $this->getUserObject($uid, $backend);
361
-				}
362
-			}
363
-		}
364
-
365
-		usort($users, function ($a, $b) {
366
-			/**
367
-			 * @var IUser $a
368
-			 * @var IUser $b
369
-			 */
370
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
371
-		});
372
-		return $users;
373
-	}
374
-
375
-	/**
376
-	 * @param string $uid
377
-	 * @param string $password
378
-	 * @return false|IUser the created user or false
379
-	 * @throws \InvalidArgumentException
380
-	 * @throws HintException
381
-	 */
382
-	public function createUser($uid, $password) {
383
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
384
-		/** @var IAssertion $assertion */
385
-		$assertion = \OC::$server->get(IAssertion::class);
386
-		$assertion->createUserIsLegit();
387
-
388
-		$localBackends = [];
389
-		foreach ($this->backends as $backend) {
390
-			if ($backend instanceof Database) {
391
-				// First check if there is another user backend
392
-				$localBackends[] = $backend;
393
-				continue;
394
-			}
395
-
396
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
397
-				return $this->createUserFromBackend($uid, $password, $backend);
398
-			}
399
-		}
400
-
401
-		foreach ($localBackends as $backend) {
402
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
403
-				return $this->createUserFromBackend($uid, $password, $backend);
404
-			}
405
-		}
406
-
407
-		return false;
408
-	}
409
-
410
-	/**
411
-	 * @param string $uid
412
-	 * @param string $password
413
-	 * @param UserInterface $backend
414
-	 * @return IUser|false
415
-	 * @throws \InvalidArgumentException
416
-	 */
417
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
418
-		$l = \OCP\Util::getL10N('lib');
419
-
420
-		$this->validateUserId($uid, true);
421
-
422
-		// No empty password
423
-		if (trim($password) === '') {
424
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
425
-		}
426
-
427
-		// Check if user already exists
428
-		if ($this->userExists($uid)) {
429
-			throw new \InvalidArgumentException($l->t('The Login is already being used'));
430
-		}
431
-
432
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
433
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
434
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
435
-		$state = $backend->createUser($uid, $password);
436
-		if ($state === false) {
437
-			throw new \InvalidArgumentException($l->t('Could not create account'));
438
-		}
439
-		$user = $this->getUserObject($uid, $backend);
440
-		if ($user instanceof IUser) {
441
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
442
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
443
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
444
-			return $user;
445
-		}
446
-		return false;
447
-	}
448
-
449
-	/**
450
-	 * returns how many users per backend exist (if supported by backend)
451
-	 *
452
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
453
-	 *                             entry in the preferences table will be affected
454
-	 * @return array<string, int> an array of backend class as key and count number as value
455
-	 */
456
-	public function countUsers() {
457
-		$userCountStatistics = [];
458
-		foreach ($this->backends as $backend) {
459
-			if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
460
-				/** @var ICountUsersBackend|IUserBackend $backend */
461
-				$backendUsers = $backend->countUsers();
462
-				if ($backendUsers !== false) {
463
-					if ($backend instanceof IUserBackend) {
464
-						$name = $backend->getBackendName();
465
-					} else {
466
-						$name = get_class($backend);
467
-					}
468
-					if (isset($userCountStatistics[$name])) {
469
-						$userCountStatistics[$name] += $backendUsers;
470
-					} else {
471
-						$userCountStatistics[$name] = $backendUsers;
472
-					}
473
-				}
474
-			}
475
-		}
476
-		return $userCountStatistics;
477
-	}
478
-
479
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
480
-		$userCount = false;
481
-
482
-		foreach ($this->backends as $backend) {
483
-			if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
484
-				$backendUsers = $backend->countMappedUsers();
485
-			} elseif ($backend instanceof ILimitAwareCountUsersBackend) {
486
-				$backendUsers = $backend->countUsers($limit);
487
-			} elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
488
-				/** @var ICountUsersBackend $backend */
489
-				$backendUsers = $backend->countUsers();
490
-			} else {
491
-				$this->logger->debug('Skip backend for user count: ' . get_class($backend));
492
-				continue;
493
-			}
494
-			if ($backendUsers !== false) {
495
-				$userCount = (int)$userCount + $backendUsers;
496
-				if ($limit > 0) {
497
-					if ($userCount >= $limit) {
498
-						break;
499
-					}
500
-					$limit -= $userCount;
501
-				}
502
-			} else {
503
-				$this->logger->warning('Can not determine user count for ' . get_class($backend));
504
-			}
505
-		}
506
-		return $userCount;
507
-	}
508
-
509
-	/**
510
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
511
-	 *
512
-	 * @param IGroup[] $groups an array of groups to search in
513
-	 * @param int $limit limit to stop counting
514
-	 * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users
515
-	 */
516
-	public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
517
-		$users = [];
518
-		$disabled = [];
519
-		foreach ($groups as $group) {
520
-			foreach ($group->getUsers() as $user) {
521
-				$users[$user->getUID()] = 1;
522
-				if (!$user->isEnabled()) {
523
-					$disabled[$user->getUID()] = 1;
524
-				}
525
-				if (count($users) >= $limit) {
526
-					return [count($users),-1];
527
-				}
528
-			}
529
-		}
530
-		return [count($users),count($disabled)];
531
-	}
532
-
533
-	/**
534
-	 * The callback is executed for each user on each backend.
535
-	 * If the callback returns false no further users will be retrieved.
536
-	 *
537
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
538
-	 * @param string $search
539
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
540
-	 *                          in the preferences table will be affected
541
-	 * @since 9.0.0
542
-	 */
543
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
544
-		if ($onlySeen) {
545
-			$this->callForSeenUsers($callback);
546
-		} else {
547
-			foreach ($this->getBackends() as $backend) {
548
-				$limit = 500;
549
-				$offset = 0;
550
-				do {
551
-					$users = $backend->getUsers($search, $limit, $offset);
552
-					foreach ($users as $uid) {
553
-						if (!$backend->userExists($uid)) {
554
-							continue;
555
-						}
556
-						$user = $this->getUserObject($uid, $backend, false);
557
-						$return = $callback($user);
558
-						if ($return === false) {
559
-							break;
560
-						}
561
-					}
562
-					$offset += $limit;
563
-				} while (count($users) >= $limit);
564
-			}
565
-		}
566
-	}
567
-
568
-	/**
569
-	 * returns how many users are disabled
570
-	 *
571
-	 * @return int
572
-	 * @since 12.0.0
573
-	 */
574
-	public function countDisabledUsers(): int {
575
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
576
-		$queryBuilder->select($queryBuilder->func()->count('*'))
577
-			->from('preferences')
578
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
579
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
580
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
581
-
582
-
583
-		$result = $queryBuilder->executeQuery();
584
-		$count = $result->fetchOne();
585
-		$result->closeCursor();
586
-
587
-		if ($count !== false) {
588
-			$count = (int)$count;
589
-		} else {
590
-			$count = 0;
591
-		}
592
-
593
-		return $count;
594
-	}
595
-
596
-	/**
597
-	 * returns how many users have logged in once
598
-	 *
599
-	 * @return int
600
-	 * @since 11.0.0
601
-	 */
602
-	public function countSeenUsers() {
603
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
604
-		$queryBuilder->select($queryBuilder->func()->count('*'))
605
-			->from('preferences')
606
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
607
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
608
-
609
-		$query = $queryBuilder->executeQuery();
610
-
611
-		$result = (int)$query->fetchOne();
612
-		$query->closeCursor();
613
-
614
-		return $result;
615
-	}
616
-
617
-	public function callForSeenUsers(\Closure $callback) {
618
-		$users = $this->getSeenUsers();
619
-		foreach ($users as $user) {
620
-			$return = $callback($user);
621
-			if ($return === false) {
622
-				return;
623
-			}
624
-		}
625
-	}
626
-
627
-	/**
628
-	 * Getting all userIds that have a lastLogin value requires checking the
629
-	 * value in php because on oracle you cannot use a clob in a where clause,
630
-	 * preventing us from doing a not null or length(value) > 0 check.
631
-	 *
632
-	 * @param int $limit
633
-	 * @param int $offset
634
-	 * @return string[] with user ids
635
-	 */
636
-	private function getSeenUserIds($limit = null, $offset = null) {
637
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
638
-		$queryBuilder->select(['userid'])
639
-			->from('preferences')
640
-			->where($queryBuilder->expr()->eq(
641
-				'appid', $queryBuilder->createNamedParameter('login'))
642
-			)
643
-			->andWhere($queryBuilder->expr()->eq(
644
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
645
-			)
646
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
647
-			);
648
-
649
-		if ($limit !== null) {
650
-			$queryBuilder->setMaxResults($limit);
651
-		}
652
-		if ($offset !== null) {
653
-			$queryBuilder->setFirstResult($offset);
654
-		}
655
-		$query = $queryBuilder->executeQuery();
656
-		$result = [];
657
-
658
-		while ($row = $query->fetch()) {
659
-			$result[] = $row['userid'];
660
-		}
661
-
662
-		$query->closeCursor();
663
-
664
-		return $result;
665
-	}
666
-
667
-	/**
668
-	 * @param string $email
669
-	 * @return IUser[]
670
-	 * @since 9.1.0
671
-	 */
672
-	public function getByEmail($email) {
673
-		// looking for 'email' only (and not primary_mail) is intentional
674
-		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
675
-
676
-		$users = array_map(function ($uid) {
677
-			return $this->get($uid);
678
-		}, $userIds);
679
-
680
-		return array_values(array_filter($users, function ($u) {
681
-			return ($u instanceof IUser);
682
-		}));
683
-	}
684
-
685
-	/**
686
-	 * @param string $uid
687
-	 * @param bool $checkDataDirectory
688
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
689
-	 * @since 26.0.0
690
-	 */
691
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
692
-		$l = Server::get(IFactory::class)->get('lib');
693
-
694
-		// Check the ID for bad characters
695
-		// Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
696
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
697
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
698
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
699
-		}
700
-
701
-		// No empty user ID
702
-		if (trim($uid) === '') {
703
-			throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
704
-		}
705
-
706
-		// No whitespace at the beginning or at the end
707
-		if (trim($uid) !== $uid) {
708
-			throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
709
-		}
710
-
711
-		// User ID only consists of 1 or 2 dots (directory traversal)
712
-		if ($uid === '.' || $uid === '..') {
713
-			throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
714
-		}
715
-
716
-		// User ID is too long
717
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
718
-			// TRANSLATORS User ID is too long
719
-			throw new \InvalidArgumentException($l->t('Username is too long'));
720
-		}
721
-
722
-		if (!$this->verifyUid($uid, $checkDataDirectory)) {
723
-			throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
724
-		}
725
-	}
726
-
727
-	/**
728
-	 * Gets the list of user ids sorted by lastLogin, from most recent to least recent
729
-	 *
730
-	 * @param int|null $limit how many users to fetch (default: 25, max: 100)
731
-	 * @param int $offset from which offset to fetch
732
-	 * @param string $search search users based on search params
733
-	 * @return list<string> list of user IDs
734
-	 */
735
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
736
-		// We can't load all users who already logged in
737
-		$limit = min(100, $limit ?: 25);
738
-
739
-		$connection = \OC::$server->getDatabaseConnection();
740
-		$queryBuilder = $connection->getQueryBuilder();
741
-		$queryBuilder->select('pref_login.userid')
742
-			->from('preferences', 'pref_login')
743
-			->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
744
-			->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
745
-			->setFirstResult($offset)
746
-			->setMaxResults($limit)
747
-		;
748
-
749
-		// Oracle don't want to run ORDER BY on CLOB column
750
-		$loginOrder = $connection->getDatabasePlatform() instanceof OraclePlatform
751
-			? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
752
-			: 'pref_login.configvalue';
753
-		$queryBuilder
754
-			->orderBy($loginOrder, 'DESC')
755
-			->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
756
-
757
-		if ($search !== '') {
758
-			$displayNameMatches = $this->searchDisplayName($search);
759
-			$matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
760
-
761
-			$queryBuilder
762
-				->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
763
-					$queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
764
-					$queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
765
-					$queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
766
-				))
767
-				->andWhere($queryBuilder->expr()->orX(
768
-					$queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
769
-				));
770
-		}
771
-
772
-		/** @var list<string> */
773
-		$list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
774
-
775
-		return $list;
776
-	}
777
-
778
-	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
779
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
780
-
781
-		if (\in_array($uid, [
782
-			'.htaccess',
783
-			'files_external',
784
-			'__groupfolders',
785
-			'.ncdata',
786
-			'owncloud.log',
787
-			'nextcloud.log',
788
-			'updater.log',
789
-			'audit.log',
790
-			$appdata], true)) {
791
-			return false;
792
-		}
793
-
794
-		if (!$checkDataDirectory) {
795
-			return true;
796
-		}
797
-
798
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
799
-
800
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
801
-	}
802
-
803
-	public function getDisplayNameCache(): DisplayNameCache {
804
-		return $this->displayNameCache;
805
-	}
806
-
807
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
808
-		$maxBatchSize = 1000;
809
-
810
-		do {
811
-			if ($limit !== null) {
812
-				$batchSize = min($limit, $maxBatchSize);
813
-				$limit -= $batchSize;
814
-			} else {
815
-				$batchSize = $maxBatchSize;
816
-			}
817
-
818
-			$userIds = $this->getSeenUserIds($batchSize, $offset);
819
-			$offset += $batchSize;
820
-
821
-			foreach ($userIds as $userId) {
822
-				foreach ($this->backends as $backend) {
823
-					if ($backend->userExists($userId)) {
824
-						$user = new LazyUser($userId, $this, null, $backend);
825
-						yield $user;
826
-						break;
827
-					}
828
-				}
829
-			}
830
-		} while (count($userIds) === $batchSize && $limit !== 0);
831
-	}
56
+    /**
57
+     * @var UserInterface[] $backends
58
+     */
59
+    private array $backends = [];
60
+
61
+    /**
62
+     * @var array<string,\OC\User\User> $cachedUsers
63
+     */
64
+    private array $cachedUsers = [];
65
+
66
+    private ICache $cache;
67
+
68
+    private DisplayNameCache $displayNameCache;
69
+
70
+    public function __construct(
71
+        private IConfig $config,
72
+        ICacheFactory $cacheFactory,
73
+        private IEventDispatcher $eventDispatcher,
74
+        private LoggerInterface $logger,
75
+    ) {
76
+        $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
77
+        $this->listen('\OC\User', 'postDelete', function (IUser $user): void {
78
+            unset($this->cachedUsers[$user->getUID()]);
79
+        });
80
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
81
+    }
82
+
83
+    /**
84
+     * Get the active backends
85
+     * @return UserInterface[]
86
+     */
87
+    public function getBackends(): array {
88
+        return $this->backends;
89
+    }
90
+
91
+    public function registerBackend(UserInterface $backend): void {
92
+        $this->backends[] = $backend;
93
+    }
94
+
95
+    public function removeBackend(UserInterface $backend): void {
96
+        $this->cachedUsers = [];
97
+        if (($i = array_search($backend, $this->backends)) !== false) {
98
+            unset($this->backends[$i]);
99
+        }
100
+    }
101
+
102
+    public function clearBackends(): void {
103
+        $this->cachedUsers = [];
104
+        $this->backends = [];
105
+    }
106
+
107
+    /**
108
+     * get a user by user id
109
+     *
110
+     * @param string $uid
111
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
112
+     */
113
+    public function get($uid) {
114
+        if (is_null($uid) || $uid === '' || $uid === false) {
115
+            return null;
116
+        }
117
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
118
+            return $this->cachedUsers[$uid];
119
+        }
120
+
121
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
122
+            return null;
123
+        }
124
+
125
+        $cachedBackend = $this->cache->get(sha1($uid));
126
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
127
+            // Cache has the info of the user backend already, so ask that one directly
128
+            $backend = $this->backends[$cachedBackend];
129
+            if ($backend->userExists($uid)) {
130
+                return $this->getUserObject($uid, $backend);
131
+            }
132
+        }
133
+
134
+        foreach ($this->backends as $i => $backend) {
135
+            if ($i === $cachedBackend) {
136
+                // Tried that one already
137
+                continue;
138
+            }
139
+
140
+            if ($backend->userExists($uid)) {
141
+                // Hash $uid to ensure that only valid characters are used for the cache key
142
+                $this->cache->set(sha1($uid), $i, 300);
143
+                return $this->getUserObject($uid, $backend);
144
+            }
145
+        }
146
+        return null;
147
+    }
148
+
149
+    public function getDisplayName(string $uid): ?string {
150
+        return $this->displayNameCache->getDisplayName($uid);
151
+    }
152
+
153
+    /**
154
+     * get or construct the user object
155
+     *
156
+     * @param string $uid
157
+     * @param \OCP\UserInterface $backend
158
+     * @param bool $cacheUser If false the newly created user object will not be cached
159
+     * @return \OC\User\User
160
+     */
161
+    public function getUserObject($uid, $backend, $cacheUser = true) {
162
+        if ($backend instanceof IGetRealUIDBackend) {
163
+            $uid = $backend->getRealUID($uid);
164
+        }
165
+
166
+        if (isset($this->cachedUsers[$uid])) {
167
+            return $this->cachedUsers[$uid];
168
+        }
169
+
170
+        $user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
171
+        if ($cacheUser) {
172
+            $this->cachedUsers[$uid] = $user;
173
+        }
174
+        return $user;
175
+    }
176
+
177
+    /**
178
+     * check if a user exists
179
+     *
180
+     * @param string $uid
181
+     * @return bool
182
+     */
183
+    public function userExists($uid) {
184
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
185
+            return false;
186
+        }
187
+
188
+        $user = $this->get($uid);
189
+        return ($user !== null);
190
+    }
191
+
192
+    /**
193
+     * Check if the password is valid for the user
194
+     *
195
+     * @param string $loginName
196
+     * @param string $password
197
+     * @return IUser|false the User object on success, false otherwise
198
+     */
199
+    public function checkPassword($loginName, $password) {
200
+        $result = $this->checkPasswordNoLogging($loginName, $password);
201
+
202
+        if ($result === false) {
203
+            $this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
204
+        }
205
+
206
+        return $result;
207
+    }
208
+
209
+    /**
210
+     * Check if the password is valid for the user
211
+     *
212
+     * @internal
213
+     * @param string $loginName
214
+     * @param string $password
215
+     * @return IUser|false the User object on success, false otherwise
216
+     */
217
+    public function checkPasswordNoLogging($loginName, $password) {
218
+        $loginName = str_replace("\0", '', $loginName);
219
+        $password = str_replace("\0", '', $password);
220
+
221
+        $cachedBackend = $this->cache->get($loginName);
222
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
223
+            $backends = [$this->backends[$cachedBackend]];
224
+        } else {
225
+            $backends = $this->backends;
226
+        }
227
+        foreach ($backends as $backend) {
228
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
229
+                /** @var ICheckPasswordBackend $backend */
230
+                $uid = $backend->checkPassword($loginName, $password);
231
+                if ($uid !== false) {
232
+                    return $this->getUserObject($uid, $backend);
233
+                }
234
+            }
235
+        }
236
+
237
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
238
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
239
+        // to contain urlencoded patterns by "accident".
240
+        $password = urldecode($password);
241
+
242
+        foreach ($backends as $backend) {
243
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
244
+                /** @var ICheckPasswordBackend|UserInterface $backend */
245
+                $uid = $backend->checkPassword($loginName, $password);
246
+                if ($uid !== false) {
247
+                    return $this->getUserObject($uid, $backend);
248
+                }
249
+            }
250
+        }
251
+
252
+        return false;
253
+    }
254
+
255
+    public function search($pattern, $limit = null, $offset = null) {
256
+        $users = [];
257
+        foreach ($this->backends as $backend) {
258
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
259
+            if (is_array($backendUsers)) {
260
+                foreach ($backendUsers as $uid) {
261
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
262
+                }
263
+            }
264
+        }
265
+
266
+        uasort($users, function (IUser $a, IUser $b) {
267
+            return strcasecmp($a->getUID(), $b->getUID());
268
+        });
269
+        return $users;
270
+    }
271
+
272
+    /**
273
+     * Search by displayName
274
+     *
275
+     * @param string $pattern
276
+     * @param int $limit
277
+     * @param int $offset
278
+     * @return IUser[]
279
+     */
280
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
281
+        $users = [];
282
+        foreach ($this->backends as $backend) {
283
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
284
+            if (is_array($backendUsers)) {
285
+                foreach ($backendUsers as $uid => $displayName) {
286
+                    $users[] = new LazyUser($uid, $this, $displayName, $backend);
287
+                }
288
+            }
289
+        }
290
+
291
+        usort($users, function (IUser $a, IUser $b) {
292
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
293
+        });
294
+        return $users;
295
+    }
296
+
297
+    /**
298
+     * @return IUser[]
299
+     */
300
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
301
+        $users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
302
+        $users = array_combine(
303
+            $users,
304
+            array_map(
305
+                fn (string $uid): IUser => new LazyUser($uid, $this),
306
+                $users
307
+            )
308
+        );
309
+        if ($search !== '') {
310
+            $users = array_filter(
311
+                $users,
312
+                function (IUser $user) use ($search): bool {
313
+                    try {
314
+                        return mb_stripos($user->getUID(), $search) !== false
315
+                        || mb_stripos($user->getDisplayName(), $search) !== false
316
+                        || mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
317
+                    } catch (NoUserException $ex) {
318
+                        $this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
319
+                        return false;
320
+                    }
321
+                });
322
+        }
323
+
324
+        $tempLimit = ($limit === null ? null : $limit + $offset);
325
+        foreach ($this->backends as $backend) {
326
+            if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
327
+                break;
328
+            }
329
+            if ($backend instanceof IProvideEnabledStateBackend) {
330
+                $backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
331
+                foreach ($backendUsers as $uid) {
332
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
333
+                }
334
+            }
335
+        }
336
+
337
+        return array_slice($users, $offset, $limit);
338
+    }
339
+
340
+    /**
341
+     * Search known users (from phonebook sync) by displayName
342
+     *
343
+     * @param string $searcher
344
+     * @param string $pattern
345
+     * @param int|null $limit
346
+     * @param int|null $offset
347
+     * @return IUser[]
348
+     */
349
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
350
+        $users = [];
351
+        foreach ($this->backends as $backend) {
352
+            if ($backend instanceof ISearchKnownUsersBackend) {
353
+                $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
354
+            } else {
355
+                // Better than nothing, but filtering after pagination can remove lots of results.
356
+                $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
357
+            }
358
+            if (is_array($backendUsers)) {
359
+                foreach ($backendUsers as $uid => $displayName) {
360
+                    $users[] = $this->getUserObject($uid, $backend);
361
+                }
362
+            }
363
+        }
364
+
365
+        usort($users, function ($a, $b) {
366
+            /**
367
+             * @var IUser $a
368
+             * @var IUser $b
369
+             */
370
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
371
+        });
372
+        return $users;
373
+    }
374
+
375
+    /**
376
+     * @param string $uid
377
+     * @param string $password
378
+     * @return false|IUser the created user or false
379
+     * @throws \InvalidArgumentException
380
+     * @throws HintException
381
+     */
382
+    public function createUser($uid, $password) {
383
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
384
+        /** @var IAssertion $assertion */
385
+        $assertion = \OC::$server->get(IAssertion::class);
386
+        $assertion->createUserIsLegit();
387
+
388
+        $localBackends = [];
389
+        foreach ($this->backends as $backend) {
390
+            if ($backend instanceof Database) {
391
+                // First check if there is another user backend
392
+                $localBackends[] = $backend;
393
+                continue;
394
+            }
395
+
396
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
397
+                return $this->createUserFromBackend($uid, $password, $backend);
398
+            }
399
+        }
400
+
401
+        foreach ($localBackends as $backend) {
402
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
403
+                return $this->createUserFromBackend($uid, $password, $backend);
404
+            }
405
+        }
406
+
407
+        return false;
408
+    }
409
+
410
+    /**
411
+     * @param string $uid
412
+     * @param string $password
413
+     * @param UserInterface $backend
414
+     * @return IUser|false
415
+     * @throws \InvalidArgumentException
416
+     */
417
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
418
+        $l = \OCP\Util::getL10N('lib');
419
+
420
+        $this->validateUserId($uid, true);
421
+
422
+        // No empty password
423
+        if (trim($password) === '') {
424
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
425
+        }
426
+
427
+        // Check if user already exists
428
+        if ($this->userExists($uid)) {
429
+            throw new \InvalidArgumentException($l->t('The Login is already being used'));
430
+        }
431
+
432
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
433
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
434
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
435
+        $state = $backend->createUser($uid, $password);
436
+        if ($state === false) {
437
+            throw new \InvalidArgumentException($l->t('Could not create account'));
438
+        }
439
+        $user = $this->getUserObject($uid, $backend);
440
+        if ($user instanceof IUser) {
441
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
442
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
443
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
444
+            return $user;
445
+        }
446
+        return false;
447
+    }
448
+
449
+    /**
450
+     * returns how many users per backend exist (if supported by backend)
451
+     *
452
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
453
+     *                             entry in the preferences table will be affected
454
+     * @return array<string, int> an array of backend class as key and count number as value
455
+     */
456
+    public function countUsers() {
457
+        $userCountStatistics = [];
458
+        foreach ($this->backends as $backend) {
459
+            if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
460
+                /** @var ICountUsersBackend|IUserBackend $backend */
461
+                $backendUsers = $backend->countUsers();
462
+                if ($backendUsers !== false) {
463
+                    if ($backend instanceof IUserBackend) {
464
+                        $name = $backend->getBackendName();
465
+                    } else {
466
+                        $name = get_class($backend);
467
+                    }
468
+                    if (isset($userCountStatistics[$name])) {
469
+                        $userCountStatistics[$name] += $backendUsers;
470
+                    } else {
471
+                        $userCountStatistics[$name] = $backendUsers;
472
+                    }
473
+                }
474
+            }
475
+        }
476
+        return $userCountStatistics;
477
+    }
478
+
479
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
480
+        $userCount = false;
481
+
482
+        foreach ($this->backends as $backend) {
483
+            if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
484
+                $backendUsers = $backend->countMappedUsers();
485
+            } elseif ($backend instanceof ILimitAwareCountUsersBackend) {
486
+                $backendUsers = $backend->countUsers($limit);
487
+            } elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
488
+                /** @var ICountUsersBackend $backend */
489
+                $backendUsers = $backend->countUsers();
490
+            } else {
491
+                $this->logger->debug('Skip backend for user count: ' . get_class($backend));
492
+                continue;
493
+            }
494
+            if ($backendUsers !== false) {
495
+                $userCount = (int)$userCount + $backendUsers;
496
+                if ($limit > 0) {
497
+                    if ($userCount >= $limit) {
498
+                        break;
499
+                    }
500
+                    $limit -= $userCount;
501
+                }
502
+            } else {
503
+                $this->logger->warning('Can not determine user count for ' . get_class($backend));
504
+            }
505
+        }
506
+        return $userCount;
507
+    }
508
+
509
+    /**
510
+     * returns how many users per backend exist in the requested groups (if supported by backend)
511
+     *
512
+     * @param IGroup[] $groups an array of groups to search in
513
+     * @param int $limit limit to stop counting
514
+     * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users
515
+     */
516
+    public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
517
+        $users = [];
518
+        $disabled = [];
519
+        foreach ($groups as $group) {
520
+            foreach ($group->getUsers() as $user) {
521
+                $users[$user->getUID()] = 1;
522
+                if (!$user->isEnabled()) {
523
+                    $disabled[$user->getUID()] = 1;
524
+                }
525
+                if (count($users) >= $limit) {
526
+                    return [count($users),-1];
527
+                }
528
+            }
529
+        }
530
+        return [count($users),count($disabled)];
531
+    }
532
+
533
+    /**
534
+     * The callback is executed for each user on each backend.
535
+     * If the callback returns false no further users will be retrieved.
536
+     *
537
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
538
+     * @param string $search
539
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
540
+     *                          in the preferences table will be affected
541
+     * @since 9.0.0
542
+     */
543
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
544
+        if ($onlySeen) {
545
+            $this->callForSeenUsers($callback);
546
+        } else {
547
+            foreach ($this->getBackends() as $backend) {
548
+                $limit = 500;
549
+                $offset = 0;
550
+                do {
551
+                    $users = $backend->getUsers($search, $limit, $offset);
552
+                    foreach ($users as $uid) {
553
+                        if (!$backend->userExists($uid)) {
554
+                            continue;
555
+                        }
556
+                        $user = $this->getUserObject($uid, $backend, false);
557
+                        $return = $callback($user);
558
+                        if ($return === false) {
559
+                            break;
560
+                        }
561
+                    }
562
+                    $offset += $limit;
563
+                } while (count($users) >= $limit);
564
+            }
565
+        }
566
+    }
567
+
568
+    /**
569
+     * returns how many users are disabled
570
+     *
571
+     * @return int
572
+     * @since 12.0.0
573
+     */
574
+    public function countDisabledUsers(): int {
575
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
576
+        $queryBuilder->select($queryBuilder->func()->count('*'))
577
+            ->from('preferences')
578
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
579
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
580
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
581
+
582
+
583
+        $result = $queryBuilder->executeQuery();
584
+        $count = $result->fetchOne();
585
+        $result->closeCursor();
586
+
587
+        if ($count !== false) {
588
+            $count = (int)$count;
589
+        } else {
590
+            $count = 0;
591
+        }
592
+
593
+        return $count;
594
+    }
595
+
596
+    /**
597
+     * returns how many users have logged in once
598
+     *
599
+     * @return int
600
+     * @since 11.0.0
601
+     */
602
+    public function countSeenUsers() {
603
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
604
+        $queryBuilder->select($queryBuilder->func()->count('*'))
605
+            ->from('preferences')
606
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
607
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
608
+
609
+        $query = $queryBuilder->executeQuery();
610
+
611
+        $result = (int)$query->fetchOne();
612
+        $query->closeCursor();
613
+
614
+        return $result;
615
+    }
616
+
617
+    public function callForSeenUsers(\Closure $callback) {
618
+        $users = $this->getSeenUsers();
619
+        foreach ($users as $user) {
620
+            $return = $callback($user);
621
+            if ($return === false) {
622
+                return;
623
+            }
624
+        }
625
+    }
626
+
627
+    /**
628
+     * Getting all userIds that have a lastLogin value requires checking the
629
+     * value in php because on oracle you cannot use a clob in a where clause,
630
+     * preventing us from doing a not null or length(value) > 0 check.
631
+     *
632
+     * @param int $limit
633
+     * @param int $offset
634
+     * @return string[] with user ids
635
+     */
636
+    private function getSeenUserIds($limit = null, $offset = null) {
637
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
638
+        $queryBuilder->select(['userid'])
639
+            ->from('preferences')
640
+            ->where($queryBuilder->expr()->eq(
641
+                'appid', $queryBuilder->createNamedParameter('login'))
642
+            )
643
+            ->andWhere($queryBuilder->expr()->eq(
644
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
645
+            )
646
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
647
+            );
648
+
649
+        if ($limit !== null) {
650
+            $queryBuilder->setMaxResults($limit);
651
+        }
652
+        if ($offset !== null) {
653
+            $queryBuilder->setFirstResult($offset);
654
+        }
655
+        $query = $queryBuilder->executeQuery();
656
+        $result = [];
657
+
658
+        while ($row = $query->fetch()) {
659
+            $result[] = $row['userid'];
660
+        }
661
+
662
+        $query->closeCursor();
663
+
664
+        return $result;
665
+    }
666
+
667
+    /**
668
+     * @param string $email
669
+     * @return IUser[]
670
+     * @since 9.1.0
671
+     */
672
+    public function getByEmail($email) {
673
+        // looking for 'email' only (and not primary_mail) is intentional
674
+        $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
675
+
676
+        $users = array_map(function ($uid) {
677
+            return $this->get($uid);
678
+        }, $userIds);
679
+
680
+        return array_values(array_filter($users, function ($u) {
681
+            return ($u instanceof IUser);
682
+        }));
683
+    }
684
+
685
+    /**
686
+     * @param string $uid
687
+     * @param bool $checkDataDirectory
688
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
689
+     * @since 26.0.0
690
+     */
691
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
692
+        $l = Server::get(IFactory::class)->get('lib');
693
+
694
+        // Check the ID for bad characters
695
+        // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
696
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
697
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
698
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
699
+        }
700
+
701
+        // No empty user ID
702
+        if (trim($uid) === '') {
703
+            throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
704
+        }
705
+
706
+        // No whitespace at the beginning or at the end
707
+        if (trim($uid) !== $uid) {
708
+            throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
709
+        }
710
+
711
+        // User ID only consists of 1 or 2 dots (directory traversal)
712
+        if ($uid === '.' || $uid === '..') {
713
+            throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
714
+        }
715
+
716
+        // User ID is too long
717
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
718
+            // TRANSLATORS User ID is too long
719
+            throw new \InvalidArgumentException($l->t('Username is too long'));
720
+        }
721
+
722
+        if (!$this->verifyUid($uid, $checkDataDirectory)) {
723
+            throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
724
+        }
725
+    }
726
+
727
+    /**
728
+     * Gets the list of user ids sorted by lastLogin, from most recent to least recent
729
+     *
730
+     * @param int|null $limit how many users to fetch (default: 25, max: 100)
731
+     * @param int $offset from which offset to fetch
732
+     * @param string $search search users based on search params
733
+     * @return list<string> list of user IDs
734
+     */
735
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
736
+        // We can't load all users who already logged in
737
+        $limit = min(100, $limit ?: 25);
738
+
739
+        $connection = \OC::$server->getDatabaseConnection();
740
+        $queryBuilder = $connection->getQueryBuilder();
741
+        $queryBuilder->select('pref_login.userid')
742
+            ->from('preferences', 'pref_login')
743
+            ->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
744
+            ->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
745
+            ->setFirstResult($offset)
746
+            ->setMaxResults($limit)
747
+        ;
748
+
749
+        // Oracle don't want to run ORDER BY on CLOB column
750
+        $loginOrder = $connection->getDatabasePlatform() instanceof OraclePlatform
751
+            ? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
752
+            : 'pref_login.configvalue';
753
+        $queryBuilder
754
+            ->orderBy($loginOrder, 'DESC')
755
+            ->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
756
+
757
+        if ($search !== '') {
758
+            $displayNameMatches = $this->searchDisplayName($search);
759
+            $matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
760
+
761
+            $queryBuilder
762
+                ->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
763
+                    $queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
764
+                    $queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
765
+                    $queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
766
+                ))
767
+                ->andWhere($queryBuilder->expr()->orX(
768
+                    $queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
769
+                ));
770
+        }
771
+
772
+        /** @var list<string> */
773
+        $list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
774
+
775
+        return $list;
776
+    }
777
+
778
+    private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
779
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
780
+
781
+        if (\in_array($uid, [
782
+            '.htaccess',
783
+            'files_external',
784
+            '__groupfolders',
785
+            '.ncdata',
786
+            'owncloud.log',
787
+            'nextcloud.log',
788
+            'updater.log',
789
+            'audit.log',
790
+            $appdata], true)) {
791
+            return false;
792
+        }
793
+
794
+        if (!$checkDataDirectory) {
795
+            return true;
796
+        }
797
+
798
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
799
+
800
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
801
+    }
802
+
803
+    public function getDisplayNameCache(): DisplayNameCache {
804
+        return $this->displayNameCache;
805
+    }
806
+
807
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
808
+        $maxBatchSize = 1000;
809
+
810
+        do {
811
+            if ($limit !== null) {
812
+                $batchSize = min($limit, $maxBatchSize);
813
+                $limit -= $batchSize;
814
+            } else {
815
+                $batchSize = $maxBatchSize;
816
+            }
817
+
818
+            $userIds = $this->getSeenUserIds($batchSize, $offset);
819
+            $offset += $batchSize;
820
+
821
+            foreach ($userIds as $userId) {
822
+                foreach ($this->backends as $backend) {
823
+                    if ($backend->userExists($userId)) {
824
+                        $user = new LazyUser($userId, $this, null, $backend);
825
+                        yield $user;
826
+                        break;
827
+                    }
828
+                }
829
+            }
830
+        } while (count($userIds) === $batchSize && $limit !== 0);
831
+    }
832 832
 }
Please login to merge, or discard this patch.