Completed
Push — master ( dc8799...fb4a06 )
by
unknown
19:35 queued 14s
created
lib/public/IUser.php 2 patches
Indentation   +299 added lines, -299 removed lines patch added patch discarded remove patch
@@ -15,303 +15,303 @@
 block discarded – undo
15 15
  * @since 8.0.0
16 16
  */
17 17
 interface IUser {
18
-	/**
19
-	 * @since 32.0.0
20
-	 */
21
-	public const MAX_USERID_LENGTH = 64;
22
-
23
-	/**
24
-	 * get the user id
25
-	 *
26
-	 * @return string
27
-	 * @since 8.0.0
28
-	 */
29
-	public function getUID();
30
-
31
-	/**
32
-	 * get the display name for the user, if no specific display name is set it will fallback to the user id
33
-	 *
34
-	 * @return string
35
-	 * @since 8.0.0
36
-	 */
37
-	public function getDisplayName();
38
-
39
-	/**
40
-	 * set the display name for the user
41
-	 *
42
-	 * @param string $displayName
43
-	 * @return bool
44
-	 * @since 8.0.0
45
-	 *
46
-	 * @since 25.0.0 Throw InvalidArgumentException
47
-	 * @throws \InvalidArgumentException
48
-	 */
49
-	public function setDisplayName($displayName);
50
-
51
-	/**
52
-	 * returns the timestamp of the user's last login or 0 if the user did never
53
-	 * login
54
-	 *
55
-	 * @return int
56
-	 * @since 8.0.0
57
-	 */
58
-	public function getLastLogin(): int;
59
-
60
-	/**
61
-	 * Returns the timestamp of the user's first login, 0 if the user did never login, or -1 if the data is unknown (first login was on an older version)
62
-	 *
63
-	 * @since 31.0.0
64
-	 */
65
-	public function getFirstLogin(): int;
66
-
67
-	/**
68
-	 * Updates the timestamp of the most recent login of this user (and first login if needed)
69
-	 *
70
-	 * @return bool whether this is the first login
71
-	 * @since 8.0.0
72
-	 */
73
-	public function updateLastLoginTimestamp(): bool;
74
-
75
-	/**
76
-	 * Delete the user
77
-	 *
78
-	 * @return bool
79
-	 * @since 8.0.0
80
-	 */
81
-	public function delete();
82
-
83
-	/**
84
-	 * Set the password of the user
85
-	 *
86
-	 * @param string $password
87
-	 * @param string $recoveryPassword for the encryption app to reset encryption keys
88
-	 * @return bool
89
-	 * @since 8.0.0
90
-	 */
91
-	public function setPassword($password, $recoveryPassword = null);
92
-
93
-	/**
94
-	 * Get the password hash of the user
95
-	 *
96
-	 * @return ?string the password hash hashed by `\OCP\Security\IHasher::hash()`
97
-	 * @since 30.0.0
98
-	 */
99
-	public function getPasswordHash(): ?string;
100
-
101
-	/**
102
-	 * Set the password hash of the user
103
-	 *
104
-	 * @param string $passwordHash the password hash hashed by `\OCP\Security\IHasher::hash()`
105
-	 * @throws InvalidArgumentException when `$passwordHash` is not a valid hash
106
-	 * @since 30.0.0
107
-	 */
108
-	public function setPasswordHash(string $passwordHash): bool;
109
-
110
-	/**
111
-	 * get the users home folder to mount
112
-	 *
113
-	 * @return string
114
-	 * @since 8.0.0
115
-	 */
116
-	public function getHome();
117
-
118
-	/**
119
-	 * Get the name of the backend class the user is connected with
120
-	 *
121
-	 * @return string
122
-	 * @since 8.0.0
123
-	 */
124
-	public function getBackendClassName();
125
-
126
-	/**
127
-	 * Get the backend for the current user object
128
-	 * @return ?UserInterface
129
-	 * @since 15.0.0
130
-	 */
131
-	public function getBackend();
132
-
133
-	/**
134
-	 * check if the backend allows the user to change their avatar on Personal page
135
-	 *
136
-	 * @return bool
137
-	 * @since 8.0.0
138
-	 */
139
-	public function canChangeAvatar();
140
-
141
-	/**
142
-	 * check if the backend supports changing passwords
143
-	 *
144
-	 * @return bool
145
-	 * @since 8.0.0
146
-	 */
147
-	public function canChangePassword();
148
-
149
-	/**
150
-	 * check if the backend supports changing display names
151
-	 *
152
-	 * @return bool
153
-	 * @since 8.0.0
154
-	 */
155
-	public function canChangeDisplayName();
156
-
157
-	/**
158
-	 * Check if the backend supports changing email
159
-	 *
160
-	 * @since 32.0.0
161
-	 */
162
-	public function canChangeEmail(): bool;
163
-
164
-	/**
165
-	 * check if the user is enabled
166
-	 *
167
-	 * @return bool
168
-	 * @since 8.0.0
169
-	 */
170
-	public function isEnabled();
171
-
172
-	/**
173
-	 * set the enabled status for the user
174
-	 *
175
-	 * @param bool $enabled
176
-	 * @since 8.0.0
177
-	 */
178
-	public function setEnabled(bool $enabled = true);
179
-
180
-	/**
181
-	 * get the user's email address
182
-	 *
183
-	 * @return string|null
184
-	 * @since 9.0.0
185
-	 */
186
-	public function getEMailAddress();
187
-
188
-	/**
189
-	 * get the user's system email address
190
-	 *
191
-	 * The system mail address may be read only and may be set from different
192
-	 * sources like LDAP, SAML or simply the admin.
193
-	 *
194
-	 * Use this getter only when the system address is needed. For picking the
195
-	 * proper address to e.g. send a mail to, use getEMailAddress().
196
-	 *
197
-	 * @return string|null
198
-	 * @since 23.0.0
199
-	 */
200
-	public function getSystemEMailAddress(): ?string;
201
-
202
-	/**
203
-	 * get the user's preferred email address
204
-	 *
205
-	 * The primary mail address may be set be the user to specify a different
206
-	 * email address where mails by Nextcloud are sent to. It is not necessarily
207
-	 * set.
208
-	 *
209
-	 * Use this getter only when the primary address is needed. For picking the
210
-	 * proper address to e.g. send a mail to, use getEMailAddress().
211
-	 *
212
-	 * @return string|null
213
-	 * @since 23.0.0
214
-	 */
215
-	public function getPrimaryEMailAddress(): ?string;
216
-
217
-	/**
218
-	 * get the avatar image if it exists
219
-	 *
220
-	 * @param int $size
221
-	 * @return IImage|null
222
-	 * @since 9.0.0
223
-	 */
224
-	public function getAvatarImage($size);
225
-
226
-	/**
227
-	 * get the federation cloud id
228
-	 *
229
-	 * @return string
230
-	 * @since 9.0.0
231
-	 */
232
-	public function getCloudId();
233
-
234
-	/**
235
-	 * set the email address of the user
236
-	 *
237
-	 * It is an alias to setSystemEMailAddress()
238
-	 *
239
-	 * @param string|null $mailAddress
240
-	 * @return void
241
-	 * @since 9.0.0
242
-	 * @deprecated 23.0.0 use setSystemEMailAddress() or setPrimaryEMailAddress()
243
-	 */
244
-	public function setEMailAddress($mailAddress);
245
-
246
-	/**
247
-	 * Set the system email address of the user
248
-	 *
249
-	 * This is supposed to be used when the email is set from different sources
250
-	 * (i.e. other user backends, admin).
251
-	 *
252
-	 * @since 23.0.0
253
-	 */
254
-	public function setSystemEMailAddress(string $mailAddress): void;
255
-
256
-	/**
257
-	 * Set the primary email address of the user.
258
-	 *
259
-	 * This method should be typically called when the user is changing their
260
-	 * own primary address and is not allowed to change their system email.
261
-	 *
262
-	 * The mail address provided here must be already registered as an
263
-	 * additional mail in the user account and also be verified locally. Also
264
-	 * an empty string is allowed to delete this preference.
265
-	 *
266
-	 * @throws InvalidArgumentException when the provided email address does not
267
-	 *                                  satisfy constraints.
268
-	 *
269
-	 * @since 23.0.0
270
-	 */
271
-	public function setPrimaryEMailAddress(string $mailAddress): void;
272
-
273
-	/**
274
-	 * get the users' quota in human readable form. If a specific quota is not
275
-	 * set for the user, the default value is returned. If a default setting
276
-	 * was not set otherwise, it is return as 'none', i.e. quota is not limited.
277
-	 *
278
-	 * @return string
279
-	 * @since 9.0.0
280
-	 */
281
-	public function getQuota();
282
-
283
-	/**
284
-	 * Get the users' quota in machine readable form. If a specific quota is set
285
-	 * for the user, then the quota is returned in bytes. Otherwise the default value is returned.
286
-	 * If a default setting was not set, it is return as `\OCP\Files\FileInfo::SPACE_UNLIMITED`, i.e. quota is not limited.
287
-	 *
288
-	 * @since 32.0.0
289
-	 */
290
-	public function getQuotaBytes(): int|float;
291
-
292
-	/**
293
-	 * set the users' quota
294
-	 *
295
-	 * @param string $quota
296
-	 * @return void
297
-	 * @since 9.0.0
298
-	 */
299
-	public function setQuota($quota);
300
-
301
-	/**
302
-	 * Get the user's manager UIDs
303
-	 *
304
-	 * @since 27.0.0
305
-	 * @return string[]
306
-	 */
307
-	public function getManagerUids(): array;
308
-
309
-	/**
310
-	 * Set the user's manager UIDs
311
-	 *
312
-	 * @param string[] $uids UIDs of all managers
313
-	 * @return void
314
-	 * @since 27.0.0
315
-	 */
316
-	public function setManagerUids(array $uids): void;
18
+    /**
19
+     * @since 32.0.0
20
+     */
21
+    public const MAX_USERID_LENGTH = 64;
22
+
23
+    /**
24
+     * get the user id
25
+     *
26
+     * @return string
27
+     * @since 8.0.0
28
+     */
29
+    public function getUID();
30
+
31
+    /**
32
+     * get the display name for the user, if no specific display name is set it will fallback to the user id
33
+     *
34
+     * @return string
35
+     * @since 8.0.0
36
+     */
37
+    public function getDisplayName();
38
+
39
+    /**
40
+     * set the display name for the user
41
+     *
42
+     * @param string $displayName
43
+     * @return bool
44
+     * @since 8.0.0
45
+     *
46
+     * @since 25.0.0 Throw InvalidArgumentException
47
+     * @throws \InvalidArgumentException
48
+     */
49
+    public function setDisplayName($displayName);
50
+
51
+    /**
52
+     * returns the timestamp of the user's last login or 0 if the user did never
53
+     * login
54
+     *
55
+     * @return int
56
+     * @since 8.0.0
57
+     */
58
+    public function getLastLogin(): int;
59
+
60
+    /**
61
+     * Returns the timestamp of the user's first login, 0 if the user did never login, or -1 if the data is unknown (first login was on an older version)
62
+     *
63
+     * @since 31.0.0
64
+     */
65
+    public function getFirstLogin(): int;
66
+
67
+    /**
68
+     * Updates the timestamp of the most recent login of this user (and first login if needed)
69
+     *
70
+     * @return bool whether this is the first login
71
+     * @since 8.0.0
72
+     */
73
+    public function updateLastLoginTimestamp(): bool;
74
+
75
+    /**
76
+     * Delete the user
77
+     *
78
+     * @return bool
79
+     * @since 8.0.0
80
+     */
81
+    public function delete();
82
+
83
+    /**
84
+     * Set the password of the user
85
+     *
86
+     * @param string $password
87
+     * @param string $recoveryPassword for the encryption app to reset encryption keys
88
+     * @return bool
89
+     * @since 8.0.0
90
+     */
91
+    public function setPassword($password, $recoveryPassword = null);
92
+
93
+    /**
94
+     * Get the password hash of the user
95
+     *
96
+     * @return ?string the password hash hashed by `\OCP\Security\IHasher::hash()`
97
+     * @since 30.0.0
98
+     */
99
+    public function getPasswordHash(): ?string;
100
+
101
+    /**
102
+     * Set the password hash of the user
103
+     *
104
+     * @param string $passwordHash the password hash hashed by `\OCP\Security\IHasher::hash()`
105
+     * @throws InvalidArgumentException when `$passwordHash` is not a valid hash
106
+     * @since 30.0.0
107
+     */
108
+    public function setPasswordHash(string $passwordHash): bool;
109
+
110
+    /**
111
+     * get the users home folder to mount
112
+     *
113
+     * @return string
114
+     * @since 8.0.0
115
+     */
116
+    public function getHome();
117
+
118
+    /**
119
+     * Get the name of the backend class the user is connected with
120
+     *
121
+     * @return string
122
+     * @since 8.0.0
123
+     */
124
+    public function getBackendClassName();
125
+
126
+    /**
127
+     * Get the backend for the current user object
128
+     * @return ?UserInterface
129
+     * @since 15.0.0
130
+     */
131
+    public function getBackend();
132
+
133
+    /**
134
+     * check if the backend allows the user to change their avatar on Personal page
135
+     *
136
+     * @return bool
137
+     * @since 8.0.0
138
+     */
139
+    public function canChangeAvatar();
140
+
141
+    /**
142
+     * check if the backend supports changing passwords
143
+     *
144
+     * @return bool
145
+     * @since 8.0.0
146
+     */
147
+    public function canChangePassword();
148
+
149
+    /**
150
+     * check if the backend supports changing display names
151
+     *
152
+     * @return bool
153
+     * @since 8.0.0
154
+     */
155
+    public function canChangeDisplayName();
156
+
157
+    /**
158
+     * Check if the backend supports changing email
159
+     *
160
+     * @since 32.0.0
161
+     */
162
+    public function canChangeEmail(): bool;
163
+
164
+    /**
165
+     * check if the user is enabled
166
+     *
167
+     * @return bool
168
+     * @since 8.0.0
169
+     */
170
+    public function isEnabled();
171
+
172
+    /**
173
+     * set the enabled status for the user
174
+     *
175
+     * @param bool $enabled
176
+     * @since 8.0.0
177
+     */
178
+    public function setEnabled(bool $enabled = true);
179
+
180
+    /**
181
+     * get the user's email address
182
+     *
183
+     * @return string|null
184
+     * @since 9.0.0
185
+     */
186
+    public function getEMailAddress();
187
+
188
+    /**
189
+     * get the user's system email address
190
+     *
191
+     * The system mail address may be read only and may be set from different
192
+     * sources like LDAP, SAML or simply the admin.
193
+     *
194
+     * Use this getter only when the system address is needed. For picking the
195
+     * proper address to e.g. send a mail to, use getEMailAddress().
196
+     *
197
+     * @return string|null
198
+     * @since 23.0.0
199
+     */
200
+    public function getSystemEMailAddress(): ?string;
201
+
202
+    /**
203
+     * get the user's preferred email address
204
+     *
205
+     * The primary mail address may be set be the user to specify a different
206
+     * email address where mails by Nextcloud are sent to. It is not necessarily
207
+     * set.
208
+     *
209
+     * Use this getter only when the primary address is needed. For picking the
210
+     * proper address to e.g. send a mail to, use getEMailAddress().
211
+     *
212
+     * @return string|null
213
+     * @since 23.0.0
214
+     */
215
+    public function getPrimaryEMailAddress(): ?string;
216
+
217
+    /**
218
+     * get the avatar image if it exists
219
+     *
220
+     * @param int $size
221
+     * @return IImage|null
222
+     * @since 9.0.0
223
+     */
224
+    public function getAvatarImage($size);
225
+
226
+    /**
227
+     * get the federation cloud id
228
+     *
229
+     * @return string
230
+     * @since 9.0.0
231
+     */
232
+    public function getCloudId();
233
+
234
+    /**
235
+     * set the email address of the user
236
+     *
237
+     * It is an alias to setSystemEMailAddress()
238
+     *
239
+     * @param string|null $mailAddress
240
+     * @return void
241
+     * @since 9.0.0
242
+     * @deprecated 23.0.0 use setSystemEMailAddress() or setPrimaryEMailAddress()
243
+     */
244
+    public function setEMailAddress($mailAddress);
245
+
246
+    /**
247
+     * Set the system email address of the user
248
+     *
249
+     * This is supposed to be used when the email is set from different sources
250
+     * (i.e. other user backends, admin).
251
+     *
252
+     * @since 23.0.0
253
+     */
254
+    public function setSystemEMailAddress(string $mailAddress): void;
255
+
256
+    /**
257
+     * Set the primary email address of the user.
258
+     *
259
+     * This method should be typically called when the user is changing their
260
+     * own primary address and is not allowed to change their system email.
261
+     *
262
+     * The mail address provided here must be already registered as an
263
+     * additional mail in the user account and also be verified locally. Also
264
+     * an empty string is allowed to delete this preference.
265
+     *
266
+     * @throws InvalidArgumentException when the provided email address does not
267
+     *                                  satisfy constraints.
268
+     *
269
+     * @since 23.0.0
270
+     */
271
+    public function setPrimaryEMailAddress(string $mailAddress): void;
272
+
273
+    /**
274
+     * get the users' quota in human readable form. If a specific quota is not
275
+     * set for the user, the default value is returned. If a default setting
276
+     * was not set otherwise, it is return as 'none', i.e. quota is not limited.
277
+     *
278
+     * @return string
279
+     * @since 9.0.0
280
+     */
281
+    public function getQuota();
282
+
283
+    /**
284
+     * Get the users' quota in machine readable form. If a specific quota is set
285
+     * for the user, then the quota is returned in bytes. Otherwise the default value is returned.
286
+     * If a default setting was not set, it is return as `\OCP\Files\FileInfo::SPACE_UNLIMITED`, i.e. quota is not limited.
287
+     *
288
+     * @since 32.0.0
289
+     */
290
+    public function getQuotaBytes(): int|float;
291
+
292
+    /**
293
+     * set the users' quota
294
+     *
295
+     * @param string $quota
296
+     * @return void
297
+     * @since 9.0.0
298
+     */
299
+    public function setQuota($quota);
300
+
301
+    /**
302
+     * Get the user's manager UIDs
303
+     *
304
+     * @since 27.0.0
305
+     * @return string[]
306
+     */
307
+    public function getManagerUids(): array;
308
+
309
+    /**
310
+     * Set the user's manager UIDs
311
+     *
312
+     * @param string[] $uids UIDs of all managers
313
+     * @return void
314
+     * @since 27.0.0
315
+     */
316
+    public function setManagerUids(array $uids): void;
317 317
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -287,7 +287,7 @@
 block discarded – undo
287 287
 	 *
288 288
 	 * @since 32.0.0
289 289
 	 */
290
-	public function getQuotaBytes(): int|float;
290
+	public function getQuotaBytes(): int | float;
291 291
 
292 292
 	/**
293 293
 	 * set the users' quota
Please login to merge, or discard this patch.
lib/private/legacy/OC_Helper.php 1 patch
Indentation   +385 added lines, -385 removed lines patch added patch discarded remove patch
@@ -32,392 +32,392 @@
 block discarded – undo
32 32
  * }
33 33
  */
34 34
 class OC_Helper {
35
-	private static $templateManager;
36
-	private static ?ICacheFactory $cacheFactory = null;
37
-	private static ?bool $quotaIncludeExternalStorage = null;
38
-
39
-	/**
40
-	 * Recursive copying of folders
41
-	 * @param string $src source folder
42
-	 * @param string $dest target folder
43
-	 * @return void
44
-	 * @deprecated 32.0.0 - use \OCP\Files\Folder::copy
45
-	 */
46
-	public static function copyr($src, $dest) {
47
-		if (!file_exists($src)) {
48
-			return;
49
-		}
50
-
51
-		if (is_dir($src)) {
52
-			if (!is_dir($dest)) {
53
-				mkdir($dest);
54
-			}
55
-			$files = scandir($src);
56
-			foreach ($files as $file) {
57
-				if ($file != '.' && $file != '..') {
58
-					self::copyr("$src/$file", "$dest/$file");
59
-				}
60
-			}
61
-		} else {
62
-			$validator = \OCP\Server::get(FilenameValidator::class);
63
-			if (!$validator->isForbidden($src)) {
64
-				copy($src, $dest);
65
-			}
66
-		}
67
-	}
68
-
69
-	/**
70
-	 * @deprecated 18.0.0
71
-	 * @return \OC\Files\Type\TemplateManager
72
-	 */
73
-	public static function getFileTemplateManager() {
74
-		if (!self::$templateManager) {
75
-			self::$templateManager = new \OC\Files\Type\TemplateManager();
76
-		}
77
-		return self::$templateManager;
78
-	}
79
-
80
-	/**
81
-	 * detect if a given program is found in the search PATH
82
-	 *
83
-	 * @param string $name
84
-	 * @param bool $path
85
-	 * @internal param string $program name
86
-	 * @internal param string $optional search path, defaults to $PATH
87
-	 * @return bool true if executable program found in path
88
-	 * @deprecated 32.0.0 use the \OCP\IBinaryFinder
89
-	 */
90
-	public static function canExecute($name, $path = false) {
91
-		// path defaults to PATH from environment if not set
92
-		if ($path === false) {
93
-			$path = getenv('PATH');
94
-		}
95
-		// we look for an executable file of that name
96
-		$exts = [''];
97
-		$check_fn = 'is_executable';
98
-		// Default check will be done with $path directories :
99
-		$dirs = explode(PATH_SEPARATOR, (string)$path);
100
-		// WARNING : We have to check if open_basedir is enabled :
101
-		$obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
102
-		if ($obd != 'none') {
103
-			$obd_values = explode(PATH_SEPARATOR, $obd);
104
-			if (count($obd_values) > 0 and $obd_values[0]) {
105
-				// open_basedir is in effect !
106
-				// We need to check if the program is in one of these dirs :
107
-				$dirs = $obd_values;
108
-			}
109
-		}
110
-		foreach ($dirs as $dir) {
111
-			foreach ($exts as $ext) {
112
-				if ($check_fn("$dir/$name" . $ext)) {
113
-					return true;
114
-				}
115
-			}
116
-		}
117
-		return false;
118
-	}
119
-
120
-	/**
121
-	 * copy the contents of one stream to another
122
-	 *
123
-	 * @param resource $source
124
-	 * @param resource $target
125
-	 * @return array the number of bytes copied and result
126
-	 * @deprecated 5.0.0 - Use \OCP\Files::streamCopy
127
-	 */
128
-	public static function streamCopy($source, $target) {
129
-		return \OCP\Files::streamCopy($source, $target, true);
130
-	}
131
-
132
-	/**
133
-	 * Adds a suffix to the name in case the file exists
134
-	 *
135
-	 * @param string $path
136
-	 * @param string $filename
137
-	 * @return string
138
-	 */
139
-	public static function buildNotExistingFileName($path, $filename) {
140
-		$view = \OC\Files\Filesystem::getView();
141
-		return self::buildNotExistingFileNameForView($path, $filename, $view);
142
-	}
143
-
144
-	/**
145
-	 * Adds a suffix to the name in case the file exists
146
-	 *
147
-	 * @param string $path
148
-	 * @param string $filename
149
-	 * @return string
150
-	 */
151
-	public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
152
-		if ($path === '/') {
153
-			$path = '';
154
-		}
155
-		if ($pos = strrpos($filename, '.')) {
156
-			$name = substr($filename, 0, $pos);
157
-			$ext = substr($filename, $pos);
158
-		} else {
159
-			$name = $filename;
160
-			$ext = '';
161
-		}
162
-
163
-		$newpath = $path . '/' . $filename;
164
-		if ($view->file_exists($newpath)) {
165
-			if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
166
-				//Replace the last "(number)" with "(number+1)"
167
-				$last_match = count($matches[0]) - 1;
168
-				$counter = $matches[1][$last_match][0] + 1;
169
-				$offset = $matches[0][$last_match][1];
170
-				$match_length = strlen($matches[0][$last_match][0]);
171
-			} else {
172
-				$counter = 2;
173
-				$match_length = 0;
174
-				$offset = false;
175
-			}
176
-			do {
177
-				if ($offset) {
178
-					//Replace the last "(number)" with "(number+1)"
179
-					$newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
180
-				} else {
181
-					$newname = $name . ' (' . $counter . ')';
182
-				}
183
-				$newpath = $path . '/' . $newname . $ext;
184
-				$counter++;
185
-			} while ($view->file_exists($newpath));
186
-		}
187
-
188
-		return $newpath;
189
-	}
190
-
191
-	/**
192
-	 * Checks if a function is available
193
-	 *
194
-	 * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead
195
-	 */
196
-	public static function is_function_enabled(string $function_name): bool {
197
-		return Util::isFunctionEnabled($function_name);
198
-	}
199
-
200
-	/**
201
-	 * Try to find a program
202
-	 * @deprecated 25.0.0 Use \OCP\IBinaryFinder directly
203
-	 */
204
-	public static function findBinaryPath(string $program): ?string {
205
-		$result = Server::get(IBinaryFinder::class)->findBinaryPath($program);
206
-		return $result !== false ? $result : null;
207
-	}
208
-
209
-	/**
210
-	 * Calculate the disc space for the given path
211
-	 *
212
-	 * BEWARE: this requires that Util::setupFS() was called
213
-	 * already !
214
-	 *
215
-	 * @param string $path
216
-	 * @param \OCP\Files\FileInfo $rootInfo (optional)
217
-	 * @param bool $includeMountPoints whether to include mount points in the size calculation
218
-	 * @param bool $useCache whether to use the cached quota values
219
-	 * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
220
-	 * @return StorageInfo
221
-	 * @throws \OCP\Files\NotFoundException
222
-	 */
223
-	public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
224
-		if (!self::$cacheFactory) {
225
-			self::$cacheFactory = Server::get(ICacheFactory::class);
226
-		}
227
-		$memcache = self::$cacheFactory->createLocal('storage_info');
228
-
229
-		// return storage info without adding mount points
230
-		if (self::$quotaIncludeExternalStorage === null) {
231
-			self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
232
-		}
233
-
234
-		$view = Filesystem::getView();
235
-		if (!$view) {
236
-			throw new \OCP\Files\NotFoundException();
237
-		}
238
-		$fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
239
-
240
-		$cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude');
241
-		if ($useCache) {
242
-			$cached = $memcache->get($cacheKey);
243
-			if ($cached) {
244
-				return $cached;
245
-			}
246
-		}
247
-
248
-		if (!$rootInfo) {
249
-			$rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false);
250
-		}
251
-		if (!$rootInfo instanceof \OCP\Files\FileInfo) {
252
-			throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing');
253
-		}
254
-		$used = $rootInfo->getSize($includeMountPoints);
255
-		if ($used < 0) {
256
-			$used = 0.0;
257
-		}
258
-		/** @var int|float $quota */
259
-		$quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
260
-		$mount = $rootInfo->getMountPoint();
261
-		$storage = $mount->getStorage();
262
-		$sourceStorage = $storage;
263
-		if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
264
-			self::$quotaIncludeExternalStorage = false;
265
-		}
266
-		if (self::$quotaIncludeExternalStorage) {
267
-			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
268
-				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
269
-			) {
270
-				/** @var \OC\Files\Storage\Home $storage */
271
-				$user = $storage->getUser();
272
-			} else {
273
-				$user = \OC::$server->getUserSession()->getUser();
274
-			}
275
-			$quota = $user?->getQuotaBytes() ?? \OCP\Files\FileInfo::SPACE_UNKNOWN;
276
-			if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
277
-				// always get free space / total space from root + mount points
278
-				return self::getGlobalStorageInfo($quota, $user, $mount);
279
-			}
280
-		}
281
-
282
-		// TODO: need a better way to get total space from storage
283
-		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
284
-			/** @var \OC\Files\Storage\Wrapper\Quota $storage */
285
-			$quota = $sourceStorage->getQuota();
286
-		}
287
-		try {
288
-			$free = $sourceStorage->free_space($rootInfo->getInternalPath());
289
-			if (is_bool($free)) {
290
-				$free = 0.0;
291
-			}
292
-		} catch (\Exception $e) {
293
-			if ($path === '') {
294
-				throw $e;
295
-			}
296
-			/** @var LoggerInterface $logger */
297
-			$logger = \OC::$server->get(LoggerInterface::class);
298
-			$logger->warning('Error while getting quota info, using root quota', ['exception' => $e]);
299
-			$rootInfo = self::getStorageInfo('');
300
-			$memcache->set($cacheKey, $rootInfo, 5 * 60);
301
-			return $rootInfo;
302
-		}
303
-		if ($free >= 0) {
304
-			$total = $free + $used;
305
-		} else {
306
-			$total = $free; //either unknown or unlimited
307
-		}
308
-		if ($total > 0) {
309
-			if ($quota > 0 && $total > $quota) {
310
-				$total = $quota;
311
-			}
312
-			// prevent division by zero or error codes (negative values)
313
-			$relative = round(($used / $total) * 10000) / 100;
314
-		} else {
315
-			$relative = 0;
316
-		}
317
-
318
-		/*
35
+    private static $templateManager;
36
+    private static ?ICacheFactory $cacheFactory = null;
37
+    private static ?bool $quotaIncludeExternalStorage = null;
38
+
39
+    /**
40
+     * Recursive copying of folders
41
+     * @param string $src source folder
42
+     * @param string $dest target folder
43
+     * @return void
44
+     * @deprecated 32.0.0 - use \OCP\Files\Folder::copy
45
+     */
46
+    public static function copyr($src, $dest) {
47
+        if (!file_exists($src)) {
48
+            return;
49
+        }
50
+
51
+        if (is_dir($src)) {
52
+            if (!is_dir($dest)) {
53
+                mkdir($dest);
54
+            }
55
+            $files = scandir($src);
56
+            foreach ($files as $file) {
57
+                if ($file != '.' && $file != '..') {
58
+                    self::copyr("$src/$file", "$dest/$file");
59
+                }
60
+            }
61
+        } else {
62
+            $validator = \OCP\Server::get(FilenameValidator::class);
63
+            if (!$validator->isForbidden($src)) {
64
+                copy($src, $dest);
65
+            }
66
+        }
67
+    }
68
+
69
+    /**
70
+     * @deprecated 18.0.0
71
+     * @return \OC\Files\Type\TemplateManager
72
+     */
73
+    public static function getFileTemplateManager() {
74
+        if (!self::$templateManager) {
75
+            self::$templateManager = new \OC\Files\Type\TemplateManager();
76
+        }
77
+        return self::$templateManager;
78
+    }
79
+
80
+    /**
81
+     * detect if a given program is found in the search PATH
82
+     *
83
+     * @param string $name
84
+     * @param bool $path
85
+     * @internal param string $program name
86
+     * @internal param string $optional search path, defaults to $PATH
87
+     * @return bool true if executable program found in path
88
+     * @deprecated 32.0.0 use the \OCP\IBinaryFinder
89
+     */
90
+    public static function canExecute($name, $path = false) {
91
+        // path defaults to PATH from environment if not set
92
+        if ($path === false) {
93
+            $path = getenv('PATH');
94
+        }
95
+        // we look for an executable file of that name
96
+        $exts = [''];
97
+        $check_fn = 'is_executable';
98
+        // Default check will be done with $path directories :
99
+        $dirs = explode(PATH_SEPARATOR, (string)$path);
100
+        // WARNING : We have to check if open_basedir is enabled :
101
+        $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
102
+        if ($obd != 'none') {
103
+            $obd_values = explode(PATH_SEPARATOR, $obd);
104
+            if (count($obd_values) > 0 and $obd_values[0]) {
105
+                // open_basedir is in effect !
106
+                // We need to check if the program is in one of these dirs :
107
+                $dirs = $obd_values;
108
+            }
109
+        }
110
+        foreach ($dirs as $dir) {
111
+            foreach ($exts as $ext) {
112
+                if ($check_fn("$dir/$name" . $ext)) {
113
+                    return true;
114
+                }
115
+            }
116
+        }
117
+        return false;
118
+    }
119
+
120
+    /**
121
+     * copy the contents of one stream to another
122
+     *
123
+     * @param resource $source
124
+     * @param resource $target
125
+     * @return array the number of bytes copied and result
126
+     * @deprecated 5.0.0 - Use \OCP\Files::streamCopy
127
+     */
128
+    public static function streamCopy($source, $target) {
129
+        return \OCP\Files::streamCopy($source, $target, true);
130
+    }
131
+
132
+    /**
133
+     * Adds a suffix to the name in case the file exists
134
+     *
135
+     * @param string $path
136
+     * @param string $filename
137
+     * @return string
138
+     */
139
+    public static function buildNotExistingFileName($path, $filename) {
140
+        $view = \OC\Files\Filesystem::getView();
141
+        return self::buildNotExistingFileNameForView($path, $filename, $view);
142
+    }
143
+
144
+    /**
145
+     * Adds a suffix to the name in case the file exists
146
+     *
147
+     * @param string $path
148
+     * @param string $filename
149
+     * @return string
150
+     */
151
+    public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
152
+        if ($path === '/') {
153
+            $path = '';
154
+        }
155
+        if ($pos = strrpos($filename, '.')) {
156
+            $name = substr($filename, 0, $pos);
157
+            $ext = substr($filename, $pos);
158
+        } else {
159
+            $name = $filename;
160
+            $ext = '';
161
+        }
162
+
163
+        $newpath = $path . '/' . $filename;
164
+        if ($view->file_exists($newpath)) {
165
+            if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
166
+                //Replace the last "(number)" with "(number+1)"
167
+                $last_match = count($matches[0]) - 1;
168
+                $counter = $matches[1][$last_match][0] + 1;
169
+                $offset = $matches[0][$last_match][1];
170
+                $match_length = strlen($matches[0][$last_match][0]);
171
+            } else {
172
+                $counter = 2;
173
+                $match_length = 0;
174
+                $offset = false;
175
+            }
176
+            do {
177
+                if ($offset) {
178
+                    //Replace the last "(number)" with "(number+1)"
179
+                    $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
180
+                } else {
181
+                    $newname = $name . ' (' . $counter . ')';
182
+                }
183
+                $newpath = $path . '/' . $newname . $ext;
184
+                $counter++;
185
+            } while ($view->file_exists($newpath));
186
+        }
187
+
188
+        return $newpath;
189
+    }
190
+
191
+    /**
192
+     * Checks if a function is available
193
+     *
194
+     * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead
195
+     */
196
+    public static function is_function_enabled(string $function_name): bool {
197
+        return Util::isFunctionEnabled($function_name);
198
+    }
199
+
200
+    /**
201
+     * Try to find a program
202
+     * @deprecated 25.0.0 Use \OCP\IBinaryFinder directly
203
+     */
204
+    public static function findBinaryPath(string $program): ?string {
205
+        $result = Server::get(IBinaryFinder::class)->findBinaryPath($program);
206
+        return $result !== false ? $result : null;
207
+    }
208
+
209
+    /**
210
+     * Calculate the disc space for the given path
211
+     *
212
+     * BEWARE: this requires that Util::setupFS() was called
213
+     * already !
214
+     *
215
+     * @param string $path
216
+     * @param \OCP\Files\FileInfo $rootInfo (optional)
217
+     * @param bool $includeMountPoints whether to include mount points in the size calculation
218
+     * @param bool $useCache whether to use the cached quota values
219
+     * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
220
+     * @return StorageInfo
221
+     * @throws \OCP\Files\NotFoundException
222
+     */
223
+    public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
224
+        if (!self::$cacheFactory) {
225
+            self::$cacheFactory = Server::get(ICacheFactory::class);
226
+        }
227
+        $memcache = self::$cacheFactory->createLocal('storage_info');
228
+
229
+        // return storage info without adding mount points
230
+        if (self::$quotaIncludeExternalStorage === null) {
231
+            self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
232
+        }
233
+
234
+        $view = Filesystem::getView();
235
+        if (!$view) {
236
+            throw new \OCP\Files\NotFoundException();
237
+        }
238
+        $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
239
+
240
+        $cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude');
241
+        if ($useCache) {
242
+            $cached = $memcache->get($cacheKey);
243
+            if ($cached) {
244
+                return $cached;
245
+            }
246
+        }
247
+
248
+        if (!$rootInfo) {
249
+            $rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false);
250
+        }
251
+        if (!$rootInfo instanceof \OCP\Files\FileInfo) {
252
+            throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing');
253
+        }
254
+        $used = $rootInfo->getSize($includeMountPoints);
255
+        if ($used < 0) {
256
+            $used = 0.0;
257
+        }
258
+        /** @var int|float $quota */
259
+        $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
260
+        $mount = $rootInfo->getMountPoint();
261
+        $storage = $mount->getStorage();
262
+        $sourceStorage = $storage;
263
+        if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
264
+            self::$quotaIncludeExternalStorage = false;
265
+        }
266
+        if (self::$quotaIncludeExternalStorage) {
267
+            if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
268
+                || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
269
+            ) {
270
+                /** @var \OC\Files\Storage\Home $storage */
271
+                $user = $storage->getUser();
272
+            } else {
273
+                $user = \OC::$server->getUserSession()->getUser();
274
+            }
275
+            $quota = $user?->getQuotaBytes() ?? \OCP\Files\FileInfo::SPACE_UNKNOWN;
276
+            if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
277
+                // always get free space / total space from root + mount points
278
+                return self::getGlobalStorageInfo($quota, $user, $mount);
279
+            }
280
+        }
281
+
282
+        // TODO: need a better way to get total space from storage
283
+        if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
284
+            /** @var \OC\Files\Storage\Wrapper\Quota $storage */
285
+            $quota = $sourceStorage->getQuota();
286
+        }
287
+        try {
288
+            $free = $sourceStorage->free_space($rootInfo->getInternalPath());
289
+            if (is_bool($free)) {
290
+                $free = 0.0;
291
+            }
292
+        } catch (\Exception $e) {
293
+            if ($path === '') {
294
+                throw $e;
295
+            }
296
+            /** @var LoggerInterface $logger */
297
+            $logger = \OC::$server->get(LoggerInterface::class);
298
+            $logger->warning('Error while getting quota info, using root quota', ['exception' => $e]);
299
+            $rootInfo = self::getStorageInfo('');
300
+            $memcache->set($cacheKey, $rootInfo, 5 * 60);
301
+            return $rootInfo;
302
+        }
303
+        if ($free >= 0) {
304
+            $total = $free + $used;
305
+        } else {
306
+            $total = $free; //either unknown or unlimited
307
+        }
308
+        if ($total > 0) {
309
+            if ($quota > 0 && $total > $quota) {
310
+                $total = $quota;
311
+            }
312
+            // prevent division by zero or error codes (negative values)
313
+            $relative = round(($used / $total) * 10000) / 100;
314
+        } else {
315
+            $relative = 0;
316
+        }
317
+
318
+        /*
319 319
 		 * \OCA\Files_Sharing\External\Storage returns the cloud ID as the owner for the storage.
320 320
 		 * It is unnecessary to query the user manager for the display name, as it won't have this information.
321 321
 		 */
322
-		$isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class);
323
-
324
-		$ownerId = $storage->getOwner($path);
325
-		$ownerDisplayName = '';
326
-
327
-		if ($isRemoteShare === false && $ownerId !== false) {
328
-			$ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? '';
329
-		}
330
-
331
-		if (substr_count($mount->getMountPoint(), '/') < 3) {
332
-			$mountPoint = '';
333
-		} else {
334
-			[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
335
-		}
336
-
337
-		$info = [
338
-			'free' => $free,
339
-			'used' => $used,
340
-			'quota' => $quota,
341
-			'total' => $total,
342
-			'relative' => $relative,
343
-			'owner' => $ownerId,
344
-			'ownerDisplayName' => $ownerDisplayName,
345
-			'mountType' => $mount->getMountType(),
346
-			'mountPoint' => trim($mountPoint, '/'),
347
-		];
348
-
349
-		if ($isRemoteShare === false && $ownerId !== false && $path === '/') {
350
-			// If path is root, store this as last known quota usage for this user
351
-			\OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative);
352
-		}
353
-
354
-		$memcache->set($cacheKey, $info, 5 * 60);
355
-
356
-		return $info;
357
-	}
358
-
359
-	/**
360
-	 * Get storage info including all mount points and quota
361
-	 *
362
-	 * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
363
-	 * @return StorageInfo
364
-	 */
365
-	private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
366
-		$rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
367
-		/** @var int|float $used */
368
-		$used = $rootInfo['size'];
369
-		if ($used < 0) {
370
-			$used = 0.0;
371
-		}
372
-
373
-		$total = $quota;
374
-		/** @var int|float $free */
375
-		$free = $quota - $used;
376
-
377
-		if ($total > 0) {
378
-			if ($quota > 0 && $total > $quota) {
379
-				$total = $quota;
380
-			}
381
-			// prevent division by zero or error codes (negative values)
382
-			$relative = round(($used / $total) * 10000) / 100;
383
-		} else {
384
-			$relative = 0.0;
385
-		}
386
-
387
-		if (substr_count($mount->getMountPoint(), '/') < 3) {
388
-			$mountPoint = '';
389
-		} else {
390
-			[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
391
-		}
392
-
393
-		return [
394
-			'free' => $free,
395
-			'used' => $used,
396
-			'total' => $total,
397
-			'relative' => $relative,
398
-			'quota' => $quota,
399
-			'owner' => $user->getUID(),
400
-			'ownerDisplayName' => $user->getDisplayName(),
401
-			'mountType' => $mount->getMountType(),
402
-			'mountPoint' => trim($mountPoint, '/'),
403
-		];
404
-	}
405
-
406
-	public static function clearStorageInfo(string $absolutePath): void {
407
-		/** @var ICacheFactory $cacheFactory */
408
-		$cacheFactory = \OC::$server->get(ICacheFactory::class);
409
-		$memcache = $cacheFactory->createLocal('storage_info');
410
-		$cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
411
-		$memcache->remove($cacheKeyPrefix . 'include');
412
-		$memcache->remove($cacheKeyPrefix . 'exclude');
413
-	}
414
-
415
-	/**
416
-	 * Returns whether the config file is set manually to read-only
417
-	 * @return bool
418
-	 * @deprecated 32.0.0 use the `config_is_read_only` system config directly
419
-	 */
420
-	public static function isReadOnlyConfigEnabled() {
421
-		return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false);
422
-	}
322
+        $isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class);
323
+
324
+        $ownerId = $storage->getOwner($path);
325
+        $ownerDisplayName = '';
326
+
327
+        if ($isRemoteShare === false && $ownerId !== false) {
328
+            $ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? '';
329
+        }
330
+
331
+        if (substr_count($mount->getMountPoint(), '/') < 3) {
332
+            $mountPoint = '';
333
+        } else {
334
+            [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
335
+        }
336
+
337
+        $info = [
338
+            'free' => $free,
339
+            'used' => $used,
340
+            'quota' => $quota,
341
+            'total' => $total,
342
+            'relative' => $relative,
343
+            'owner' => $ownerId,
344
+            'ownerDisplayName' => $ownerDisplayName,
345
+            'mountType' => $mount->getMountType(),
346
+            'mountPoint' => trim($mountPoint, '/'),
347
+        ];
348
+
349
+        if ($isRemoteShare === false && $ownerId !== false && $path === '/') {
350
+            // If path is root, store this as last known quota usage for this user
351
+            \OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative);
352
+        }
353
+
354
+        $memcache->set($cacheKey, $info, 5 * 60);
355
+
356
+        return $info;
357
+    }
358
+
359
+    /**
360
+     * Get storage info including all mount points and quota
361
+     *
362
+     * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
363
+     * @return StorageInfo
364
+     */
365
+    private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
366
+        $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
367
+        /** @var int|float $used */
368
+        $used = $rootInfo['size'];
369
+        if ($used < 0) {
370
+            $used = 0.0;
371
+        }
372
+
373
+        $total = $quota;
374
+        /** @var int|float $free */
375
+        $free = $quota - $used;
376
+
377
+        if ($total > 0) {
378
+            if ($quota > 0 && $total > $quota) {
379
+                $total = $quota;
380
+            }
381
+            // prevent division by zero or error codes (negative values)
382
+            $relative = round(($used / $total) * 10000) / 100;
383
+        } else {
384
+            $relative = 0.0;
385
+        }
386
+
387
+        if (substr_count($mount->getMountPoint(), '/') < 3) {
388
+            $mountPoint = '';
389
+        } else {
390
+            [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
391
+        }
392
+
393
+        return [
394
+            'free' => $free,
395
+            'used' => $used,
396
+            'total' => $total,
397
+            'relative' => $relative,
398
+            'quota' => $quota,
399
+            'owner' => $user->getUID(),
400
+            'ownerDisplayName' => $user->getDisplayName(),
401
+            'mountType' => $mount->getMountType(),
402
+            'mountPoint' => trim($mountPoint, '/'),
403
+        ];
404
+    }
405
+
406
+    public static function clearStorageInfo(string $absolutePath): void {
407
+        /** @var ICacheFactory $cacheFactory */
408
+        $cacheFactory = \OC::$server->get(ICacheFactory::class);
409
+        $memcache = $cacheFactory->createLocal('storage_info');
410
+        $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
411
+        $memcache->remove($cacheKeyPrefix . 'include');
412
+        $memcache->remove($cacheKeyPrefix . 'exclude');
413
+    }
414
+
415
+    /**
416
+     * Returns whether the config file is set manually to read-only
417
+     * @return bool
418
+     * @deprecated 32.0.0 use the `config_is_read_only` system config directly
419
+     */
420
+    public static function isReadOnlyConfigEnabled() {
421
+        return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false);
422
+    }
423 423
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_Util.php 1 patch
Indentation   +836 added lines, -836 removed lines patch added patch discarded remove patch
@@ -22,840 +22,840 @@
 block discarded – undo
22 22
  * @deprecated 32.0.0 Use \OCP\Util or any appropriate official API instead
23 23
  */
24 24
 class OC_Util {
25
-	public static $styles = [];
26
-	public static $headers = [];
27
-
28
-	/**
29
-	 * Setup the file system
30
-	 *
31
-	 * @param string|null $user
32
-	 * @return boolean
33
-	 * @description configure the initial filesystem based on the configuration
34
-	 * @suppress PhanDeprecatedFunction
35
-	 * @suppress PhanAccessMethodInternal
36
-	 */
37
-	public static function setupFS(?string $user = '') {
38
-		// If we are not forced to load a specific user we load the one that is logged in
39
-		if ($user === '') {
40
-			$userObject = \OC::$server->get(\OCP\IUserSession::class)->getUser();
41
-		} else {
42
-			$userObject = \OC::$server->get(\OCP\IUserManager::class)->get($user);
43
-		}
44
-
45
-		/** @var SetupManager $setupManager */
46
-		$setupManager = \OC::$server->get(SetupManager::class);
47
-
48
-		if ($userObject) {
49
-			$setupManager->setupForUser($userObject);
50
-		} else {
51
-			$setupManager->setupRoot();
52
-		}
53
-		return true;
54
-	}
55
-
56
-	/**
57
-	 * Check if a password is required for each public link
58
-	 *
59
-	 * @param bool $checkGroupMembership Check group membership exclusion
60
-	 * @return bool
61
-	 * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkEnforcePassword directly
62
-	 */
63
-	public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
64
-		/** @var IManager $shareManager */
65
-		$shareManager = \OC::$server->get(IManager::class);
66
-		return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership);
67
-	}
68
-
69
-	/**
70
-	 * check if sharing is disabled for the current user
71
-	 * @param IConfig $config
72
-	 * @param IGroupManager $groupManager
73
-	 * @param IUser|null $user
74
-	 * @return bool
75
-	 * @deprecated 32.0.0 use OCP\Share\IManager's sharingDisabledForUser directly
76
-	 */
77
-	public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
78
-		/** @var IManager $shareManager */
79
-		$shareManager = \OC::$server->get(IManager::class);
80
-		$userId = $user ? $user->getUID() : null;
81
-		return $shareManager->sharingDisabledForUser($userId);
82
-	}
83
-
84
-	/**
85
-	 * check if share API enforces a default expire date
86
-	 *
87
-	 * @return bool
88
-	 * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkDefaultExpireDateEnforced directly
89
-	 */
90
-	public static function isDefaultExpireDateEnforced() {
91
-		/** @var IManager $shareManager */
92
-		$shareManager = \OC::$server->get(IManager::class);
93
-		return $shareManager->shareApiLinkDefaultExpireDateEnforced();
94
-	}
95
-
96
-	/**
97
-	 * Get the quota of a user
98
-	 *
99
-	 * @param IUser|null $user
100
-	 * @return int|\OCP\Files\FileInfo::SPACE_UNLIMITED|false|float Quota bytes
101
-	 * @deprecated 9.0.0 - Use \OCP\IUser::getQuota or \OCP\IUser::getQuotaBytes
102
-	 */
103
-	public static function getUserQuota(?IUser $user) {
104
-		if (is_null($user)) {
105
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
106
-		}
107
-		$userQuota = $user->getQuota();
108
-		if ($userQuota === 'none') {
109
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
110
-		}
111
-		return \OCP\Util::computerFileSize($userQuota);
112
-	}
113
-
114
-	/**
115
-	 * copies the skeleton to the users /files
116
-	 *
117
-	 * @param string $userId
118
-	 * @param \OCP\Files\Folder $userDirectory
119
-	 * @throws \OCP\Files\NotFoundException
120
-	 * @throws \OCP\Files\NotPermittedException
121
-	 * @suppress PhanDeprecatedFunction
122
-	 */
123
-	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
124
-		/** @var LoggerInterface $logger */
125
-		$logger = \OC::$server->get(LoggerInterface::class);
126
-
127
-		$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
128
-		$userLang = \OC::$server->get(IFactory::class)->findLanguage();
129
-		$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
130
-
131
-		if (!file_exists($skeletonDirectory)) {
132
-			$dialectStart = strpos($userLang, '_');
133
-			if ($dialectStart !== false) {
134
-				$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
135
-			}
136
-			if ($dialectStart === false || !file_exists($skeletonDirectory)) {
137
-				$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
138
-			}
139
-			if (!file_exists($skeletonDirectory)) {
140
-				$skeletonDirectory = '';
141
-			}
142
-		}
143
-
144
-		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
145
-
146
-		if ($instanceId === null) {
147
-			throw new \RuntimeException('no instance id!');
148
-		}
149
-		$appdata = 'appdata_' . $instanceId;
150
-		if ($userId === $appdata) {
151
-			throw new \RuntimeException('username is reserved name: ' . $appdata);
152
-		}
153
-
154
-		if (!empty($skeletonDirectory)) {
155
-			$logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
156
-			self::copyr($skeletonDirectory, $userDirectory);
157
-			// update the file cache
158
-			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
159
-
160
-			/** @var ITemplateManager $templateManager */
161
-			$templateManager = \OC::$server->get(ITemplateManager::class);
162
-			$templateManager->initializeTemplateDirectory(null, $userId);
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * copies a directory recursively by using streams
168
-	 *
169
-	 * @param string $source
170
-	 * @param \OCP\Files\Folder $target
171
-	 * @return void
172
-	 */
173
-	public static function copyr($source, \OCP\Files\Folder $target) {
174
-		$logger = \OCP\Server::get(LoggerInterface::class);
175
-
176
-		// Verify if folder exists
177
-		$dir = opendir($source);
178
-		if ($dir === false) {
179
-			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
180
-			return;
181
-		}
182
-
183
-		// Copy the files
184
-		while (false !== ($file = readdir($dir))) {
185
-			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
186
-				if (is_dir($source . '/' . $file)) {
187
-					$child = $target->newFolder($file);
188
-					self::copyr($source . '/' . $file, $child);
189
-				} else {
190
-					$child = $target->newFile($file);
191
-					$sourceStream = fopen($source . '/' . $file, 'r');
192
-					if ($sourceStream === false) {
193
-						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
194
-						closedir($dir);
195
-						return;
196
-					}
197
-					$child->putContent($sourceStream);
198
-				}
199
-			}
200
-		}
201
-		closedir($dir);
202
-	}
203
-
204
-	/**
205
-	 * @deprecated 32.0.0 Call tearDown directly on SetupManager
206
-	 */
207
-	public static function tearDownFS(): void {
208
-		$setupManager = \OCP\Server::get(SetupManager::class);
209
-		$setupManager->tearDown();
210
-	}
211
-
212
-	/**
213
-	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
214
-	 *
215
-	 * @param string $application application to get the files from
216
-	 * @param string $directory directory within this application (css, js, vendor, etc)
217
-	 * @param ?string $file the file inside of the above folder
218
-	 */
219
-	private static function generatePath($application, $directory, $file): string {
220
-		if (is_null($file)) {
221
-			$file = $application;
222
-			$application = '';
223
-		}
224
-		if (!empty($application)) {
225
-			return "$application/$directory/$file";
226
-		} else {
227
-			return "$directory/$file";
228
-		}
229
-	}
230
-
231
-	/**
232
-	 * add a css file
233
-	 *
234
-	 * @param string $application application id
235
-	 * @param string|null $file filename
236
-	 * @param bool $prepend prepend the Style to the beginning of the list
237
-	 * @deprecated 32.0.0 Use \OCP\Util::addStyle
238
-	 */
239
-	public static function addStyle($application, $file = null, $prepend = false): void {
240
-		$path = OC_Util::generatePath($application, 'css', $file);
241
-		self::addExternalResource($application, $prepend, $path, 'style');
242
-	}
243
-
244
-	/**
245
-	 * add a css file from the vendor sub folder
246
-	 *
247
-	 * @param string $application application id
248
-	 * @param string|null $file filename
249
-	 * @param bool $prepend prepend the Style to the beginning of the list
250
-	 * @deprecated 32.0.0
251
-	 */
252
-	public static function addVendorStyle($application, $file = null, $prepend = false): void {
253
-		$path = OC_Util::generatePath($application, 'vendor', $file);
254
-		self::addExternalResource($application, $prepend, $path, 'style');
255
-	}
256
-
257
-	/**
258
-	 * add an external resource css/js file
259
-	 *
260
-	 * @param string $application application id
261
-	 * @param bool $prepend prepend the file to the beginning of the list
262
-	 * @param string $path
263
-	 * @param string $type (script or style)
264
-	 */
265
-	private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
266
-		if ($type === 'style') {
267
-			if (!in_array($path, self::$styles)) {
268
-				if ($prepend === true) {
269
-					array_unshift(self::$styles, $path);
270
-				} else {
271
-					self::$styles[] = $path;
272
-				}
273
-			}
274
-		}
275
-	}
276
-
277
-	/**
278
-	 * Add a custom element to the header
279
-	 * If $text is null then the element will be written as empty element.
280
-	 * So use "" to get a closing tag.
281
-	 * @param string $tag tag name of the element
282
-	 * @param array $attributes array of attributes for the element
283
-	 * @param string $text the text content for the element
284
-	 * @param bool $prepend prepend the header to the beginning of the list
285
-	 * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
286
-	 */
287
-	public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
288
-		$header = [
289
-			'tag' => $tag,
290
-			'attributes' => $attributes,
291
-			'text' => $text
292
-		];
293
-		if ($prepend === true) {
294
-			array_unshift(self::$headers, $header);
295
-		} else {
296
-			self::$headers[] = $header;
297
-		}
298
-	}
299
-
300
-	/**
301
-	 * check if the current server configuration is suitable for ownCloud
302
-	 *
303
-	 * @return array arrays with error messages and hints
304
-	 */
305
-	public static function checkServer(\OC\SystemConfig $config) {
306
-		$l = \OC::$server->getL10N('lib');
307
-		$errors = [];
308
-		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
309
-
310
-		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
311
-			// this check needs to be done every time
312
-			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
313
-		}
314
-
315
-		// Assume that if checkServer() succeeded before in this session, then all is fine.
316
-		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
317
-			return $errors;
318
-		}
319
-
320
-		$webServerRestart = false;
321
-		$setup = \OCP\Server::get(\OC\Setup::class);
322
-
323
-		$urlGenerator = \OC::$server->getURLGenerator();
324
-
325
-		$availableDatabases = $setup->getSupportedDatabases();
326
-		if (empty($availableDatabases)) {
327
-			$errors[] = [
328
-				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
329
-				'hint' => '' //TODO: sane hint
330
-			];
331
-			$webServerRestart = true;
332
-		}
333
-
334
-		// Check if config folder is writable.
335
-		if (!(bool)$config->getValue('config_is_read_only', false)) {
336
-			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
337
-				$errors[] = [
338
-					'error' => $l->t('Cannot write into "config" directory.'),
339
-					'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
340
-						[ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
341
-						. $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
342
-							[ $urlGenerator->linkToDocs('admin-config') ])
343
-				];
344
-			}
345
-		}
346
-
347
-		// Check if there is a writable install folder.
348
-		if ($config->getValue('appstoreenabled', true)) {
349
-			if (OC_App::getInstallPath() === null
350
-				|| !is_writable(OC_App::getInstallPath())
351
-				|| !is_readable(OC_App::getInstallPath())
352
-			) {
353
-				$errors[] = [
354
-					'error' => $l->t('Cannot write into "apps" directory.'),
355
-					'hint' => $l->t('This can usually be fixed by giving the web server write access to the apps directory'
356
-						. ' or disabling the App Store in the config file.')
357
-				];
358
-			}
359
-		}
360
-		// Create root dir.
361
-		if ($config->getValue('installed', false)) {
362
-			if (!is_dir($CONFIG_DATADIRECTORY)) {
363
-				$success = @mkdir($CONFIG_DATADIRECTORY);
364
-				if ($success) {
365
-					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
366
-				} else {
367
-					$errors[] = [
368
-						'error' => $l->t('Cannot create "data" directory.'),
369
-						'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
370
-							[$urlGenerator->linkToDocs('admin-dir_permissions')])
371
-					];
372
-				}
373
-			} elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
374
-				// is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
375
-				$testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
376
-				$handle = fopen($testFile, 'w');
377
-				if (!$handle || fwrite($handle, 'Test write operation') === false) {
378
-					$permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
379
-						[$urlGenerator->linkToDocs('admin-dir_permissions')]);
380
-					$errors[] = [
381
-						'error' => $l->t('Your data directory is not writable.'),
382
-						'hint' => $permissionsHint
383
-					];
384
-				} else {
385
-					fclose($handle);
386
-					unlink($testFile);
387
-				}
388
-			} else {
389
-				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
390
-			}
391
-		}
392
-
393
-		if (!OC_Util::isSetLocaleWorking()) {
394
-			$errors[] = [
395
-				'error' => $l->t('Setting locale to %s failed.',
396
-					['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
397
-						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
398
-				'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
399
-			];
400
-		}
401
-
402
-		// Contains the dependencies that should be checked against
403
-		// classes = class_exists
404
-		// functions = function_exists
405
-		// defined = defined
406
-		// ini = ini_get
407
-		// If the dependency is not found the missing module name is shown to the EndUser
408
-		// When adding new checks always verify that they pass on CI as well
409
-		$dependencies = [
410
-			'classes' => [
411
-				'ZipArchive' => 'zip',
412
-				'DOMDocument' => 'dom',
413
-				'XMLWriter' => 'XMLWriter',
414
-				'XMLReader' => 'XMLReader',
415
-			],
416
-			'functions' => [
417
-				'xml_parser_create' => 'libxml',
418
-				'mb_strcut' => 'mbstring',
419
-				'ctype_digit' => 'ctype',
420
-				'json_encode' => 'JSON',
421
-				'gd_info' => 'GD',
422
-				'gzencode' => 'zlib',
423
-				'simplexml_load_string' => 'SimpleXML',
424
-				'hash' => 'HASH Message Digest Framework',
425
-				'curl_init' => 'cURL',
426
-				'openssl_verify' => 'OpenSSL',
427
-			],
428
-			'defined' => [
429
-				'PDO::ATTR_DRIVER_NAME' => 'PDO'
430
-			],
431
-			'ini' => [
432
-				'default_charset' => 'UTF-8',
433
-			],
434
-		];
435
-		$missingDependencies = [];
436
-		$invalidIniSettings = [];
437
-
438
-		$iniWrapper = \OC::$server->get(IniGetWrapper::class);
439
-		foreach ($dependencies['classes'] as $class => $module) {
440
-			if (!class_exists($class)) {
441
-				$missingDependencies[] = $module;
442
-			}
443
-		}
444
-		foreach ($dependencies['functions'] as $function => $module) {
445
-			if (!function_exists($function)) {
446
-				$missingDependencies[] = $module;
447
-			}
448
-		}
449
-		foreach ($dependencies['defined'] as $defined => $module) {
450
-			if (!defined($defined)) {
451
-				$missingDependencies[] = $module;
452
-			}
453
-		}
454
-		foreach ($dependencies['ini'] as $setting => $expected) {
455
-			if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
456
-				$invalidIniSettings[] = [$setting, $expected];
457
-			}
458
-		}
459
-
460
-		foreach ($missingDependencies as $missingDependency) {
461
-			$errors[] = [
462
-				'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
463
-				'hint' => $l->t('Please ask your server administrator to install the module.'),
464
-			];
465
-			$webServerRestart = true;
466
-		}
467
-		foreach ($invalidIniSettings as $setting) {
468
-			$errors[] = [
469
-				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
470
-				'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
471
-			];
472
-			$webServerRestart = true;
473
-		}
474
-
475
-		/**
476
-		 * The mbstring.func_overload check can only be performed if the mbstring
477
-		 * module is installed as it will return null if the checking setting is
478
-		 * not available and thus a check on the boolean value fails.
479
-		 *
480
-		 * TODO: Should probably be implemented in the above generic dependency
481
-		 *       check somehow in the long-term.
482
-		 */
483
-		if ($iniWrapper->getBool('mbstring.func_overload') !== null &&
484
-			$iniWrapper->getBool('mbstring.func_overload') === true) {
485
-			$errors[] = [
486
-				'error' => $l->t('<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.', [$iniWrapper->getString('mbstring.func_overload')]),
487
-				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.')
488
-			];
489
-		}
490
-
491
-		if (!self::isAnnotationsWorking()) {
492
-			$errors[] = [
493
-				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
494
-				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
495
-			];
496
-		}
497
-
498
-		if (!\OC::$CLI && $webServerRestart) {
499
-			$errors[] = [
500
-				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
501
-				'hint' => $l->t('Please ask your server administrator to restart the web server.')
502
-			];
503
-		}
504
-
505
-		foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
506
-			if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
507
-				$errors[] = [
508
-					'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
509
-					'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
510
-				];
511
-			}
512
-		}
513
-
514
-		// Cache the result of this function
515
-		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
516
-
517
-		return $errors;
518
-	}
519
-
520
-	/**
521
-	 * Check for correct file permissions of data directory
522
-	 *
523
-	 * @param string $dataDirectory
524
-	 * @return array arrays with error messages and hints
525
-	 * @internal
526
-	 */
527
-	public static function checkDataDirectoryPermissions($dataDirectory) {
528
-		if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
529
-			return  [];
530
-		}
531
-
532
-		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
533
-		if (substr($perms, -1) !== '0') {
534
-			chmod($dataDirectory, 0770);
535
-			clearstatcache();
536
-			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
537
-			if ($perms[2] !== '0') {
538
-				$l = \OC::$server->getL10N('lib');
539
-				return [[
540
-					'error' => $l->t('Your data directory is readable by other people.'),
541
-					'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
542
-				]];
543
-			}
544
-		}
545
-		return [];
546
-	}
547
-
548
-	/**
549
-	 * Check that the data directory exists and is valid by
550
-	 * checking the existence of the ".ncdata" file.
551
-	 *
552
-	 * @param string $dataDirectory data directory path
553
-	 * @return array errors found
554
-	 * @internal
555
-	 */
556
-	public static function checkDataDirectoryValidity($dataDirectory) {
557
-		$l = \OC::$server->getL10N('lib');
558
-		$errors = [];
559
-		if ($dataDirectory[0] !== '/') {
560
-			$errors[] = [
561
-				'error' => $l->t('Your data directory must be an absolute path.'),
562
-				'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
563
-			];
564
-		}
565
-
566
-		if (!file_exists($dataDirectory . '/.ncdata')) {
567
-			$errors[] = [
568
-				'error' => $l->t('Your data directory is invalid.'),
569
-				'hint' => $l->t('Ensure there is a file called "%1$s" in the root of the data directory. It should have the content: "%2$s"', ['.ncdata', '# Nextcloud data directory']),
570
-			];
571
-		}
572
-		return $errors;
573
-	}
574
-
575
-	/**
576
-	 * Check if the user is logged in, redirects to home if not. With
577
-	 * redirect URL parameter to the request URI.
578
-	 *
579
-	 * @deprecated 32.0.0
580
-	 */
581
-	public static function checkLoggedIn(): void {
582
-		// Check if we are a user
583
-		if (!\OC::$server->getUserSession()->isLoggedIn()) {
584
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
585
-				'core.login.showLoginForm',
586
-				[
587
-					'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
588
-				]
589
-			)
590
-			);
591
-			exit();
592
-		}
593
-		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
594
-		if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
595
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
596
-			exit();
597
-		}
598
-	}
599
-
600
-	/**
601
-	 * Check if the user is a admin, redirects to home if not
602
-	 *
603
-	 * @deprecated 32.0.0
604
-	 */
605
-	public static function checkAdminUser(): void {
606
-		self::checkLoggedIn();
607
-		if (!OC_User::isAdminUser(OC_User::getUser())) {
608
-			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
609
-			exit();
610
-		}
611
-	}
612
-
613
-	/**
614
-	 * Returns the URL of the default page
615
-	 * based on the system configuration and
616
-	 * the apps visible for the current user
617
-	 *
618
-	 * @return string URL
619
-	 * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
620
-	 */
621
-	public static function getDefaultPageUrl() {
622
-		/** @var IURLGenerator $urlGenerator */
623
-		$urlGenerator = \OC::$server->get(IURLGenerator::class);
624
-		return $urlGenerator->linkToDefaultPageUrl();
625
-	}
626
-
627
-	/**
628
-	 * Redirect to the user default page
629
-	 *
630
-	 * @deprecated 32.0.0
631
-	 */
632
-	public static function redirectToDefaultPage(): void {
633
-		$location = self::getDefaultPageUrl();
634
-		header('Location: ' . $location);
635
-		exit();
636
-	}
637
-
638
-	/**
639
-	 * get an id unique for this instance
640
-	 *
641
-	 * @return string
642
-	 */
643
-	public static function getInstanceId(): string {
644
-		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
645
-		if (is_null($id)) {
646
-			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
647
-			$id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
648
-			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
649
-		}
650
-		return $id;
651
-	}
652
-
653
-	/**
654
-	 * Public function to sanitize HTML
655
-	 *
656
-	 * This function is used to sanitize HTML and should be applied on any
657
-	 * string or array of strings before displaying it on a web page.
658
-	 *
659
-	 * @param string|string[] $value
660
-	 * @return ($value is array ? string[] : string)
661
-	 * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
662
-	 */
663
-	public static function sanitizeHTML($value) {
664
-		if (is_array($value)) {
665
-			$value = array_map(function ($value) {
666
-				return self::sanitizeHTML($value);
667
-			}, $value);
668
-		} else {
669
-			// Specify encoding for PHP<5.4
670
-			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
671
-		}
672
-		return $value;
673
-	}
674
-
675
-	/**
676
-	 * Public function to encode url parameters
677
-	 *
678
-	 * This function is used to encode path to file before output.
679
-	 * Encoding is done according to RFC 3986 with one exception:
680
-	 * Character '/' is preserved as is.
681
-	 *
682
-	 * @param string $component part of URI to encode
683
-	 * @return string
684
-	 * @deprecated 32.0.0 use \OCP\Util::encodePath instead
685
-	 */
686
-	public static function encodePath($component) {
687
-		$encoded = rawurlencode($component);
688
-		$encoded = str_replace('%2F', '/', $encoded);
689
-		return $encoded;
690
-	}
691
-
692
-	/**
693
-	 * Check if current locale is non-UTF8
694
-	 *
695
-	 * @return bool
696
-	 */
697
-	private static function isNonUTF8Locale() {
698
-		if (function_exists('escapeshellcmd')) {
699
-			return escapeshellcmd('§') === '';
700
-		} elseif (function_exists('escapeshellarg')) {
701
-			return escapeshellarg('§') === '\'\'';
702
-		} else {
703
-			return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
704
-		}
705
-	}
706
-
707
-	/**
708
-	 * Check if the setlocale call does not work. This can happen if the right
709
-	 * local packages are not available on the server.
710
-	 *
711
-	 * @internal
712
-	 */
713
-	public static function isSetLocaleWorking(): bool {
714
-		if (self::isNonUTF8Locale()) {
715
-			// Borrowed from \Patchwork\Utf8\Bootup::initLocale
716
-			setlocale(LC_ALL, 'C.UTF-8', 'C');
717
-			setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0');
718
-
719
-			// Check again
720
-			if (self::isNonUTF8Locale()) {
721
-				return false;
722
-			}
723
-		}
724
-
725
-		return true;
726
-	}
727
-
728
-	/**
729
-	 * Check if it's possible to get the inline annotations
730
-	 *
731
-	 * @internal
732
-	 */
733
-	public static function isAnnotationsWorking(): bool {
734
-		if (PHP_VERSION_ID >= 80300) {
735
-			/** @psalm-suppress UndefinedMethod */
736
-			$reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
737
-		} else {
738
-			$reflection = new \ReflectionMethod(__METHOD__);
739
-		}
740
-		$docs = $reflection->getDocComment();
741
-
742
-		return (is_string($docs) && strlen($docs) > 50);
743
-	}
744
-
745
-	/**
746
-	 * Check if the PHP module fileinfo is loaded.
747
-	 *
748
-	 * @internal
749
-	 */
750
-	public static function fileInfoLoaded(): bool {
751
-		return function_exists('finfo_open');
752
-	}
753
-
754
-	/**
755
-	 * clear all levels of output buffering
756
-	 *
757
-	 * @return void
758
-	 */
759
-	public static function obEnd() {
760
-		while (ob_get_level()) {
761
-			ob_end_clean();
762
-		}
763
-	}
764
-
765
-	/**
766
-	 * Checks whether the server is running on Mac OS X
767
-	 *
768
-	 * @return bool true if running on Mac OS X, false otherwise
769
-	 */
770
-	public static function runningOnMac() {
771
-		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
772
-	}
773
-
774
-	/**
775
-	 * Handles the case that there may not be a theme, then check if a "default"
776
-	 * theme exists and take that one
777
-	 *
778
-	 * @return string the theme
779
-	 */
780
-	public static function getTheme() {
781
-		$theme = \OC::$server->getSystemConfig()->getValue('theme', '');
782
-
783
-		if ($theme === '') {
784
-			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
785
-				$theme = 'default';
786
-			}
787
-		}
788
-
789
-		return $theme;
790
-	}
791
-
792
-	/**
793
-	 * Normalize a unicode string
794
-	 *
795
-	 * @param string $value a not normalized string
796
-	 * @return string The normalized string or the input if the normalization failed
797
-	 */
798
-	public static function normalizeUnicode(string $value): string {
799
-		if (Normalizer::isNormalized($value)) {
800
-			return $value;
801
-		}
802
-
803
-		$normalizedValue = Normalizer::normalize($value);
804
-		if ($normalizedValue === false) {
805
-			\OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
806
-			return $value;
807
-		}
808
-
809
-		return $normalizedValue;
810
-	}
811
-
812
-	/**
813
-	 * Check whether the instance needs to perform an upgrade,
814
-	 * either when the core version is higher or any app requires
815
-	 * an upgrade.
816
-	 *
817
-	 * @param \OC\SystemConfig $config
818
-	 * @return bool whether the core or any app needs an upgrade
819
-	 * @throws \OCP\HintException When the upgrade from the given version is not allowed
820
-	 * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
821
-	 */
822
-	public static function needUpgrade(\OC\SystemConfig $config) {
823
-		if ($config->getValue('installed', false)) {
824
-			$installedVersion = $config->getValue('version', '0.0.0');
825
-			$currentVersion = implode('.', \OCP\Util::getVersion());
826
-			$versionDiff = version_compare($currentVersion, $installedVersion);
827
-			if ($versionDiff > 0) {
828
-				return true;
829
-			} elseif ($config->getValue('debug', false) && $versionDiff < 0) {
830
-				// downgrade with debug
831
-				$installedMajor = explode('.', $installedVersion);
832
-				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
833
-				$currentMajor = explode('.', $currentVersion);
834
-				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
835
-				if ($installedMajor === $currentMajor) {
836
-					// Same major, allow downgrade for developers
837
-					return true;
838
-				} else {
839
-					// downgrade attempt, throw exception
840
-					throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
841
-				}
842
-			} elseif ($versionDiff < 0) {
843
-				// downgrade attempt, throw exception
844
-				throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
845
-			}
846
-
847
-			// also check for upgrades for apps (independently from the user)
848
-			$apps = \OC_App::getEnabledApps(false, true);
849
-			$shouldUpgrade = false;
850
-			foreach ($apps as $app) {
851
-				if (\OC_App::shouldUpgrade($app)) {
852
-					$shouldUpgrade = true;
853
-					break;
854
-				}
855
-			}
856
-			return $shouldUpgrade;
857
-		} else {
858
-			return false;
859
-		}
860
-	}
25
+    public static $styles = [];
26
+    public static $headers = [];
27
+
28
+    /**
29
+     * Setup the file system
30
+     *
31
+     * @param string|null $user
32
+     * @return boolean
33
+     * @description configure the initial filesystem based on the configuration
34
+     * @suppress PhanDeprecatedFunction
35
+     * @suppress PhanAccessMethodInternal
36
+     */
37
+    public static function setupFS(?string $user = '') {
38
+        // If we are not forced to load a specific user we load the one that is logged in
39
+        if ($user === '') {
40
+            $userObject = \OC::$server->get(\OCP\IUserSession::class)->getUser();
41
+        } else {
42
+            $userObject = \OC::$server->get(\OCP\IUserManager::class)->get($user);
43
+        }
44
+
45
+        /** @var SetupManager $setupManager */
46
+        $setupManager = \OC::$server->get(SetupManager::class);
47
+
48
+        if ($userObject) {
49
+            $setupManager->setupForUser($userObject);
50
+        } else {
51
+            $setupManager->setupRoot();
52
+        }
53
+        return true;
54
+    }
55
+
56
+    /**
57
+     * Check if a password is required for each public link
58
+     *
59
+     * @param bool $checkGroupMembership Check group membership exclusion
60
+     * @return bool
61
+     * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkEnforcePassword directly
62
+     */
63
+    public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
64
+        /** @var IManager $shareManager */
65
+        $shareManager = \OC::$server->get(IManager::class);
66
+        return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership);
67
+    }
68
+
69
+    /**
70
+     * check if sharing is disabled for the current user
71
+     * @param IConfig $config
72
+     * @param IGroupManager $groupManager
73
+     * @param IUser|null $user
74
+     * @return bool
75
+     * @deprecated 32.0.0 use OCP\Share\IManager's sharingDisabledForUser directly
76
+     */
77
+    public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
78
+        /** @var IManager $shareManager */
79
+        $shareManager = \OC::$server->get(IManager::class);
80
+        $userId = $user ? $user->getUID() : null;
81
+        return $shareManager->sharingDisabledForUser($userId);
82
+    }
83
+
84
+    /**
85
+     * check if share API enforces a default expire date
86
+     *
87
+     * @return bool
88
+     * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkDefaultExpireDateEnforced directly
89
+     */
90
+    public static function isDefaultExpireDateEnforced() {
91
+        /** @var IManager $shareManager */
92
+        $shareManager = \OC::$server->get(IManager::class);
93
+        return $shareManager->shareApiLinkDefaultExpireDateEnforced();
94
+    }
95
+
96
+    /**
97
+     * Get the quota of a user
98
+     *
99
+     * @param IUser|null $user
100
+     * @return int|\OCP\Files\FileInfo::SPACE_UNLIMITED|false|float Quota bytes
101
+     * @deprecated 9.0.0 - Use \OCP\IUser::getQuota or \OCP\IUser::getQuotaBytes
102
+     */
103
+    public static function getUserQuota(?IUser $user) {
104
+        if (is_null($user)) {
105
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
106
+        }
107
+        $userQuota = $user->getQuota();
108
+        if ($userQuota === 'none') {
109
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
110
+        }
111
+        return \OCP\Util::computerFileSize($userQuota);
112
+    }
113
+
114
+    /**
115
+     * copies the skeleton to the users /files
116
+     *
117
+     * @param string $userId
118
+     * @param \OCP\Files\Folder $userDirectory
119
+     * @throws \OCP\Files\NotFoundException
120
+     * @throws \OCP\Files\NotPermittedException
121
+     * @suppress PhanDeprecatedFunction
122
+     */
123
+    public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
124
+        /** @var LoggerInterface $logger */
125
+        $logger = \OC::$server->get(LoggerInterface::class);
126
+
127
+        $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
128
+        $userLang = \OC::$server->get(IFactory::class)->findLanguage();
129
+        $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
130
+
131
+        if (!file_exists($skeletonDirectory)) {
132
+            $dialectStart = strpos($userLang, '_');
133
+            if ($dialectStart !== false) {
134
+                $skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
135
+            }
136
+            if ($dialectStart === false || !file_exists($skeletonDirectory)) {
137
+                $skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
138
+            }
139
+            if (!file_exists($skeletonDirectory)) {
140
+                $skeletonDirectory = '';
141
+            }
142
+        }
143
+
144
+        $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
145
+
146
+        if ($instanceId === null) {
147
+            throw new \RuntimeException('no instance id!');
148
+        }
149
+        $appdata = 'appdata_' . $instanceId;
150
+        if ($userId === $appdata) {
151
+            throw new \RuntimeException('username is reserved name: ' . $appdata);
152
+        }
153
+
154
+        if (!empty($skeletonDirectory)) {
155
+            $logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
156
+            self::copyr($skeletonDirectory, $userDirectory);
157
+            // update the file cache
158
+            $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
159
+
160
+            /** @var ITemplateManager $templateManager */
161
+            $templateManager = \OC::$server->get(ITemplateManager::class);
162
+            $templateManager->initializeTemplateDirectory(null, $userId);
163
+        }
164
+    }
165
+
166
+    /**
167
+     * copies a directory recursively by using streams
168
+     *
169
+     * @param string $source
170
+     * @param \OCP\Files\Folder $target
171
+     * @return void
172
+     */
173
+    public static function copyr($source, \OCP\Files\Folder $target) {
174
+        $logger = \OCP\Server::get(LoggerInterface::class);
175
+
176
+        // Verify if folder exists
177
+        $dir = opendir($source);
178
+        if ($dir === false) {
179
+            $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
180
+            return;
181
+        }
182
+
183
+        // Copy the files
184
+        while (false !== ($file = readdir($dir))) {
185
+            if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
186
+                if (is_dir($source . '/' . $file)) {
187
+                    $child = $target->newFolder($file);
188
+                    self::copyr($source . '/' . $file, $child);
189
+                } else {
190
+                    $child = $target->newFile($file);
191
+                    $sourceStream = fopen($source . '/' . $file, 'r');
192
+                    if ($sourceStream === false) {
193
+                        $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
194
+                        closedir($dir);
195
+                        return;
196
+                    }
197
+                    $child->putContent($sourceStream);
198
+                }
199
+            }
200
+        }
201
+        closedir($dir);
202
+    }
203
+
204
+    /**
205
+     * @deprecated 32.0.0 Call tearDown directly on SetupManager
206
+     */
207
+    public static function tearDownFS(): void {
208
+        $setupManager = \OCP\Server::get(SetupManager::class);
209
+        $setupManager->tearDown();
210
+    }
211
+
212
+    /**
213
+     * generates a path for JS/CSS files. If no application is provided it will create the path for core.
214
+     *
215
+     * @param string $application application to get the files from
216
+     * @param string $directory directory within this application (css, js, vendor, etc)
217
+     * @param ?string $file the file inside of the above folder
218
+     */
219
+    private static function generatePath($application, $directory, $file): string {
220
+        if (is_null($file)) {
221
+            $file = $application;
222
+            $application = '';
223
+        }
224
+        if (!empty($application)) {
225
+            return "$application/$directory/$file";
226
+        } else {
227
+            return "$directory/$file";
228
+        }
229
+    }
230
+
231
+    /**
232
+     * add a css file
233
+     *
234
+     * @param string $application application id
235
+     * @param string|null $file filename
236
+     * @param bool $prepend prepend the Style to the beginning of the list
237
+     * @deprecated 32.0.0 Use \OCP\Util::addStyle
238
+     */
239
+    public static function addStyle($application, $file = null, $prepend = false): void {
240
+        $path = OC_Util::generatePath($application, 'css', $file);
241
+        self::addExternalResource($application, $prepend, $path, 'style');
242
+    }
243
+
244
+    /**
245
+     * add a css file from the vendor sub folder
246
+     *
247
+     * @param string $application application id
248
+     * @param string|null $file filename
249
+     * @param bool $prepend prepend the Style to the beginning of the list
250
+     * @deprecated 32.0.0
251
+     */
252
+    public static function addVendorStyle($application, $file = null, $prepend = false): void {
253
+        $path = OC_Util::generatePath($application, 'vendor', $file);
254
+        self::addExternalResource($application, $prepend, $path, 'style');
255
+    }
256
+
257
+    /**
258
+     * add an external resource css/js file
259
+     *
260
+     * @param string $application application id
261
+     * @param bool $prepend prepend the file to the beginning of the list
262
+     * @param string $path
263
+     * @param string $type (script or style)
264
+     */
265
+    private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
266
+        if ($type === 'style') {
267
+            if (!in_array($path, self::$styles)) {
268
+                if ($prepend === true) {
269
+                    array_unshift(self::$styles, $path);
270
+                } else {
271
+                    self::$styles[] = $path;
272
+                }
273
+            }
274
+        }
275
+    }
276
+
277
+    /**
278
+     * Add a custom element to the header
279
+     * If $text is null then the element will be written as empty element.
280
+     * So use "" to get a closing tag.
281
+     * @param string $tag tag name of the element
282
+     * @param array $attributes array of attributes for the element
283
+     * @param string $text the text content for the element
284
+     * @param bool $prepend prepend the header to the beginning of the list
285
+     * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
286
+     */
287
+    public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
288
+        $header = [
289
+            'tag' => $tag,
290
+            'attributes' => $attributes,
291
+            'text' => $text
292
+        ];
293
+        if ($prepend === true) {
294
+            array_unshift(self::$headers, $header);
295
+        } else {
296
+            self::$headers[] = $header;
297
+        }
298
+    }
299
+
300
+    /**
301
+     * check if the current server configuration is suitable for ownCloud
302
+     *
303
+     * @return array arrays with error messages and hints
304
+     */
305
+    public static function checkServer(\OC\SystemConfig $config) {
306
+        $l = \OC::$server->getL10N('lib');
307
+        $errors = [];
308
+        $CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
309
+
310
+        if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
311
+            // this check needs to be done every time
312
+            $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
313
+        }
314
+
315
+        // Assume that if checkServer() succeeded before in this session, then all is fine.
316
+        if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
317
+            return $errors;
318
+        }
319
+
320
+        $webServerRestart = false;
321
+        $setup = \OCP\Server::get(\OC\Setup::class);
322
+
323
+        $urlGenerator = \OC::$server->getURLGenerator();
324
+
325
+        $availableDatabases = $setup->getSupportedDatabases();
326
+        if (empty($availableDatabases)) {
327
+            $errors[] = [
328
+                'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
329
+                'hint' => '' //TODO: sane hint
330
+            ];
331
+            $webServerRestart = true;
332
+        }
333
+
334
+        // Check if config folder is writable.
335
+        if (!(bool)$config->getValue('config_is_read_only', false)) {
336
+            if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
337
+                $errors[] = [
338
+                    'error' => $l->t('Cannot write into "config" directory.'),
339
+                    'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
340
+                        [ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
341
+                        . $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
342
+                            [ $urlGenerator->linkToDocs('admin-config') ])
343
+                ];
344
+            }
345
+        }
346
+
347
+        // Check if there is a writable install folder.
348
+        if ($config->getValue('appstoreenabled', true)) {
349
+            if (OC_App::getInstallPath() === null
350
+                || !is_writable(OC_App::getInstallPath())
351
+                || !is_readable(OC_App::getInstallPath())
352
+            ) {
353
+                $errors[] = [
354
+                    'error' => $l->t('Cannot write into "apps" directory.'),
355
+                    'hint' => $l->t('This can usually be fixed by giving the web server write access to the apps directory'
356
+                        . ' or disabling the App Store in the config file.')
357
+                ];
358
+            }
359
+        }
360
+        // Create root dir.
361
+        if ($config->getValue('installed', false)) {
362
+            if (!is_dir($CONFIG_DATADIRECTORY)) {
363
+                $success = @mkdir($CONFIG_DATADIRECTORY);
364
+                if ($success) {
365
+                    $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
366
+                } else {
367
+                    $errors[] = [
368
+                        'error' => $l->t('Cannot create "data" directory.'),
369
+                        'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
370
+                            [$urlGenerator->linkToDocs('admin-dir_permissions')])
371
+                    ];
372
+                }
373
+            } elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
374
+                // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
375
+                $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
376
+                $handle = fopen($testFile, 'w');
377
+                if (!$handle || fwrite($handle, 'Test write operation') === false) {
378
+                    $permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
379
+                        [$urlGenerator->linkToDocs('admin-dir_permissions')]);
380
+                    $errors[] = [
381
+                        'error' => $l->t('Your data directory is not writable.'),
382
+                        'hint' => $permissionsHint
383
+                    ];
384
+                } else {
385
+                    fclose($handle);
386
+                    unlink($testFile);
387
+                }
388
+            } else {
389
+                $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
390
+            }
391
+        }
392
+
393
+        if (!OC_Util::isSetLocaleWorking()) {
394
+            $errors[] = [
395
+                'error' => $l->t('Setting locale to %s failed.',
396
+                    ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
397
+                        . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
398
+                'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
399
+            ];
400
+        }
401
+
402
+        // Contains the dependencies that should be checked against
403
+        // classes = class_exists
404
+        // functions = function_exists
405
+        // defined = defined
406
+        // ini = ini_get
407
+        // If the dependency is not found the missing module name is shown to the EndUser
408
+        // When adding new checks always verify that they pass on CI as well
409
+        $dependencies = [
410
+            'classes' => [
411
+                'ZipArchive' => 'zip',
412
+                'DOMDocument' => 'dom',
413
+                'XMLWriter' => 'XMLWriter',
414
+                'XMLReader' => 'XMLReader',
415
+            ],
416
+            'functions' => [
417
+                'xml_parser_create' => 'libxml',
418
+                'mb_strcut' => 'mbstring',
419
+                'ctype_digit' => 'ctype',
420
+                'json_encode' => 'JSON',
421
+                'gd_info' => 'GD',
422
+                'gzencode' => 'zlib',
423
+                'simplexml_load_string' => 'SimpleXML',
424
+                'hash' => 'HASH Message Digest Framework',
425
+                'curl_init' => 'cURL',
426
+                'openssl_verify' => 'OpenSSL',
427
+            ],
428
+            'defined' => [
429
+                'PDO::ATTR_DRIVER_NAME' => 'PDO'
430
+            ],
431
+            'ini' => [
432
+                'default_charset' => 'UTF-8',
433
+            ],
434
+        ];
435
+        $missingDependencies = [];
436
+        $invalidIniSettings = [];
437
+
438
+        $iniWrapper = \OC::$server->get(IniGetWrapper::class);
439
+        foreach ($dependencies['classes'] as $class => $module) {
440
+            if (!class_exists($class)) {
441
+                $missingDependencies[] = $module;
442
+            }
443
+        }
444
+        foreach ($dependencies['functions'] as $function => $module) {
445
+            if (!function_exists($function)) {
446
+                $missingDependencies[] = $module;
447
+            }
448
+        }
449
+        foreach ($dependencies['defined'] as $defined => $module) {
450
+            if (!defined($defined)) {
451
+                $missingDependencies[] = $module;
452
+            }
453
+        }
454
+        foreach ($dependencies['ini'] as $setting => $expected) {
455
+            if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
456
+                $invalidIniSettings[] = [$setting, $expected];
457
+            }
458
+        }
459
+
460
+        foreach ($missingDependencies as $missingDependency) {
461
+            $errors[] = [
462
+                'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
463
+                'hint' => $l->t('Please ask your server administrator to install the module.'),
464
+            ];
465
+            $webServerRestart = true;
466
+        }
467
+        foreach ($invalidIniSettings as $setting) {
468
+            $errors[] = [
469
+                'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
470
+                'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
471
+            ];
472
+            $webServerRestart = true;
473
+        }
474
+
475
+        /**
476
+         * The mbstring.func_overload check can only be performed if the mbstring
477
+         * module is installed as it will return null if the checking setting is
478
+         * not available and thus a check on the boolean value fails.
479
+         *
480
+         * TODO: Should probably be implemented in the above generic dependency
481
+         *       check somehow in the long-term.
482
+         */
483
+        if ($iniWrapper->getBool('mbstring.func_overload') !== null &&
484
+            $iniWrapper->getBool('mbstring.func_overload') === true) {
485
+            $errors[] = [
486
+                'error' => $l->t('<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.', [$iniWrapper->getString('mbstring.func_overload')]),
487
+                'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.')
488
+            ];
489
+        }
490
+
491
+        if (!self::isAnnotationsWorking()) {
492
+            $errors[] = [
493
+                'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
494
+                'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
495
+            ];
496
+        }
497
+
498
+        if (!\OC::$CLI && $webServerRestart) {
499
+            $errors[] = [
500
+                'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
501
+                'hint' => $l->t('Please ask your server administrator to restart the web server.')
502
+            ];
503
+        }
504
+
505
+        foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
506
+            if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
507
+                $errors[] = [
508
+                    'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
509
+                    'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
510
+                ];
511
+            }
512
+        }
513
+
514
+        // Cache the result of this function
515
+        \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
516
+
517
+        return $errors;
518
+    }
519
+
520
+    /**
521
+     * Check for correct file permissions of data directory
522
+     *
523
+     * @param string $dataDirectory
524
+     * @return array arrays with error messages and hints
525
+     * @internal
526
+     */
527
+    public static function checkDataDirectoryPermissions($dataDirectory) {
528
+        if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
529
+            return  [];
530
+        }
531
+
532
+        $perms = substr(decoct(@fileperms($dataDirectory)), -3);
533
+        if (substr($perms, -1) !== '0') {
534
+            chmod($dataDirectory, 0770);
535
+            clearstatcache();
536
+            $perms = substr(decoct(@fileperms($dataDirectory)), -3);
537
+            if ($perms[2] !== '0') {
538
+                $l = \OC::$server->getL10N('lib');
539
+                return [[
540
+                    'error' => $l->t('Your data directory is readable by other people.'),
541
+                    'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
542
+                ]];
543
+            }
544
+        }
545
+        return [];
546
+    }
547
+
548
+    /**
549
+     * Check that the data directory exists and is valid by
550
+     * checking the existence of the ".ncdata" file.
551
+     *
552
+     * @param string $dataDirectory data directory path
553
+     * @return array errors found
554
+     * @internal
555
+     */
556
+    public static function checkDataDirectoryValidity($dataDirectory) {
557
+        $l = \OC::$server->getL10N('lib');
558
+        $errors = [];
559
+        if ($dataDirectory[0] !== '/') {
560
+            $errors[] = [
561
+                'error' => $l->t('Your data directory must be an absolute path.'),
562
+                'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
563
+            ];
564
+        }
565
+
566
+        if (!file_exists($dataDirectory . '/.ncdata')) {
567
+            $errors[] = [
568
+                'error' => $l->t('Your data directory is invalid.'),
569
+                'hint' => $l->t('Ensure there is a file called "%1$s" in the root of the data directory. It should have the content: "%2$s"', ['.ncdata', '# Nextcloud data directory']),
570
+            ];
571
+        }
572
+        return $errors;
573
+    }
574
+
575
+    /**
576
+     * Check if the user is logged in, redirects to home if not. With
577
+     * redirect URL parameter to the request URI.
578
+     *
579
+     * @deprecated 32.0.0
580
+     */
581
+    public static function checkLoggedIn(): void {
582
+        // Check if we are a user
583
+        if (!\OC::$server->getUserSession()->isLoggedIn()) {
584
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
585
+                'core.login.showLoginForm',
586
+                [
587
+                    'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
588
+                ]
589
+            )
590
+            );
591
+            exit();
592
+        }
593
+        // Redirect to 2FA challenge selection if 2FA challenge was not solved yet
594
+        if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
595
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
596
+            exit();
597
+        }
598
+    }
599
+
600
+    /**
601
+     * Check if the user is a admin, redirects to home if not
602
+     *
603
+     * @deprecated 32.0.0
604
+     */
605
+    public static function checkAdminUser(): void {
606
+        self::checkLoggedIn();
607
+        if (!OC_User::isAdminUser(OC_User::getUser())) {
608
+            header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
609
+            exit();
610
+        }
611
+    }
612
+
613
+    /**
614
+     * Returns the URL of the default page
615
+     * based on the system configuration and
616
+     * the apps visible for the current user
617
+     *
618
+     * @return string URL
619
+     * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
620
+     */
621
+    public static function getDefaultPageUrl() {
622
+        /** @var IURLGenerator $urlGenerator */
623
+        $urlGenerator = \OC::$server->get(IURLGenerator::class);
624
+        return $urlGenerator->linkToDefaultPageUrl();
625
+    }
626
+
627
+    /**
628
+     * Redirect to the user default page
629
+     *
630
+     * @deprecated 32.0.0
631
+     */
632
+    public static function redirectToDefaultPage(): void {
633
+        $location = self::getDefaultPageUrl();
634
+        header('Location: ' . $location);
635
+        exit();
636
+    }
637
+
638
+    /**
639
+     * get an id unique for this instance
640
+     *
641
+     * @return string
642
+     */
643
+    public static function getInstanceId(): string {
644
+        $id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
645
+        if (is_null($id)) {
646
+            // We need to guarantee at least one letter in instanceid so it can be used as the session_name
647
+            $id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
648
+            \OC::$server->getSystemConfig()->setValue('instanceid', $id);
649
+        }
650
+        return $id;
651
+    }
652
+
653
+    /**
654
+     * Public function to sanitize HTML
655
+     *
656
+     * This function is used to sanitize HTML and should be applied on any
657
+     * string or array of strings before displaying it on a web page.
658
+     *
659
+     * @param string|string[] $value
660
+     * @return ($value is array ? string[] : string)
661
+     * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
662
+     */
663
+    public static function sanitizeHTML($value) {
664
+        if (is_array($value)) {
665
+            $value = array_map(function ($value) {
666
+                return self::sanitizeHTML($value);
667
+            }, $value);
668
+        } else {
669
+            // Specify encoding for PHP<5.4
670
+            $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
671
+        }
672
+        return $value;
673
+    }
674
+
675
+    /**
676
+     * Public function to encode url parameters
677
+     *
678
+     * This function is used to encode path to file before output.
679
+     * Encoding is done according to RFC 3986 with one exception:
680
+     * Character '/' is preserved as is.
681
+     *
682
+     * @param string $component part of URI to encode
683
+     * @return string
684
+     * @deprecated 32.0.0 use \OCP\Util::encodePath instead
685
+     */
686
+    public static function encodePath($component) {
687
+        $encoded = rawurlencode($component);
688
+        $encoded = str_replace('%2F', '/', $encoded);
689
+        return $encoded;
690
+    }
691
+
692
+    /**
693
+     * Check if current locale is non-UTF8
694
+     *
695
+     * @return bool
696
+     */
697
+    private static function isNonUTF8Locale() {
698
+        if (function_exists('escapeshellcmd')) {
699
+            return escapeshellcmd('§') === '';
700
+        } elseif (function_exists('escapeshellarg')) {
701
+            return escapeshellarg('§') === '\'\'';
702
+        } else {
703
+            return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
704
+        }
705
+    }
706
+
707
+    /**
708
+     * Check if the setlocale call does not work. This can happen if the right
709
+     * local packages are not available on the server.
710
+     *
711
+     * @internal
712
+     */
713
+    public static function isSetLocaleWorking(): bool {
714
+        if (self::isNonUTF8Locale()) {
715
+            // Borrowed from \Patchwork\Utf8\Bootup::initLocale
716
+            setlocale(LC_ALL, 'C.UTF-8', 'C');
717
+            setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0');
718
+
719
+            // Check again
720
+            if (self::isNonUTF8Locale()) {
721
+                return false;
722
+            }
723
+        }
724
+
725
+        return true;
726
+    }
727
+
728
+    /**
729
+     * Check if it's possible to get the inline annotations
730
+     *
731
+     * @internal
732
+     */
733
+    public static function isAnnotationsWorking(): bool {
734
+        if (PHP_VERSION_ID >= 80300) {
735
+            /** @psalm-suppress UndefinedMethod */
736
+            $reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
737
+        } else {
738
+            $reflection = new \ReflectionMethod(__METHOD__);
739
+        }
740
+        $docs = $reflection->getDocComment();
741
+
742
+        return (is_string($docs) && strlen($docs) > 50);
743
+    }
744
+
745
+    /**
746
+     * Check if the PHP module fileinfo is loaded.
747
+     *
748
+     * @internal
749
+     */
750
+    public static function fileInfoLoaded(): bool {
751
+        return function_exists('finfo_open');
752
+    }
753
+
754
+    /**
755
+     * clear all levels of output buffering
756
+     *
757
+     * @return void
758
+     */
759
+    public static function obEnd() {
760
+        while (ob_get_level()) {
761
+            ob_end_clean();
762
+        }
763
+    }
764
+
765
+    /**
766
+     * Checks whether the server is running on Mac OS X
767
+     *
768
+     * @return bool true if running on Mac OS X, false otherwise
769
+     */
770
+    public static function runningOnMac() {
771
+        return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
772
+    }
773
+
774
+    /**
775
+     * Handles the case that there may not be a theme, then check if a "default"
776
+     * theme exists and take that one
777
+     *
778
+     * @return string the theme
779
+     */
780
+    public static function getTheme() {
781
+        $theme = \OC::$server->getSystemConfig()->getValue('theme', '');
782
+
783
+        if ($theme === '') {
784
+            if (is_dir(OC::$SERVERROOT . '/themes/default')) {
785
+                $theme = 'default';
786
+            }
787
+        }
788
+
789
+        return $theme;
790
+    }
791
+
792
+    /**
793
+     * Normalize a unicode string
794
+     *
795
+     * @param string $value a not normalized string
796
+     * @return string The normalized string or the input if the normalization failed
797
+     */
798
+    public static function normalizeUnicode(string $value): string {
799
+        if (Normalizer::isNormalized($value)) {
800
+            return $value;
801
+        }
802
+
803
+        $normalizedValue = Normalizer::normalize($value);
804
+        if ($normalizedValue === false) {
805
+            \OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
806
+            return $value;
807
+        }
808
+
809
+        return $normalizedValue;
810
+    }
811
+
812
+    /**
813
+     * Check whether the instance needs to perform an upgrade,
814
+     * either when the core version is higher or any app requires
815
+     * an upgrade.
816
+     *
817
+     * @param \OC\SystemConfig $config
818
+     * @return bool whether the core or any app needs an upgrade
819
+     * @throws \OCP\HintException When the upgrade from the given version is not allowed
820
+     * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
821
+     */
822
+    public static function needUpgrade(\OC\SystemConfig $config) {
823
+        if ($config->getValue('installed', false)) {
824
+            $installedVersion = $config->getValue('version', '0.0.0');
825
+            $currentVersion = implode('.', \OCP\Util::getVersion());
826
+            $versionDiff = version_compare($currentVersion, $installedVersion);
827
+            if ($versionDiff > 0) {
828
+                return true;
829
+            } elseif ($config->getValue('debug', false) && $versionDiff < 0) {
830
+                // downgrade with debug
831
+                $installedMajor = explode('.', $installedVersion);
832
+                $installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
833
+                $currentMajor = explode('.', $currentVersion);
834
+                $currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
835
+                if ($installedMajor === $currentMajor) {
836
+                    // Same major, allow downgrade for developers
837
+                    return true;
838
+                } else {
839
+                    // downgrade attempt, throw exception
840
+                    throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
841
+                }
842
+            } elseif ($versionDiff < 0) {
843
+                // downgrade attempt, throw exception
844
+                throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
845
+            }
846
+
847
+            // also check for upgrades for apps (independently from the user)
848
+            $apps = \OC_App::getEnabledApps(false, true);
849
+            $shouldUpgrade = false;
850
+            foreach ($apps as $app) {
851
+                if (\OC_App::shouldUpgrade($app)) {
852
+                    $shouldUpgrade = true;
853
+                    break;
854
+                }
855
+            }
856
+            return $shouldUpgrade;
857
+        } else {
858
+            return false;
859
+        }
860
+    }
861 861
 }
Please login to merge, or discard this patch.
lib/private/User/LazyUser.php 2 patches
Indentation   +152 added lines, -152 removed lines patch added patch discarded remove patch
@@ -13,166 +13,166 @@
 block discarded – undo
13 13
 use OCP\UserInterface;
14 14
 
15 15
 class LazyUser implements IUser {
16
-	private ?IUser $user = null;
17
-	private string $uid;
18
-	private ?string $displayName;
19
-	private IUserManager $userManager;
20
-	private ?UserInterface $backend;
21
-
22
-	public function __construct(string $uid, IUserManager $userManager, ?string $displayName = null, ?UserInterface $backend = null) {
23
-		$this->uid = $uid;
24
-		$this->userManager = $userManager;
25
-		$this->displayName = $displayName;
26
-		$this->backend = $backend;
27
-	}
28
-
29
-	private function getUser(): IUser {
30
-		if ($this->user === null) {
31
-			if ($this->backend) {
32
-				/** @var \OC\User\Manager $manager */
33
-				$manager = $this->userManager;
34
-				$this->user = $manager->getUserObject($this->uid, $this->backend);
35
-			} else {
36
-				$this->user = $this->userManager->get($this->uid);
37
-			}
38
-		}
39
-
40
-		if ($this->user === null) {
41
-			throw new NoUserException('User not found in backend');
42
-		}
43
-
44
-		return $this->user;
45
-	}
46
-
47
-	public function getUID() {
48
-		return $this->uid;
49
-	}
50
-
51
-	public function getDisplayName() {
52
-		if ($this->displayName) {
53
-			return $this->displayName;
54
-		}
55
-
56
-		return $this->userManager->getDisplayName($this->uid) ?? $this->uid;
57
-	}
58
-
59
-	public function setDisplayName($displayName) {
60
-		return $this->getUser()->setDisplayName($displayName);
61
-	}
62
-
63
-	public function getLastLogin(): int {
64
-		return $this->getUser()->getLastLogin();
65
-	}
66
-
67
-	public function getFirstLogin(): int {
68
-		return $this->getUser()->getFirstLogin();
69
-	}
70
-
71
-	public function updateLastLoginTimestamp(): bool {
72
-		return $this->getUser()->updateLastLoginTimestamp();
73
-	}
74
-
75
-	public function delete() {
76
-		return $this->getUser()->delete();
77
-	}
78
-
79
-	public function setPassword($password, $recoveryPassword = null) {
80
-		return $this->getUser()->setPassword($password, $recoveryPassword);
81
-	}
82
-
83
-	public function getPasswordHash(): ?string {
84
-		return $this->getUser()->getPasswordHash();
85
-	}
86
-
87
-	public function setPasswordHash(string $passwordHash): bool {
88
-		return $this->getUser()->setPasswordHash($passwordHash);
89
-	}
90
-
91
-	public function getHome() {
92
-		return $this->getUser()->getHome();
93
-	}
94
-
95
-	public function getBackendClassName() {
96
-		return $this->getUser()->getBackendClassName();
97
-	}
98
-
99
-	public function getBackend(): ?UserInterface {
100
-		return $this->getUser()->getBackend();
101
-	}
102
-
103
-	public function canChangeAvatar() {
104
-		return $this->getUser()->canChangeAvatar();
105
-	}
106
-
107
-	public function canChangePassword() {
108
-		return $this->getUser()->canChangePassword();
109
-	}
110
-
111
-	public function canChangeDisplayName() {
112
-		return $this->getUser()->canChangeDisplayName();
113
-	}
114
-
115
-	public function canChangeEmail(): bool {
116
-		return $this->getUser()->canChangeEmail();
117
-	}
118
-
119
-	public function isEnabled() {
120
-		return $this->getUser()->isEnabled();
121
-	}
122
-
123
-	public function setEnabled(bool $enabled = true) {
124
-		return $this->getUser()->setEnabled($enabled);
125
-	}
126
-
127
-	public function getEMailAddress() {
128
-		return $this->getUser()->getEMailAddress();
129
-	}
130
-
131
-	public function getSystemEMailAddress(): ?string {
132
-		return $this->getUser()->getSystemEMailAddress();
133
-	}
134
-
135
-	public function getPrimaryEMailAddress(): ?string {
136
-		return $this->getUser()->getPrimaryEMailAddress();
137
-	}
16
+    private ?IUser $user = null;
17
+    private string $uid;
18
+    private ?string $displayName;
19
+    private IUserManager $userManager;
20
+    private ?UserInterface $backend;
21
+
22
+    public function __construct(string $uid, IUserManager $userManager, ?string $displayName = null, ?UserInterface $backend = null) {
23
+        $this->uid = $uid;
24
+        $this->userManager = $userManager;
25
+        $this->displayName = $displayName;
26
+        $this->backend = $backend;
27
+    }
28
+
29
+    private function getUser(): IUser {
30
+        if ($this->user === null) {
31
+            if ($this->backend) {
32
+                /** @var \OC\User\Manager $manager */
33
+                $manager = $this->userManager;
34
+                $this->user = $manager->getUserObject($this->uid, $this->backend);
35
+            } else {
36
+                $this->user = $this->userManager->get($this->uid);
37
+            }
38
+        }
39
+
40
+        if ($this->user === null) {
41
+            throw new NoUserException('User not found in backend');
42
+        }
43
+
44
+        return $this->user;
45
+    }
46
+
47
+    public function getUID() {
48
+        return $this->uid;
49
+    }
50
+
51
+    public function getDisplayName() {
52
+        if ($this->displayName) {
53
+            return $this->displayName;
54
+        }
55
+
56
+        return $this->userManager->getDisplayName($this->uid) ?? $this->uid;
57
+    }
58
+
59
+    public function setDisplayName($displayName) {
60
+        return $this->getUser()->setDisplayName($displayName);
61
+    }
62
+
63
+    public function getLastLogin(): int {
64
+        return $this->getUser()->getLastLogin();
65
+    }
66
+
67
+    public function getFirstLogin(): int {
68
+        return $this->getUser()->getFirstLogin();
69
+    }
70
+
71
+    public function updateLastLoginTimestamp(): bool {
72
+        return $this->getUser()->updateLastLoginTimestamp();
73
+    }
74
+
75
+    public function delete() {
76
+        return $this->getUser()->delete();
77
+    }
78
+
79
+    public function setPassword($password, $recoveryPassword = null) {
80
+        return $this->getUser()->setPassword($password, $recoveryPassword);
81
+    }
82
+
83
+    public function getPasswordHash(): ?string {
84
+        return $this->getUser()->getPasswordHash();
85
+    }
86
+
87
+    public function setPasswordHash(string $passwordHash): bool {
88
+        return $this->getUser()->setPasswordHash($passwordHash);
89
+    }
90
+
91
+    public function getHome() {
92
+        return $this->getUser()->getHome();
93
+    }
94
+
95
+    public function getBackendClassName() {
96
+        return $this->getUser()->getBackendClassName();
97
+    }
98
+
99
+    public function getBackend(): ?UserInterface {
100
+        return $this->getUser()->getBackend();
101
+    }
102
+
103
+    public function canChangeAvatar() {
104
+        return $this->getUser()->canChangeAvatar();
105
+    }
106
+
107
+    public function canChangePassword() {
108
+        return $this->getUser()->canChangePassword();
109
+    }
110
+
111
+    public function canChangeDisplayName() {
112
+        return $this->getUser()->canChangeDisplayName();
113
+    }
114
+
115
+    public function canChangeEmail(): bool {
116
+        return $this->getUser()->canChangeEmail();
117
+    }
118
+
119
+    public function isEnabled() {
120
+        return $this->getUser()->isEnabled();
121
+    }
122
+
123
+    public function setEnabled(bool $enabled = true) {
124
+        return $this->getUser()->setEnabled($enabled);
125
+    }
126
+
127
+    public function getEMailAddress() {
128
+        return $this->getUser()->getEMailAddress();
129
+    }
130
+
131
+    public function getSystemEMailAddress(): ?string {
132
+        return $this->getUser()->getSystemEMailAddress();
133
+    }
134
+
135
+    public function getPrimaryEMailAddress(): ?string {
136
+        return $this->getUser()->getPrimaryEMailAddress();
137
+    }
138 138
 
139
-	public function getAvatarImage($size) {
140
-		return $this->getUser()->getAvatarImage($size);
141
-	}
139
+    public function getAvatarImage($size) {
140
+        return $this->getUser()->getAvatarImage($size);
141
+    }
142 142
 
143
-	public function getCloudId() {
144
-		return $this->getUser()->getCloudId();
145
-	}
143
+    public function getCloudId() {
144
+        return $this->getUser()->getCloudId();
145
+    }
146 146
 
147
-	public function setEMailAddress($mailAddress) {
148
-		$this->getUser()->setEMailAddress($mailAddress);
149
-	}
147
+    public function setEMailAddress($mailAddress) {
148
+        $this->getUser()->setEMailAddress($mailAddress);
149
+    }
150 150
 
151
-	public function setSystemEMailAddress(string $mailAddress): void {
152
-		$this->getUser()->setSystemEMailAddress($mailAddress);
153
-	}
151
+    public function setSystemEMailAddress(string $mailAddress): void {
152
+        $this->getUser()->setSystemEMailAddress($mailAddress);
153
+    }
154 154
 
155
-	public function setPrimaryEMailAddress(string $mailAddress): void {
156
-		$this->getUser()->setPrimaryEMailAddress($mailAddress);
157
-	}
155
+    public function setPrimaryEMailAddress(string $mailAddress): void {
156
+        $this->getUser()->setPrimaryEMailAddress($mailAddress);
157
+    }
158 158
 
159
-	public function getQuota() {
160
-		return $this->getUser()->getQuota();
161
-	}
159
+    public function getQuota() {
160
+        return $this->getUser()->getQuota();
161
+    }
162 162
 
163
-	public function getQuotaBytes(): int|float {
164
-		return $this->getUser()->getQuotaBytes();
165
-	}
163
+    public function getQuotaBytes(): int|float {
164
+        return $this->getUser()->getQuotaBytes();
165
+    }
166 166
 
167
-	public function setQuota($quota) {
168
-		$this->getUser()->setQuota($quota);
169
-	}
167
+    public function setQuota($quota) {
168
+        $this->getUser()->setQuota($quota);
169
+    }
170 170
 
171
-	public function getManagerUids(): array {
172
-		return $this->getUser()->getManagerUids();
173
-	}
171
+    public function getManagerUids(): array {
172
+        return $this->getUser()->getManagerUids();
173
+    }
174 174
 
175
-	public function setManagerUids(array $uids): void {
176
-		$this->getUser()->setManagerUids($uids);
177
-	}
175
+    public function setManagerUids(array $uids): void {
176
+        $this->getUser()->setManagerUids($uids);
177
+    }
178 178
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -160,7 +160,7 @@
 block discarded – undo
160 160
 		return $this->getUser()->getQuota();
161 161
 	}
162 162
 
163
-	public function getQuotaBytes(): int|float {
163
+	public function getQuotaBytes(): int | float {
164 164
 		return $this->getUser()->getQuotaBytes();
165 165
 	}
166 166
 
Please login to merge, or discard this patch.
lib/private/User/User.php 2 patches
Indentation   +624 added lines, -624 removed lines patch added patch discarded remove patch
@@ -44,628 +44,628 @@
 block discarded – undo
44 44
 use function json_encode;
45 45
 
46 46
 class User implements IUser {
47
-	private const CONFIG_KEY_MANAGERS = 'manager';
48
-
49
-	private IConfig $config;
50
-	private IURLGenerator $urlGenerator;
51
-
52
-	/** @var IAccountManager */
53
-	protected $accountManager;
54
-
55
-	/** @var string|null */
56
-	private $displayName;
57
-
58
-	/** @var bool|null */
59
-	private $enabled;
60
-
61
-	/** @var Emitter|Manager|null */
62
-	private $emitter;
63
-
64
-	/** @var string */
65
-	private $home;
66
-
67
-	private ?int $lastLogin = null;
68
-	private ?int $firstLogin = null;
69
-
70
-	/** @var IAvatarManager */
71
-	private $avatarManager;
72
-
73
-	public function __construct(
74
-		private string $uid,
75
-		private ?UserInterface $backend,
76
-		private IEventDispatcher $dispatcher,
77
-		$emitter = null,
78
-		?IConfig $config = null,
79
-		$urlGenerator = null,
80
-	) {
81
-		$this->emitter = $emitter;
82
-		$this->config = $config ?? \OCP\Server::get(IConfig::class);
83
-		$this->urlGenerator = $urlGenerator ?? \OCP\Server::get(IURLGenerator::class);
84
-	}
85
-
86
-	/**
87
-	 * get the user id
88
-	 *
89
-	 * @return string
90
-	 */
91
-	public function getUID() {
92
-		return $this->uid;
93
-	}
94
-
95
-	/**
96
-	 * get the display name for the user, if no specific display name is set it will fallback to the user id
97
-	 *
98
-	 * @return string
99
-	 */
100
-	public function getDisplayName() {
101
-		if ($this->displayName === null) {
102
-			$displayName = '';
103
-			if ($this->backend && $this->backend->implementsActions(Backend::GET_DISPLAYNAME)) {
104
-				// get display name and strip whitespace from the beginning and end of it
105
-				$backendDisplayName = $this->backend->getDisplayName($this->uid);
106
-				if (is_string($backendDisplayName)) {
107
-					$displayName = trim($backendDisplayName);
108
-				}
109
-			}
110
-
111
-			if (!empty($displayName)) {
112
-				$this->displayName = $displayName;
113
-			} else {
114
-				$this->displayName = $this->uid;
115
-			}
116
-		}
117
-		return $this->displayName;
118
-	}
119
-
120
-	/**
121
-	 * set the displayname for the user
122
-	 *
123
-	 * @param string $displayName
124
-	 * @return bool
125
-	 *
126
-	 * @since 25.0.0 Throw InvalidArgumentException
127
-	 * @throws \InvalidArgumentException
128
-	 */
129
-	public function setDisplayName($displayName) {
130
-		$displayName = trim($displayName);
131
-		$oldDisplayName = $this->getDisplayName();
132
-		if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) {
133
-			/** @var ISetDisplayNameBackend $backend */
134
-			$backend = $this->backend;
135
-			$result = $backend->setDisplayName($this->uid, $displayName);
136
-			if ($result) {
137
-				$this->displayName = $displayName;
138
-				$this->triggerChange('displayName', $displayName, $oldDisplayName);
139
-			}
140
-			return $result !== false;
141
-		}
142
-		return false;
143
-	}
144
-
145
-	/**
146
-	 * @inheritDoc
147
-	 */
148
-	public function setEMailAddress($mailAddress) {
149
-		$this->setSystemEMailAddress($mailAddress);
150
-	}
151
-
152
-	/**
153
-	 * @inheritDoc
154
-	 */
155
-	public function setSystemEMailAddress(string $mailAddress): void {
156
-		$oldMailAddress = $this->getSystemEMailAddress();
157
-
158
-		if ($mailAddress === '') {
159
-			$this->config->deleteUserValue($this->uid, 'settings', 'email');
160
-		} else {
161
-			$this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress);
162
-		}
163
-
164
-		$primaryAddress = $this->getPrimaryEMailAddress();
165
-		if ($primaryAddress === $mailAddress) {
166
-			// on match no dedicated primary settings is necessary
167
-			$this->setPrimaryEMailAddress('');
168
-		}
169
-
170
-		if ($oldMailAddress !== strtolower($mailAddress)) {
171
-			$this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress);
172
-		}
173
-	}
174
-
175
-	/**
176
-	 * @inheritDoc
177
-	 */
178
-	public function setPrimaryEMailAddress(string $mailAddress): void {
179
-		if ($mailAddress === '') {
180
-			$this->config->deleteUserValue($this->uid, 'settings', 'primary_email');
181
-			return;
182
-		}
183
-
184
-		$this->ensureAccountManager();
185
-		$account = $this->accountManager->getAccount($this);
186
-		$property = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)
187
-			->getPropertyByValue($mailAddress);
188
-
189
-		if ($property === null || $property->getLocallyVerified() !== IAccountManager::VERIFIED) {
190
-			throw new InvalidArgumentException('Only verified emails can be set as primary');
191
-		}
192
-		$this->config->setUserValue($this->uid, 'settings', 'primary_email', $mailAddress);
193
-	}
194
-
195
-	private function ensureAccountManager() {
196
-		if (!$this->accountManager instanceof IAccountManager) {
197
-			$this->accountManager = \OC::$server->get(IAccountManager::class);
198
-		}
199
-	}
200
-
201
-	/**
202
-	 * returns the timestamp of the user's last login or 0 if the user did never
203
-	 * login
204
-	 */
205
-	public function getLastLogin(): int {
206
-		if ($this->lastLogin === null) {
207
-			$this->lastLogin = (int)$this->config->getUserValue($this->uid, 'login', 'lastLogin', 0);
208
-		}
209
-		return $this->lastLogin;
210
-	}
211
-
212
-	/**
213
-	 * returns the timestamp of the user's last login or 0 if the user did never
214
-	 * login
215
-	 */
216
-	public function getFirstLogin(): int {
217
-		if ($this->firstLogin === null) {
218
-			$this->firstLogin = (int)$this->config->getUserValue($this->uid, 'login', 'firstLogin', 0);
219
-		}
220
-		return $this->firstLogin;
221
-	}
222
-
223
-	/**
224
-	 * updates the timestamp of the most recent login of this user
225
-	 */
226
-	public function updateLastLoginTimestamp(): bool {
227
-		$previousLogin = $this->getLastLogin();
228
-		$firstLogin = $this->getFirstLogin();
229
-		$now = time();
230
-		$firstTimeLogin = $previousLogin === 0;
231
-
232
-		if ($now - $previousLogin > 60) {
233
-			$this->lastLogin = $now;
234
-			$this->config->setUserValue($this->uid, 'login', 'lastLogin', (string)$this->lastLogin);
235
-		}
236
-
237
-		if ($firstLogin === 0) {
238
-			if ($firstTimeLogin) {
239
-				$this->firstLogin = $now;
240
-			} else {
241
-				/* Unknown first login, most likely was before upgrade to Nextcloud 31 */
242
-				$this->firstLogin = -1;
243
-			}
244
-			$this->config->setUserValue($this->uid, 'login', 'firstLogin', (string)$this->firstLogin);
245
-		}
246
-
247
-		return $firstTimeLogin;
248
-	}
249
-
250
-	/**
251
-	 * Delete the user
252
-	 *
253
-	 * @return bool
254
-	 */
255
-	public function delete() {
256
-		if ($this->backend === null) {
257
-			\OCP\Server::get(LoggerInterface::class)->error('Cannot delete user: No backend set');
258
-			return false;
259
-		}
260
-
261
-		if ($this->emitter) {
262
-			/** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
263
-			$this->emitter->emit('\OC\User', 'preDelete', [$this]);
264
-		}
265
-		$this->dispatcher->dispatchTyped(new BeforeUserDeletedEvent($this));
266
-
267
-		// Set delete flag on the user - this is needed to ensure that the user data is removed if there happen any exception in the backend
268
-		// because we can not restore the user meaning we could not rollback to any stable state otherwise.
269
-		$this->config->setUserValue($this->uid, 'core', 'deleted', 'true');
270
-		// We also need to backup the home path as this can not be reconstructed later if the original backend uses custom home paths
271
-		$this->config->setUserValue($this->uid, 'core', 'deleted.home-path', $this->getHome());
272
-
273
-		// Try to delete the user on the backend
274
-		$result = $this->backend->deleteUser($this->uid);
275
-		if ($result === false) {
276
-			// The deletion was aborted or something else happened, we are in a defined state, so remove the delete flag
277
-			$this->config->deleteUserValue($this->uid, 'core', 'deleted');
278
-			return false;
279
-		}
280
-
281
-		// We have to delete the user from all groups
282
-		$groupManager = \OCP\Server::get(IGroupManager::class);
283
-		foreach ($groupManager->getUserGroupIds($this) as $groupId) {
284
-			$group = $groupManager->get($groupId);
285
-			if ($group) {
286
-				$this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this));
287
-				$group->removeUser($this);
288
-				$this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this));
289
-			}
290
-		}
291
-
292
-		$commentsManager = \OCP\Server::get(ICommentsManager::class);
293
-		$commentsManager->deleteReferencesOfActor('users', $this->uid);
294
-		$commentsManager->deleteReadMarksFromUser($this);
295
-
296
-		$avatarManager = \OCP\Server::get(AvatarManager::class);
297
-		$avatarManager->deleteUserAvatar($this->uid);
298
-
299
-		$notificationManager = \OCP\Server::get(INotificationManager::class);
300
-		$notification = $notificationManager->createNotification();
301
-		$notification->setUser($this->uid);
302
-		$notificationManager->markProcessed($notification);
303
-
304
-		$accountManager = \OCP\Server::get(AccountManager::class);
305
-		$accountManager->deleteUser($this);
306
-
307
-		$database = \OCP\Server::get(IDBConnection::class);
308
-		try {
309
-			// We need to create a transaction to make sure we are in a defined state
310
-			// because if all user values are removed also the flag is gone, but if an exception happens (e.g. database lost connection on the set operation)
311
-			// exactly here we are in an undefined state as the data is still present but the user does not exist on the system anymore.
312
-			$database->beginTransaction();
313
-			// Remove all user settings
314
-			$this->config->deleteAllUserValues($this->uid);
315
-			// But again set flag that this user is about to be deleted
316
-			$this->config->setUserValue($this->uid, 'core', 'deleted', 'true');
317
-			$this->config->setUserValue($this->uid, 'core', 'deleted.home-path', $this->getHome());
318
-			// Commit the transaction so we are in a defined state: either the preferences are removed or an exception occurred but the delete flag is still present
319
-			$database->commit();
320
-		} catch (\Throwable $e) {
321
-			$database->rollback();
322
-			throw $e;
323
-		}
324
-
325
-		if ($this->emitter !== null) {
326
-			/** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
327
-			$this->emitter->emit('\OC\User', 'postDelete', [$this]);
328
-		}
329
-		$this->dispatcher->dispatchTyped(new UserDeletedEvent($this));
330
-
331
-		// Finally we can unset the delete flag and all other states
332
-		$this->config->deleteAllUserValues($this->uid);
333
-
334
-		return true;
335
-	}
336
-
337
-	/**
338
-	 * Set the password of the user
339
-	 *
340
-	 * @param string $password
341
-	 * @param string $recoveryPassword for the encryption app to reset encryption keys
342
-	 * @return bool
343
-	 */
344
-	public function setPassword($password, $recoveryPassword = null) {
345
-		$this->dispatcher->dispatchTyped(new BeforePasswordUpdatedEvent($this, $password, $recoveryPassword));
346
-		if ($this->emitter) {
347
-			$this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]);
348
-		}
349
-		if ($this->backend->implementsActions(Backend::SET_PASSWORD)) {
350
-			/** @var ISetPasswordBackend $backend */
351
-			$backend = $this->backend;
352
-			$result = $backend->setPassword($this->uid, $password);
353
-
354
-			if ($result !== false) {
355
-				$this->dispatcher->dispatchTyped(new PasswordUpdatedEvent($this, $password, $recoveryPassword));
356
-				if ($this->emitter) {
357
-					$this->emitter->emit('\OC\User', 'postSetPassword', [$this, $password, $recoveryPassword]);
358
-				}
359
-			}
360
-
361
-			return !($result === false);
362
-		} else {
363
-			return false;
364
-		}
365
-	}
366
-
367
-	public function getPasswordHash(): ?string {
368
-		if (!($this->backend instanceof IPasswordHashBackend)) {
369
-			return null;
370
-		}
371
-		return $this->backend->getPasswordHash($this->uid);
372
-	}
373
-
374
-	public function setPasswordHash(string $passwordHash): bool {
375
-		if (!($this->backend instanceof IPasswordHashBackend)) {
376
-			return false;
377
-		}
378
-		return $this->backend->setPasswordHash($this->uid, $passwordHash);
379
-	}
380
-
381
-	/**
382
-	 * get the users home folder to mount
383
-	 *
384
-	 * @return string
385
-	 */
386
-	public function getHome() {
387
-		if (!$this->home) {
388
-			/** @psalm-suppress UndefinedInterfaceMethod Once we get rid of the legacy implementsActions, psalm won't complain anymore */
389
-			if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->getHome($this->uid)) {
390
-				$this->home = $home;
391
-			} else {
392
-				$this->home = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid;
393
-			}
394
-		}
395
-		return $this->home;
396
-	}
397
-
398
-	/**
399
-	 * Get the name of the backend class the user is connected with
400
-	 *
401
-	 * @return string
402
-	 */
403
-	public function getBackendClassName() {
404
-		if ($this->backend instanceof IUserBackend) {
405
-			return $this->backend->getBackendName();
406
-		}
407
-		return get_class($this->backend);
408
-	}
409
-
410
-	public function getBackend(): ?UserInterface {
411
-		return $this->backend;
412
-	}
413
-
414
-	/**
415
-	 * Check if the backend allows the user to change their avatar on Personal page
416
-	 *
417
-	 * @return bool
418
-	 */
419
-	public function canChangeAvatar() {
420
-		if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
421
-			/** @var IProvideAvatarBackend $backend */
422
-			$backend = $this->backend;
423
-			return $backend->canChangeAvatar($this->uid);
424
-		}
425
-		return true;
426
-	}
427
-
428
-	/**
429
-	 * check if the backend supports changing passwords
430
-	 *
431
-	 * @return bool
432
-	 */
433
-	public function canChangePassword() {
434
-		return $this->backend->implementsActions(Backend::SET_PASSWORD);
435
-	}
436
-
437
-	/**
438
-	 * check if the backend supports changing display names
439
-	 *
440
-	 * @return bool
441
-	 */
442
-	public function canChangeDisplayName() {
443
-		if (!$this->config->getSystemValueBool('allow_user_to_change_display_name', true)) {
444
-			return false;
445
-		}
446
-		return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
447
-	}
448
-
449
-	public function canChangeEmail(): bool {
450
-		// Fallback to display name value to avoid changing behavior with the new option.
451
-		return $this->config->getSystemValueBool('allow_user_to_change_email', $this->config->getSystemValueBool('allow_user_to_change_display_name', true));
452
-	}
453
-
454
-	/**
455
-	 * check if the user is enabled
456
-	 *
457
-	 * @return bool
458
-	 */
459
-	public function isEnabled() {
460
-		$queryDatabaseValue = function (): bool {
461
-			if ($this->enabled === null) {
462
-				$enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
463
-				$this->enabled = $enabled === 'true';
464
-			}
465
-			return $this->enabled;
466
-		};
467
-		if ($this->backend instanceof IProvideEnabledStateBackend) {
468
-			return $this->backend->isUserEnabled($this->uid, $queryDatabaseValue);
469
-		} else {
470
-			return $queryDatabaseValue();
471
-		}
472
-	}
473
-
474
-	/**
475
-	 * set the enabled status for the user
476
-	 *
477
-	 * @return void
478
-	 */
479
-	public function setEnabled(bool $enabled = true) {
480
-		$oldStatus = $this->isEnabled();
481
-		$setDatabaseValue = function (bool $enabled): void {
482
-			$this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false');
483
-			$this->enabled = $enabled;
484
-		};
485
-		if ($this->backend instanceof IProvideEnabledStateBackend) {
486
-			$queryDatabaseValue = function (): bool {
487
-				if ($this->enabled === null) {
488
-					$enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
489
-					$this->enabled = $enabled === 'true';
490
-				}
491
-				return $this->enabled;
492
-			};
493
-			$enabled = $this->backend->setUserEnabled($this->uid, $enabled, $queryDatabaseValue, $setDatabaseValue);
494
-			if ($oldStatus !== $enabled) {
495
-				$this->triggerChange('enabled', $enabled, $oldStatus);
496
-			}
497
-		} elseif ($oldStatus !== $enabled) {
498
-			$setDatabaseValue($enabled);
499
-			$this->triggerChange('enabled', $enabled, $oldStatus);
500
-		}
501
-	}
502
-
503
-	/**
504
-	 * get the users email address
505
-	 *
506
-	 * @return string|null
507
-	 * @since 9.0.0
508
-	 */
509
-	public function getEMailAddress() {
510
-		return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress();
511
-	}
512
-
513
-	/**
514
-	 * @inheritDoc
515
-	 */
516
-	public function getSystemEMailAddress(): ?string {
517
-		return $this->config->getUserValue($this->uid, 'settings', 'email', null);
518
-	}
519
-
520
-	/**
521
-	 * @inheritDoc
522
-	 */
523
-	public function getPrimaryEMailAddress(): ?string {
524
-		return $this->config->getUserValue($this->uid, 'settings', 'primary_email', null);
525
-	}
526
-
527
-	/**
528
-	 * get the users' quota
529
-	 *
530
-	 * @return string
531
-	 * @since 9.0.0
532
-	 */
533
-	public function getQuota() {
534
-		// allow apps to modify the user quota by hooking into the event
535
-		$event = new GetQuotaEvent($this);
536
-		$this->dispatcher->dispatchTyped($event);
537
-		$overwriteQuota = $event->getQuota();
538
-		if ($overwriteQuota) {
539
-			$quota = $overwriteQuota;
540
-		} else {
541
-			$quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default');
542
-		}
543
-		if ($quota === 'default') {
544
-			$quota = $this->config->getAppValue('files', 'default_quota', 'none');
545
-
546
-			// if unlimited quota is not allowed => avoid getting 'unlimited' as default_quota fallback value
547
-			// use the first preset instead
548
-			$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
549
-			if (!$allowUnlimitedQuota) {
550
-				$presets = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
551
-				$presets = array_filter(array_map('trim', explode(',', $presets)));
552
-				$quotaPreset = array_values(array_diff($presets, ['default', 'none']));
553
-				if (count($quotaPreset) > 0) {
554
-					$quota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
555
-				}
556
-			}
557
-		}
558
-		return $quota;
559
-	}
560
-
561
-	public function getQuotaBytes(): int|float {
562
-		$quota = $this->getQuota();
563
-		if ($quota === 'none') {
564
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
565
-		}
566
-
567
-		$bytes = \OCP\Util::computerFileSize($quota);
568
-		if ($bytes === false) {
569
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
570
-		}
571
-		return $bytes;
572
-	}
573
-
574
-	/**
575
-	 * set the users' quota
576
-	 *
577
-	 * @param string $quota
578
-	 * @return void
579
-	 * @throws InvalidArgumentException
580
-	 * @since 9.0.0
581
-	 */
582
-	public function setQuota($quota) {
583
-		$oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
584
-		if ($quota !== 'none' and $quota !== 'default') {
585
-			$bytesQuota = \OCP\Util::computerFileSize($quota);
586
-			if ($bytesQuota === false) {
587
-				throw new InvalidArgumentException('Failed to set quota to invalid value ' . $quota);
588
-			}
589
-			$quota = \OCP\Util::humanFileSize($bytesQuota);
590
-		}
591
-		if ($quota !== $oldQuota) {
592
-			$this->config->setUserValue($this->uid, 'files', 'quota', $quota);
593
-			$this->triggerChange('quota', $quota, $oldQuota);
594
-		}
595
-		\OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
596
-	}
597
-
598
-	public function getManagerUids(): array {
599
-		$encodedUids = $this->config->getUserValue(
600
-			$this->uid,
601
-			'settings',
602
-			self::CONFIG_KEY_MANAGERS,
603
-			'[]'
604
-		);
605
-		return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
606
-	}
607
-
608
-	public function setManagerUids(array $uids): void {
609
-		$oldUids = $this->getManagerUids();
610
-		$this->config->setUserValue(
611
-			$this->uid,
612
-			'settings',
613
-			self::CONFIG_KEY_MANAGERS,
614
-			json_encode($uids, JSON_THROW_ON_ERROR)
615
-		);
616
-		$this->triggerChange('managers', $uids, $oldUids);
617
-	}
618
-
619
-	/**
620
-	 * get the avatar image if it exists
621
-	 *
622
-	 * @param int $size
623
-	 * @return IImage|null
624
-	 * @since 9.0.0
625
-	 */
626
-	public function getAvatarImage($size) {
627
-		// delay the initialization
628
-		if (is_null($this->avatarManager)) {
629
-			$this->avatarManager = \OC::$server->get(IAvatarManager::class);
630
-		}
631
-
632
-		$avatar = $this->avatarManager->getAvatar($this->uid);
633
-		$image = $avatar->get($size);
634
-		if ($image) {
635
-			return $image;
636
-		}
637
-
638
-		return null;
639
-	}
640
-
641
-	/**
642
-	 * get the federation cloud id
643
-	 *
644
-	 * @return string
645
-	 * @since 9.0.0
646
-	 */
647
-	public function getCloudId() {
648
-		$uid = $this->getUID();
649
-		$server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
650
-		if (str_ends_with($server, '/index.php')) {
651
-			$server = substr($server, 0, -10);
652
-		}
653
-		$server = $this->removeProtocolFromUrl($server);
654
-		return $uid . '@' . $server;
655
-	}
656
-
657
-	private function removeProtocolFromUrl(string $url): string {
658
-		if (str_starts_with($url, 'https://')) {
659
-			return substr($url, strlen('https://'));
660
-		}
661
-
662
-		return $url;
663
-	}
664
-
665
-	public function triggerChange($feature, $value = null, $oldValue = null) {
666
-		$this->dispatcher->dispatchTyped(new UserChangedEvent($this, $feature, $value, $oldValue));
667
-		if ($this->emitter) {
668
-			$this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
669
-		}
670
-	}
47
+    private const CONFIG_KEY_MANAGERS = 'manager';
48
+
49
+    private IConfig $config;
50
+    private IURLGenerator $urlGenerator;
51
+
52
+    /** @var IAccountManager */
53
+    protected $accountManager;
54
+
55
+    /** @var string|null */
56
+    private $displayName;
57
+
58
+    /** @var bool|null */
59
+    private $enabled;
60
+
61
+    /** @var Emitter|Manager|null */
62
+    private $emitter;
63
+
64
+    /** @var string */
65
+    private $home;
66
+
67
+    private ?int $lastLogin = null;
68
+    private ?int $firstLogin = null;
69
+
70
+    /** @var IAvatarManager */
71
+    private $avatarManager;
72
+
73
+    public function __construct(
74
+        private string $uid,
75
+        private ?UserInterface $backend,
76
+        private IEventDispatcher $dispatcher,
77
+        $emitter = null,
78
+        ?IConfig $config = null,
79
+        $urlGenerator = null,
80
+    ) {
81
+        $this->emitter = $emitter;
82
+        $this->config = $config ?? \OCP\Server::get(IConfig::class);
83
+        $this->urlGenerator = $urlGenerator ?? \OCP\Server::get(IURLGenerator::class);
84
+    }
85
+
86
+    /**
87
+     * get the user id
88
+     *
89
+     * @return string
90
+     */
91
+    public function getUID() {
92
+        return $this->uid;
93
+    }
94
+
95
+    /**
96
+     * get the display name for the user, if no specific display name is set it will fallback to the user id
97
+     *
98
+     * @return string
99
+     */
100
+    public function getDisplayName() {
101
+        if ($this->displayName === null) {
102
+            $displayName = '';
103
+            if ($this->backend && $this->backend->implementsActions(Backend::GET_DISPLAYNAME)) {
104
+                // get display name and strip whitespace from the beginning and end of it
105
+                $backendDisplayName = $this->backend->getDisplayName($this->uid);
106
+                if (is_string($backendDisplayName)) {
107
+                    $displayName = trim($backendDisplayName);
108
+                }
109
+            }
110
+
111
+            if (!empty($displayName)) {
112
+                $this->displayName = $displayName;
113
+            } else {
114
+                $this->displayName = $this->uid;
115
+            }
116
+        }
117
+        return $this->displayName;
118
+    }
119
+
120
+    /**
121
+     * set the displayname for the user
122
+     *
123
+     * @param string $displayName
124
+     * @return bool
125
+     *
126
+     * @since 25.0.0 Throw InvalidArgumentException
127
+     * @throws \InvalidArgumentException
128
+     */
129
+    public function setDisplayName($displayName) {
130
+        $displayName = trim($displayName);
131
+        $oldDisplayName = $this->getDisplayName();
132
+        if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) {
133
+            /** @var ISetDisplayNameBackend $backend */
134
+            $backend = $this->backend;
135
+            $result = $backend->setDisplayName($this->uid, $displayName);
136
+            if ($result) {
137
+                $this->displayName = $displayName;
138
+                $this->triggerChange('displayName', $displayName, $oldDisplayName);
139
+            }
140
+            return $result !== false;
141
+        }
142
+        return false;
143
+    }
144
+
145
+    /**
146
+     * @inheritDoc
147
+     */
148
+    public function setEMailAddress($mailAddress) {
149
+        $this->setSystemEMailAddress($mailAddress);
150
+    }
151
+
152
+    /**
153
+     * @inheritDoc
154
+     */
155
+    public function setSystemEMailAddress(string $mailAddress): void {
156
+        $oldMailAddress = $this->getSystemEMailAddress();
157
+
158
+        if ($mailAddress === '') {
159
+            $this->config->deleteUserValue($this->uid, 'settings', 'email');
160
+        } else {
161
+            $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress);
162
+        }
163
+
164
+        $primaryAddress = $this->getPrimaryEMailAddress();
165
+        if ($primaryAddress === $mailAddress) {
166
+            // on match no dedicated primary settings is necessary
167
+            $this->setPrimaryEMailAddress('');
168
+        }
169
+
170
+        if ($oldMailAddress !== strtolower($mailAddress)) {
171
+            $this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress);
172
+        }
173
+    }
174
+
175
+    /**
176
+     * @inheritDoc
177
+     */
178
+    public function setPrimaryEMailAddress(string $mailAddress): void {
179
+        if ($mailAddress === '') {
180
+            $this->config->deleteUserValue($this->uid, 'settings', 'primary_email');
181
+            return;
182
+        }
183
+
184
+        $this->ensureAccountManager();
185
+        $account = $this->accountManager->getAccount($this);
186
+        $property = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)
187
+            ->getPropertyByValue($mailAddress);
188
+
189
+        if ($property === null || $property->getLocallyVerified() !== IAccountManager::VERIFIED) {
190
+            throw new InvalidArgumentException('Only verified emails can be set as primary');
191
+        }
192
+        $this->config->setUserValue($this->uid, 'settings', 'primary_email', $mailAddress);
193
+    }
194
+
195
+    private function ensureAccountManager() {
196
+        if (!$this->accountManager instanceof IAccountManager) {
197
+            $this->accountManager = \OC::$server->get(IAccountManager::class);
198
+        }
199
+    }
200
+
201
+    /**
202
+     * returns the timestamp of the user's last login or 0 if the user did never
203
+     * login
204
+     */
205
+    public function getLastLogin(): int {
206
+        if ($this->lastLogin === null) {
207
+            $this->lastLogin = (int)$this->config->getUserValue($this->uid, 'login', 'lastLogin', 0);
208
+        }
209
+        return $this->lastLogin;
210
+    }
211
+
212
+    /**
213
+     * returns the timestamp of the user's last login or 0 if the user did never
214
+     * login
215
+     */
216
+    public function getFirstLogin(): int {
217
+        if ($this->firstLogin === null) {
218
+            $this->firstLogin = (int)$this->config->getUserValue($this->uid, 'login', 'firstLogin', 0);
219
+        }
220
+        return $this->firstLogin;
221
+    }
222
+
223
+    /**
224
+     * updates the timestamp of the most recent login of this user
225
+     */
226
+    public function updateLastLoginTimestamp(): bool {
227
+        $previousLogin = $this->getLastLogin();
228
+        $firstLogin = $this->getFirstLogin();
229
+        $now = time();
230
+        $firstTimeLogin = $previousLogin === 0;
231
+
232
+        if ($now - $previousLogin > 60) {
233
+            $this->lastLogin = $now;
234
+            $this->config->setUserValue($this->uid, 'login', 'lastLogin', (string)$this->lastLogin);
235
+        }
236
+
237
+        if ($firstLogin === 0) {
238
+            if ($firstTimeLogin) {
239
+                $this->firstLogin = $now;
240
+            } else {
241
+                /* Unknown first login, most likely was before upgrade to Nextcloud 31 */
242
+                $this->firstLogin = -1;
243
+            }
244
+            $this->config->setUserValue($this->uid, 'login', 'firstLogin', (string)$this->firstLogin);
245
+        }
246
+
247
+        return $firstTimeLogin;
248
+    }
249
+
250
+    /**
251
+     * Delete the user
252
+     *
253
+     * @return bool
254
+     */
255
+    public function delete() {
256
+        if ($this->backend === null) {
257
+            \OCP\Server::get(LoggerInterface::class)->error('Cannot delete user: No backend set');
258
+            return false;
259
+        }
260
+
261
+        if ($this->emitter) {
262
+            /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
263
+            $this->emitter->emit('\OC\User', 'preDelete', [$this]);
264
+        }
265
+        $this->dispatcher->dispatchTyped(new BeforeUserDeletedEvent($this));
266
+
267
+        // Set delete flag on the user - this is needed to ensure that the user data is removed if there happen any exception in the backend
268
+        // because we can not restore the user meaning we could not rollback to any stable state otherwise.
269
+        $this->config->setUserValue($this->uid, 'core', 'deleted', 'true');
270
+        // We also need to backup the home path as this can not be reconstructed later if the original backend uses custom home paths
271
+        $this->config->setUserValue($this->uid, 'core', 'deleted.home-path', $this->getHome());
272
+
273
+        // Try to delete the user on the backend
274
+        $result = $this->backend->deleteUser($this->uid);
275
+        if ($result === false) {
276
+            // The deletion was aborted or something else happened, we are in a defined state, so remove the delete flag
277
+            $this->config->deleteUserValue($this->uid, 'core', 'deleted');
278
+            return false;
279
+        }
280
+
281
+        // We have to delete the user from all groups
282
+        $groupManager = \OCP\Server::get(IGroupManager::class);
283
+        foreach ($groupManager->getUserGroupIds($this) as $groupId) {
284
+            $group = $groupManager->get($groupId);
285
+            if ($group) {
286
+                $this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this));
287
+                $group->removeUser($this);
288
+                $this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this));
289
+            }
290
+        }
291
+
292
+        $commentsManager = \OCP\Server::get(ICommentsManager::class);
293
+        $commentsManager->deleteReferencesOfActor('users', $this->uid);
294
+        $commentsManager->deleteReadMarksFromUser($this);
295
+
296
+        $avatarManager = \OCP\Server::get(AvatarManager::class);
297
+        $avatarManager->deleteUserAvatar($this->uid);
298
+
299
+        $notificationManager = \OCP\Server::get(INotificationManager::class);
300
+        $notification = $notificationManager->createNotification();
301
+        $notification->setUser($this->uid);
302
+        $notificationManager->markProcessed($notification);
303
+
304
+        $accountManager = \OCP\Server::get(AccountManager::class);
305
+        $accountManager->deleteUser($this);
306
+
307
+        $database = \OCP\Server::get(IDBConnection::class);
308
+        try {
309
+            // We need to create a transaction to make sure we are in a defined state
310
+            // because if all user values are removed also the flag is gone, but if an exception happens (e.g. database lost connection on the set operation)
311
+            // exactly here we are in an undefined state as the data is still present but the user does not exist on the system anymore.
312
+            $database->beginTransaction();
313
+            // Remove all user settings
314
+            $this->config->deleteAllUserValues($this->uid);
315
+            // But again set flag that this user is about to be deleted
316
+            $this->config->setUserValue($this->uid, 'core', 'deleted', 'true');
317
+            $this->config->setUserValue($this->uid, 'core', 'deleted.home-path', $this->getHome());
318
+            // Commit the transaction so we are in a defined state: either the preferences are removed or an exception occurred but the delete flag is still present
319
+            $database->commit();
320
+        } catch (\Throwable $e) {
321
+            $database->rollback();
322
+            throw $e;
323
+        }
324
+
325
+        if ($this->emitter !== null) {
326
+            /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
327
+            $this->emitter->emit('\OC\User', 'postDelete', [$this]);
328
+        }
329
+        $this->dispatcher->dispatchTyped(new UserDeletedEvent($this));
330
+
331
+        // Finally we can unset the delete flag and all other states
332
+        $this->config->deleteAllUserValues($this->uid);
333
+
334
+        return true;
335
+    }
336
+
337
+    /**
338
+     * Set the password of the user
339
+     *
340
+     * @param string $password
341
+     * @param string $recoveryPassword for the encryption app to reset encryption keys
342
+     * @return bool
343
+     */
344
+    public function setPassword($password, $recoveryPassword = null) {
345
+        $this->dispatcher->dispatchTyped(new BeforePasswordUpdatedEvent($this, $password, $recoveryPassword));
346
+        if ($this->emitter) {
347
+            $this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]);
348
+        }
349
+        if ($this->backend->implementsActions(Backend::SET_PASSWORD)) {
350
+            /** @var ISetPasswordBackend $backend */
351
+            $backend = $this->backend;
352
+            $result = $backend->setPassword($this->uid, $password);
353
+
354
+            if ($result !== false) {
355
+                $this->dispatcher->dispatchTyped(new PasswordUpdatedEvent($this, $password, $recoveryPassword));
356
+                if ($this->emitter) {
357
+                    $this->emitter->emit('\OC\User', 'postSetPassword', [$this, $password, $recoveryPassword]);
358
+                }
359
+            }
360
+
361
+            return !($result === false);
362
+        } else {
363
+            return false;
364
+        }
365
+    }
366
+
367
+    public function getPasswordHash(): ?string {
368
+        if (!($this->backend instanceof IPasswordHashBackend)) {
369
+            return null;
370
+        }
371
+        return $this->backend->getPasswordHash($this->uid);
372
+    }
373
+
374
+    public function setPasswordHash(string $passwordHash): bool {
375
+        if (!($this->backend instanceof IPasswordHashBackend)) {
376
+            return false;
377
+        }
378
+        return $this->backend->setPasswordHash($this->uid, $passwordHash);
379
+    }
380
+
381
+    /**
382
+     * get the users home folder to mount
383
+     *
384
+     * @return string
385
+     */
386
+    public function getHome() {
387
+        if (!$this->home) {
388
+            /** @psalm-suppress UndefinedInterfaceMethod Once we get rid of the legacy implementsActions, psalm won't complain anymore */
389
+            if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->getHome($this->uid)) {
390
+                $this->home = $home;
391
+            } else {
392
+                $this->home = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid;
393
+            }
394
+        }
395
+        return $this->home;
396
+    }
397
+
398
+    /**
399
+     * Get the name of the backend class the user is connected with
400
+     *
401
+     * @return string
402
+     */
403
+    public function getBackendClassName() {
404
+        if ($this->backend instanceof IUserBackend) {
405
+            return $this->backend->getBackendName();
406
+        }
407
+        return get_class($this->backend);
408
+    }
409
+
410
+    public function getBackend(): ?UserInterface {
411
+        return $this->backend;
412
+    }
413
+
414
+    /**
415
+     * Check if the backend allows the user to change their avatar on Personal page
416
+     *
417
+     * @return bool
418
+     */
419
+    public function canChangeAvatar() {
420
+        if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
421
+            /** @var IProvideAvatarBackend $backend */
422
+            $backend = $this->backend;
423
+            return $backend->canChangeAvatar($this->uid);
424
+        }
425
+        return true;
426
+    }
427
+
428
+    /**
429
+     * check if the backend supports changing passwords
430
+     *
431
+     * @return bool
432
+     */
433
+    public function canChangePassword() {
434
+        return $this->backend->implementsActions(Backend::SET_PASSWORD);
435
+    }
436
+
437
+    /**
438
+     * check if the backend supports changing display names
439
+     *
440
+     * @return bool
441
+     */
442
+    public function canChangeDisplayName() {
443
+        if (!$this->config->getSystemValueBool('allow_user_to_change_display_name', true)) {
444
+            return false;
445
+        }
446
+        return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
447
+    }
448
+
449
+    public function canChangeEmail(): bool {
450
+        // Fallback to display name value to avoid changing behavior with the new option.
451
+        return $this->config->getSystemValueBool('allow_user_to_change_email', $this->config->getSystemValueBool('allow_user_to_change_display_name', true));
452
+    }
453
+
454
+    /**
455
+     * check if the user is enabled
456
+     *
457
+     * @return bool
458
+     */
459
+    public function isEnabled() {
460
+        $queryDatabaseValue = function (): bool {
461
+            if ($this->enabled === null) {
462
+                $enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
463
+                $this->enabled = $enabled === 'true';
464
+            }
465
+            return $this->enabled;
466
+        };
467
+        if ($this->backend instanceof IProvideEnabledStateBackend) {
468
+            return $this->backend->isUserEnabled($this->uid, $queryDatabaseValue);
469
+        } else {
470
+            return $queryDatabaseValue();
471
+        }
472
+    }
473
+
474
+    /**
475
+     * set the enabled status for the user
476
+     *
477
+     * @return void
478
+     */
479
+    public function setEnabled(bool $enabled = true) {
480
+        $oldStatus = $this->isEnabled();
481
+        $setDatabaseValue = function (bool $enabled): void {
482
+            $this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false');
483
+            $this->enabled = $enabled;
484
+        };
485
+        if ($this->backend instanceof IProvideEnabledStateBackend) {
486
+            $queryDatabaseValue = function (): bool {
487
+                if ($this->enabled === null) {
488
+                    $enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
489
+                    $this->enabled = $enabled === 'true';
490
+                }
491
+                return $this->enabled;
492
+            };
493
+            $enabled = $this->backend->setUserEnabled($this->uid, $enabled, $queryDatabaseValue, $setDatabaseValue);
494
+            if ($oldStatus !== $enabled) {
495
+                $this->triggerChange('enabled', $enabled, $oldStatus);
496
+            }
497
+        } elseif ($oldStatus !== $enabled) {
498
+            $setDatabaseValue($enabled);
499
+            $this->triggerChange('enabled', $enabled, $oldStatus);
500
+        }
501
+    }
502
+
503
+    /**
504
+     * get the users email address
505
+     *
506
+     * @return string|null
507
+     * @since 9.0.0
508
+     */
509
+    public function getEMailAddress() {
510
+        return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress();
511
+    }
512
+
513
+    /**
514
+     * @inheritDoc
515
+     */
516
+    public function getSystemEMailAddress(): ?string {
517
+        return $this->config->getUserValue($this->uid, 'settings', 'email', null);
518
+    }
519
+
520
+    /**
521
+     * @inheritDoc
522
+     */
523
+    public function getPrimaryEMailAddress(): ?string {
524
+        return $this->config->getUserValue($this->uid, 'settings', 'primary_email', null);
525
+    }
526
+
527
+    /**
528
+     * get the users' quota
529
+     *
530
+     * @return string
531
+     * @since 9.0.0
532
+     */
533
+    public function getQuota() {
534
+        // allow apps to modify the user quota by hooking into the event
535
+        $event = new GetQuotaEvent($this);
536
+        $this->dispatcher->dispatchTyped($event);
537
+        $overwriteQuota = $event->getQuota();
538
+        if ($overwriteQuota) {
539
+            $quota = $overwriteQuota;
540
+        } else {
541
+            $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default');
542
+        }
543
+        if ($quota === 'default') {
544
+            $quota = $this->config->getAppValue('files', 'default_quota', 'none');
545
+
546
+            // if unlimited quota is not allowed => avoid getting 'unlimited' as default_quota fallback value
547
+            // use the first preset instead
548
+            $allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
549
+            if (!$allowUnlimitedQuota) {
550
+                $presets = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
551
+                $presets = array_filter(array_map('trim', explode(',', $presets)));
552
+                $quotaPreset = array_values(array_diff($presets, ['default', 'none']));
553
+                if (count($quotaPreset) > 0) {
554
+                    $quota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
555
+                }
556
+            }
557
+        }
558
+        return $quota;
559
+    }
560
+
561
+    public function getQuotaBytes(): int|float {
562
+        $quota = $this->getQuota();
563
+        if ($quota === 'none') {
564
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
565
+        }
566
+
567
+        $bytes = \OCP\Util::computerFileSize($quota);
568
+        if ($bytes === false) {
569
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
570
+        }
571
+        return $bytes;
572
+    }
573
+
574
+    /**
575
+     * set the users' quota
576
+     *
577
+     * @param string $quota
578
+     * @return void
579
+     * @throws InvalidArgumentException
580
+     * @since 9.0.0
581
+     */
582
+    public function setQuota($quota) {
583
+        $oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
584
+        if ($quota !== 'none' and $quota !== 'default') {
585
+            $bytesQuota = \OCP\Util::computerFileSize($quota);
586
+            if ($bytesQuota === false) {
587
+                throw new InvalidArgumentException('Failed to set quota to invalid value ' . $quota);
588
+            }
589
+            $quota = \OCP\Util::humanFileSize($bytesQuota);
590
+        }
591
+        if ($quota !== $oldQuota) {
592
+            $this->config->setUserValue($this->uid, 'files', 'quota', $quota);
593
+            $this->triggerChange('quota', $quota, $oldQuota);
594
+        }
595
+        \OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
596
+    }
597
+
598
+    public function getManagerUids(): array {
599
+        $encodedUids = $this->config->getUserValue(
600
+            $this->uid,
601
+            'settings',
602
+            self::CONFIG_KEY_MANAGERS,
603
+            '[]'
604
+        );
605
+        return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
606
+    }
607
+
608
+    public function setManagerUids(array $uids): void {
609
+        $oldUids = $this->getManagerUids();
610
+        $this->config->setUserValue(
611
+            $this->uid,
612
+            'settings',
613
+            self::CONFIG_KEY_MANAGERS,
614
+            json_encode($uids, JSON_THROW_ON_ERROR)
615
+        );
616
+        $this->triggerChange('managers', $uids, $oldUids);
617
+    }
618
+
619
+    /**
620
+     * get the avatar image if it exists
621
+     *
622
+     * @param int $size
623
+     * @return IImage|null
624
+     * @since 9.0.0
625
+     */
626
+    public function getAvatarImage($size) {
627
+        // delay the initialization
628
+        if (is_null($this->avatarManager)) {
629
+            $this->avatarManager = \OC::$server->get(IAvatarManager::class);
630
+        }
631
+
632
+        $avatar = $this->avatarManager->getAvatar($this->uid);
633
+        $image = $avatar->get($size);
634
+        if ($image) {
635
+            return $image;
636
+        }
637
+
638
+        return null;
639
+    }
640
+
641
+    /**
642
+     * get the federation cloud id
643
+     *
644
+     * @return string
645
+     * @since 9.0.0
646
+     */
647
+    public function getCloudId() {
648
+        $uid = $this->getUID();
649
+        $server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
650
+        if (str_ends_with($server, '/index.php')) {
651
+            $server = substr($server, 0, -10);
652
+        }
653
+        $server = $this->removeProtocolFromUrl($server);
654
+        return $uid . '@' . $server;
655
+    }
656
+
657
+    private function removeProtocolFromUrl(string $url): string {
658
+        if (str_starts_with($url, 'https://')) {
659
+            return substr($url, strlen('https://'));
660
+        }
661
+
662
+        return $url;
663
+    }
664
+
665
+    public function triggerChange($feature, $value = null, $oldValue = null) {
666
+        $this->dispatcher->dispatchTyped(new UserChangedEvent($this, $feature, $value, $oldValue));
667
+        if ($this->emitter) {
668
+            $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
669
+        }
670
+    }
671 671
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -204,7 +204,7 @@  discard block
 block discarded – undo
204 204
 	 */
205 205
 	public function getLastLogin(): int {
206 206
 		if ($this->lastLogin === null) {
207
-			$this->lastLogin = (int)$this->config->getUserValue($this->uid, 'login', 'lastLogin', 0);
207
+			$this->lastLogin = (int) $this->config->getUserValue($this->uid, 'login', 'lastLogin', 0);
208 208
 		}
209 209
 		return $this->lastLogin;
210 210
 	}
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
 	 */
216 216
 	public function getFirstLogin(): int {
217 217
 		if ($this->firstLogin === null) {
218
-			$this->firstLogin = (int)$this->config->getUserValue($this->uid, 'login', 'firstLogin', 0);
218
+			$this->firstLogin = (int) $this->config->getUserValue($this->uid, 'login', 'firstLogin', 0);
219 219
 		}
220 220
 		return $this->firstLogin;
221 221
 	}
@@ -231,7 +231,7 @@  discard block
 block discarded – undo
231 231
 
232 232
 		if ($now - $previousLogin > 60) {
233 233
 			$this->lastLogin = $now;
234
-			$this->config->setUserValue($this->uid, 'login', 'lastLogin', (string)$this->lastLogin);
234
+			$this->config->setUserValue($this->uid, 'login', 'lastLogin', (string) $this->lastLogin);
235 235
 		}
236 236
 
237 237
 		if ($firstLogin === 0) {
@@ -241,7 +241,7 @@  discard block
 block discarded – undo
241 241
 				/* Unknown first login, most likely was before upgrade to Nextcloud 31 */
242 242
 				$this->firstLogin = -1;
243 243
 			}
244
-			$this->config->setUserValue($this->uid, 'login', 'firstLogin', (string)$this->firstLogin);
244
+			$this->config->setUserValue($this->uid, 'login', 'firstLogin', (string) $this->firstLogin);
245 245
 		}
246 246
 
247 247
 		return $firstTimeLogin;
@@ -389,7 +389,7 @@  discard block
 block discarded – undo
389 389
 			if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->getHome($this->uid)) {
390 390
 				$this->home = $home;
391 391
 			} else {
392
-				$this->home = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid;
392
+				$this->home = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT.'/data').'/'.$this->uid;
393 393
 			}
394 394
 		}
395 395
 		return $this->home;
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 	 * @return bool
458 458
 	 */
459 459
 	public function isEnabled() {
460
-		$queryDatabaseValue = function (): bool {
460
+		$queryDatabaseValue = function(): bool {
461 461
 			if ($this->enabled === null) {
462 462
 				$enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
463 463
 				$this->enabled = $enabled === 'true';
@@ -478,12 +478,12 @@  discard block
 block discarded – undo
478 478
 	 */
479 479
 	public function setEnabled(bool $enabled = true) {
480 480
 		$oldStatus = $this->isEnabled();
481
-		$setDatabaseValue = function (bool $enabled): void {
481
+		$setDatabaseValue = function(bool $enabled): void {
482 482
 			$this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false');
483 483
 			$this->enabled = $enabled;
484 484
 		};
485 485
 		if ($this->backend instanceof IProvideEnabledStateBackend) {
486
-			$queryDatabaseValue = function (): bool {
486
+			$queryDatabaseValue = function(): bool {
487 487
 				if ($this->enabled === null) {
488 488
 					$enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
489 489
 					$this->enabled = $enabled === 'true';
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
 		return $quota;
559 559
 	}
560 560
 
561
-	public function getQuotaBytes(): int|float {
561
+	public function getQuotaBytes(): int | float {
562 562
 		$quota = $this->getQuota();
563 563
 		if ($quota === 'none') {
564 564
 			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
@@ -584,7 +584,7 @@  discard block
 block discarded – undo
584 584
 		if ($quota !== 'none' and $quota !== 'default') {
585 585
 			$bytesQuota = \OCP\Util::computerFileSize($quota);
586 586
 			if ($bytesQuota === false) {
587
-				throw new InvalidArgumentException('Failed to set quota to invalid value ' . $quota);
587
+				throw new InvalidArgumentException('Failed to set quota to invalid value '.$quota);
588 588
 			}
589 589
 			$quota = \OCP\Util::humanFileSize($bytesQuota);
590 590
 		}
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
 			$this->config->setUserValue($this->uid, 'files', 'quota', $quota);
593 593
 			$this->triggerChange('quota', $quota, $oldQuota);
594 594
 		}
595
-		\OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
595
+		\OC_Helper::clearStorageInfo('/'.$this->uid.'/files');
596 596
 	}
597 597
 
598 598
 	public function getManagerUids(): array {
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
 			$server = substr($server, 0, -10);
652 652
 		}
653 653
 		$server = $this->removeProtocolFromUrl($server);
654
-		return $uid . '@' . $server;
654
+		return $uid.'@'.$server;
655 655
 	}
656 656
 
657 657
 	private function removeProtocolFromUrl(string $url): string {
Please login to merge, or discard this patch.
lib/private/Files/SetupManager.php 2 patches
Indentation   +542 added lines, -542 removed lines patch added patch discarded remove patch
@@ -55,548 +55,548 @@
 block discarded – undo
55 55
 use Psr\Log\LoggerInterface;
56 56
 
57 57
 class SetupManager {
58
-	private bool $rootSetup = false;
59
-	// List of users for which at least one mount is setup
60
-	private array $setupUsers = [];
61
-	// List of users for which all mounts are setup
62
-	private array $setupUsersComplete = [];
63
-	/** @var array<string, string[]> */
64
-	private array $setupUserMountProviders = [];
65
-	private ICache $cache;
66
-	private bool $listeningForProviders;
67
-	private array $fullSetupRequired = [];
68
-	private bool $setupBuiltinWrappersDone = false;
69
-	private bool $forceFullSetup = false;
70
-
71
-	public function __construct(
72
-		private IEventLogger $eventLogger,
73
-		private MountProviderCollection $mountProviderCollection,
74
-		private IMountManager $mountManager,
75
-		private IUserManager $userManager,
76
-		private IEventDispatcher $eventDispatcher,
77
-		private IUserMountCache $userMountCache,
78
-		private ILockdownManager $lockdownManager,
79
-		private IUserSession $userSession,
80
-		ICacheFactory $cacheFactory,
81
-		private LoggerInterface $logger,
82
-		private IConfig $config,
83
-		private ShareDisableChecker $shareDisableChecker,
84
-	) {
85
-		$this->cache = $cacheFactory->createDistributed('setupmanager::');
86
-		$this->listeningForProviders = false;
87
-		$this->forceFullSetup = $this->config->getSystemValueBool('debug.force-full-fs-setup');
88
-
89
-		$this->setupListeners();
90
-	}
91
-
92
-	private function isSetupStarted(IUser $user): bool {
93
-		return in_array($user->getUID(), $this->setupUsers, true);
94
-	}
95
-
96
-	public function isSetupComplete(IUser $user): bool {
97
-		return in_array($user->getUID(), $this->setupUsersComplete, true);
98
-	}
99
-
100
-	private function setupBuiltinWrappers() {
101
-		if ($this->setupBuiltinWrappersDone) {
102
-			return;
103
-		}
104
-		$this->setupBuiltinWrappersDone = true;
105
-
106
-		// load all filesystem apps before, so no setup-hook gets lost
107
-		OC_App::loadApps(['filesystem']);
108
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
109
-
110
-		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
111
-			if ($storage->instanceOfStorage(Common::class)) {
112
-				$options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
113
-				$storage->setMountOptions($options);
114
-			}
115
-			return $storage;
116
-		});
117
-
118
-		$reSharingEnabled = Share::isResharingAllowed();
119
-		$user = $this->userSession->getUser();
120
-		$sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
121
-		Filesystem::addStorageWrapper(
122
-			'sharing_mask',
123
-			function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
124
-				$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
125
-				$isShared = $mount instanceof ISharedMountPoint;
126
-				if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
127
-					return new PermissionsMask([
128
-						'storage' => $storage,
129
-						'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
130
-					]);
131
-				}
132
-				return $storage;
133
-			}
134
-		);
135
-
136
-		// install storage availability wrapper, before most other wrappers
137
-		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
138
-			$externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
139
-			if ($externalMount && !$storage->isLocal()) {
140
-				return new Availability(['storage' => $storage]);
141
-			}
142
-			return $storage;
143
-		});
144
-
145
-		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
146
-			if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
147
-				return new Encoding(['storage' => $storage]);
148
-			}
149
-			return $storage;
150
-		});
151
-
152
-		$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
153
-		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
154
-			// set up quota for home storages, even for other users
155
-			// which can happen when using sharing
156
-			if ($mount instanceof HomeMountPoint) {
157
-				$user = $mount->getUser();
158
-				return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
159
-					return $user->getQuotaBytes();
160
-				}, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
161
-			}
162
-
163
-			return $storage;
164
-		});
165
-
166
-		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
167
-			/*
58
+    private bool $rootSetup = false;
59
+    // List of users for which at least one mount is setup
60
+    private array $setupUsers = [];
61
+    // List of users for which all mounts are setup
62
+    private array $setupUsersComplete = [];
63
+    /** @var array<string, string[]> */
64
+    private array $setupUserMountProviders = [];
65
+    private ICache $cache;
66
+    private bool $listeningForProviders;
67
+    private array $fullSetupRequired = [];
68
+    private bool $setupBuiltinWrappersDone = false;
69
+    private bool $forceFullSetup = false;
70
+
71
+    public function __construct(
72
+        private IEventLogger $eventLogger,
73
+        private MountProviderCollection $mountProviderCollection,
74
+        private IMountManager $mountManager,
75
+        private IUserManager $userManager,
76
+        private IEventDispatcher $eventDispatcher,
77
+        private IUserMountCache $userMountCache,
78
+        private ILockdownManager $lockdownManager,
79
+        private IUserSession $userSession,
80
+        ICacheFactory $cacheFactory,
81
+        private LoggerInterface $logger,
82
+        private IConfig $config,
83
+        private ShareDisableChecker $shareDisableChecker,
84
+    ) {
85
+        $this->cache = $cacheFactory->createDistributed('setupmanager::');
86
+        $this->listeningForProviders = false;
87
+        $this->forceFullSetup = $this->config->getSystemValueBool('debug.force-full-fs-setup');
88
+
89
+        $this->setupListeners();
90
+    }
91
+
92
+    private function isSetupStarted(IUser $user): bool {
93
+        return in_array($user->getUID(), $this->setupUsers, true);
94
+    }
95
+
96
+    public function isSetupComplete(IUser $user): bool {
97
+        return in_array($user->getUID(), $this->setupUsersComplete, true);
98
+    }
99
+
100
+    private function setupBuiltinWrappers() {
101
+        if ($this->setupBuiltinWrappersDone) {
102
+            return;
103
+        }
104
+        $this->setupBuiltinWrappersDone = true;
105
+
106
+        // load all filesystem apps before, so no setup-hook gets lost
107
+        OC_App::loadApps(['filesystem']);
108
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
109
+
110
+        Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
111
+            if ($storage->instanceOfStorage(Common::class)) {
112
+                $options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
113
+                $storage->setMountOptions($options);
114
+            }
115
+            return $storage;
116
+        });
117
+
118
+        $reSharingEnabled = Share::isResharingAllowed();
119
+        $user = $this->userSession->getUser();
120
+        $sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
121
+        Filesystem::addStorageWrapper(
122
+            'sharing_mask',
123
+            function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
124
+                $sharingEnabledForMount = $mount->getOption('enable_sharing', true);
125
+                $isShared = $mount instanceof ISharedMountPoint;
126
+                if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
127
+                    return new PermissionsMask([
128
+                        'storage' => $storage,
129
+                        'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
130
+                    ]);
131
+                }
132
+                return $storage;
133
+            }
134
+        );
135
+
136
+        // install storage availability wrapper, before most other wrappers
137
+        Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
138
+            $externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
139
+            if ($externalMount && !$storage->isLocal()) {
140
+                return new Availability(['storage' => $storage]);
141
+            }
142
+            return $storage;
143
+        });
144
+
145
+        Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
146
+            if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
147
+                return new Encoding(['storage' => $storage]);
148
+            }
149
+            return $storage;
150
+        });
151
+
152
+        $quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
153
+        Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
154
+            // set up quota for home storages, even for other users
155
+            // which can happen when using sharing
156
+            if ($mount instanceof HomeMountPoint) {
157
+                $user = $mount->getUser();
158
+                return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
159
+                    return $user->getQuotaBytes();
160
+                }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
161
+            }
162
+
163
+            return $storage;
164
+        });
165
+
166
+        Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
167
+            /*
168 168
 			 * Do not allow any operations that modify the storage
169 169
 			 */
170
-			if ($mount->getOption('readonly', false)) {
171
-				return new PermissionsMask([
172
-					'storage' => $storage,
173
-					'mask' => Constants::PERMISSION_ALL & ~(
174
-						Constants::PERMISSION_UPDATE |
175
-						Constants::PERMISSION_CREATE |
176
-						Constants::PERMISSION_DELETE
177
-					),
178
-				]);
179
-			}
180
-			return $storage;
181
-		});
182
-
183
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
184
-	}
185
-
186
-	/**
187
-	 * Setup the full filesystem for the specified user
188
-	 */
189
-	public function setupForUser(IUser $user): void {
190
-		if ($this->isSetupComplete($user)) {
191
-			return;
192
-		}
193
-		$this->setupUsersComplete[] = $user->getUID();
194
-
195
-		$this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
196
-
197
-		if (!isset($this->setupUserMountProviders[$user->getUID()])) {
198
-			$this->setupUserMountProviders[$user->getUID()] = [];
199
-		}
200
-
201
-		$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
202
-
203
-		$this->setupForUserWith($user, function () use ($user) {
204
-			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
205
-				IMountProvider $provider,
206
-			) use ($user) {
207
-				return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]);
208
-			});
209
-		});
210
-		$this->afterUserFullySetup($user, $previouslySetupProviders);
211
-		$this->eventLogger->end('fs:setup:user:full');
212
-	}
213
-
214
-	/**
215
-	 * part of the user setup that is run only once per user
216
-	 */
217
-	private function oneTimeUserSetup(IUser $user) {
218
-		if ($this->isSetupStarted($user)) {
219
-			return;
220
-		}
221
-		$this->setupUsers[] = $user->getUID();
222
-
223
-		$this->setupRoot();
224
-
225
-		$this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
226
-
227
-		$this->setupBuiltinWrappers();
228
-
229
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
230
-
231
-		// TODO remove hook
232
-		OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
233
-
234
-		$event = new BeforeFileSystemSetupEvent($user);
235
-		$this->eventDispatcher->dispatchTyped($event);
236
-
237
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
238
-
239
-		$userDir = '/' . $user->getUID() . '/files';
240
-
241
-		Filesystem::initInternal($userDir);
242
-
243
-		if ($this->lockdownManager->canAccessFilesystem()) {
244
-			$this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
245
-			// home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
246
-			$homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
247
-			$this->mountManager->addMount($homeMount);
248
-
249
-			if ($homeMount->getStorageRootId() === -1) {
250
-				$this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
251
-				$homeMount->getStorage()->mkdir('');
252
-				$homeMount->getStorage()->getScanner()->scan('');
253
-				$this->eventLogger->end('fs:setup:user:home:scan');
254
-			}
255
-			$this->eventLogger->end('fs:setup:user:home');
256
-		} else {
257
-			$this->mountManager->addMount(new MountPoint(
258
-				new NullStorage([]),
259
-				'/' . $user->getUID()
260
-			));
261
-			$this->mountManager->addMount(new MountPoint(
262
-				new NullStorage([]),
263
-				'/' . $user->getUID() . '/files'
264
-			));
265
-			$this->setupUsersComplete[] = $user->getUID();
266
-		}
267
-
268
-		$this->listenForNewMountProviders();
269
-
270
-		$this->eventLogger->end('fs:setup:user:onetime');
271
-	}
272
-
273
-	/**
274
-	 * Final housekeeping after a user has been fully setup
275
-	 */
276
-	private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
277
-		$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
278
-		$userRoot = '/' . $user->getUID() . '/';
279
-		$mounts = $this->mountManager->getAll();
280
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
281
-			return str_starts_with($mount->getMountPoint(), $userRoot);
282
-		});
283
-		$allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
284
-			return get_class($provider);
285
-		}, array_merge(
286
-			$this->mountProviderCollection->getProviders(),
287
-			$this->mountProviderCollection->getHomeProviders(),
288
-			$this->mountProviderCollection->getRootProviders(),
289
-		));
290
-		$newProviders = array_diff($allProviders, $previouslySetupProviders);
291
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
292
-			return !in_array($mount->getMountProvider(), $previouslySetupProviders);
293
-		});
294
-		$this->userMountCache->registerMounts($user, $mounts, $newProviders);
295
-
296
-		$cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
297
-		if ($cacheDuration > 0) {
298
-			$this->cache->set($user->getUID(), true, $cacheDuration);
299
-			$this->fullSetupRequired[$user->getUID()] = false;
300
-		}
301
-		$this->eventLogger->end('fs:setup:user:full:post');
302
-	}
303
-
304
-	/**
305
-	 * @param IUser $user
306
-	 * @param IMountPoint $mounts
307
-	 * @return void
308
-	 * @throws \OCP\HintException
309
-	 * @throws \OC\ServerNotAvailableException
310
-	 */
311
-	private function setupForUserWith(IUser $user, callable $mountCallback): void {
312
-		$this->oneTimeUserSetup($user);
313
-
314
-		if ($this->lockdownManager->canAccessFilesystem()) {
315
-			$mountCallback();
316
-		}
317
-		$this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
318
-		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
319
-		$this->eventLogger->end('fs:setup:user:post-init-mountpoint');
320
-
321
-		$userDir = '/' . $user->getUID() . '/files';
322
-		$this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
323
-		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
324
-		$this->eventLogger->end('fs:setup:user:setup-hook');
325
-	}
326
-
327
-	/**
328
-	 * Set up the root filesystem
329
-	 */
330
-	public function setupRoot(): void {
331
-		//setting up the filesystem twice can only lead to trouble
332
-		if ($this->rootSetup) {
333
-			return;
334
-		}
335
-
336
-		$this->setupBuiltinWrappers();
337
-
338
-		$this->rootSetup = true;
339
-
340
-		$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
341
-
342
-		$rootMounts = $this->mountProviderCollection->getRootMounts();
343
-		foreach ($rootMounts as $rootMountProvider) {
344
-			$this->mountManager->addMount($rootMountProvider);
345
-		}
346
-
347
-		$this->eventLogger->end('fs:setup:root');
348
-	}
349
-
350
-	/**
351
-	 * Get the user to setup for a path or `null` if the root needs to be setup
352
-	 *
353
-	 * @param string $path
354
-	 * @return IUser|null
355
-	 */
356
-	private function getUserForPath(string $path) {
357
-		if (str_starts_with($path, '/__groupfolders')) {
358
-			return null;
359
-		} elseif (substr_count($path, '/') < 2) {
360
-			if ($user = $this->userSession->getUser()) {
361
-				return $user;
362
-			} else {
363
-				return null;
364
-			}
365
-		} elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
366
-			return null;
367
-		} else {
368
-			[, $userId] = explode('/', $path);
369
-		}
370
-
371
-		return $this->userManager->get($userId);
372
-	}
373
-
374
-	/**
375
-	 * Set up the filesystem for the specified path
376
-	 */
377
-	public function setupForPath(string $path, bool $includeChildren = false): void {
378
-		$user = $this->getUserForPath($path);
379
-		if (!$user) {
380
-			$this->setupRoot();
381
-			return;
382
-		}
383
-
384
-		if ($this->isSetupComplete($user)) {
385
-			return;
386
-		}
387
-
388
-		if ($this->fullSetupRequired($user)) {
389
-			$this->setupForUser($user);
390
-			return;
391
-		}
392
-
393
-		// for the user's home folder, and includes children we need everything always
394
-		if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
395
-			$this->setupForUser($user);
396
-			return;
397
-		}
398
-
399
-		if (!isset($this->setupUserMountProviders[$user->getUID()])) {
400
-			$this->setupUserMountProviders[$user->getUID()] = [];
401
-		}
402
-		$setupProviders = &$this->setupUserMountProviders[$user->getUID()];
403
-		$currentProviders = [];
404
-
405
-		try {
406
-			$cachedMount = $this->userMountCache->getMountForPath($user, $path);
407
-		} catch (NotFoundException $e) {
408
-			$this->setupForUser($user);
409
-			return;
410
-		}
411
-
412
-		$this->oneTimeUserSetup($user);
413
-
414
-		$this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
415
-		$this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
416
-
417
-		$mounts = [];
418
-		if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
419
-			$currentProviders[] = $cachedMount->getMountProvider();
420
-			if ($cachedMount->getMountProvider()) {
421
-				$setupProviders[] = $cachedMount->getMountProvider();
422
-				$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]);
423
-			} else {
424
-				$this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
425
-				$this->eventLogger->end('fs:setup:user:path:find');
426
-				$this->setupForUser($user);
427
-				$this->eventLogger->end('fs:setup:user:path');
428
-				return;
429
-			}
430
-		}
431
-
432
-		if ($includeChildren) {
433
-			$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
434
-			$this->eventLogger->end('fs:setup:user:path:find');
435
-
436
-			$needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
437
-				return $needsFullSetup || $cachedMountInfo->getMountProvider() === '';
438
-			}, false);
439
-
440
-			if ($needsFullSetup) {
441
-				$this->logger->debug('mount has no provider set, performing full setup');
442
-				$this->setupForUser($user);
443
-				$this->eventLogger->end('fs:setup:user:path');
444
-				return;
445
-			} else {
446
-				foreach ($subCachedMounts as $cachedMount) {
447
-					if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
448
-						$currentProviders[] = $cachedMount->getMountProvider();
449
-						$setupProviders[] = $cachedMount->getMountProvider();
450
-						$mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]));
451
-					}
452
-				}
453
-			}
454
-		} else {
455
-			$this->eventLogger->end('fs:setup:user:path:find');
456
-		}
457
-
458
-		if (count($mounts)) {
459
-			$this->userMountCache->registerMounts($user, $mounts, $currentProviders);
460
-			$this->setupForUserWith($user, function () use ($mounts) {
461
-				array_walk($mounts, [$this->mountManager, 'addMount']);
462
-			});
463
-		} elseif (!$this->isSetupStarted($user)) {
464
-			$this->oneTimeUserSetup($user);
465
-		}
466
-		$this->eventLogger->end('fs:setup:user:path');
467
-	}
468
-
469
-	private function fullSetupRequired(IUser $user): bool {
470
-		if ($this->forceFullSetup) {
471
-			return true;
472
-		}
473
-
474
-		// we perform a "cached" setup only after having done the full setup recently
475
-		// this is also used to trigger a full setup after handling events that are likely
476
-		// to change the available mounts
477
-		if (!isset($this->fullSetupRequired[$user->getUID()])) {
478
-			$this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
479
-		}
480
-		return $this->fullSetupRequired[$user->getUID()];
481
-	}
482
-
483
-	/**
484
-	 * @param string $path
485
-	 * @param string[] $providers
486
-	 */
487
-	public function setupForProvider(string $path, array $providers): void {
488
-		$user = $this->getUserForPath($path);
489
-		if (!$user) {
490
-			$this->setupRoot();
491
-			return;
492
-		}
493
-
494
-		if ($this->isSetupComplete($user)) {
495
-			return;
496
-		}
497
-
498
-		if ($this->fullSetupRequired($user)) {
499
-			$this->setupForUser($user);
500
-			return;
501
-		}
502
-
503
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
504
-
505
-		$this->oneTimeUserSetup($user);
506
-
507
-		// home providers are always used
508
-		$providers = array_filter($providers, function (string $provider) {
509
-			return !is_subclass_of($provider, IHomeMountProvider::class);
510
-		});
511
-
512
-		if (in_array('', $providers)) {
513
-			$this->setupForUser($user);
514
-			return;
515
-		}
516
-		$setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
517
-
518
-		$providers = array_diff($providers, $setupProviders);
519
-		if (count($providers) === 0) {
520
-			if (!$this->isSetupStarted($user)) {
521
-				$this->oneTimeUserSetup($user);
522
-			}
523
-			$this->eventLogger->end('fs:setup:user:providers');
524
-			return;
525
-		} else {
526
-			$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
527
-			$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
528
-		}
529
-
530
-		$this->userMountCache->registerMounts($user, $mounts, $providers);
531
-		$this->setupForUserWith($user, function () use ($mounts) {
532
-			array_walk($mounts, [$this->mountManager, 'addMount']);
533
-		});
534
-		$this->eventLogger->end('fs:setup:user:providers');
535
-	}
536
-
537
-	public function tearDown() {
538
-		$this->setupUsers = [];
539
-		$this->setupUsersComplete = [];
540
-		$this->setupUserMountProviders = [];
541
-		$this->fullSetupRequired = [];
542
-		$this->rootSetup = false;
543
-		$this->mountManager->clear();
544
-		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
545
-	}
546
-
547
-	/**
548
-	 * Get mounts from mount providers that are registered after setup
549
-	 */
550
-	private function listenForNewMountProviders() {
551
-		if (!$this->listeningForProviders) {
552
-			$this->listeningForProviders = true;
553
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
554
-				IMountProvider $provider,
555
-			) {
556
-				foreach ($this->setupUsers as $userId) {
557
-					$user = $this->userManager->get($userId);
558
-					if ($user) {
559
-						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
560
-						array_walk($mounts, [$this->mountManager, 'addMount']);
561
-					}
562
-				}
563
-			});
564
-		}
565
-	}
566
-
567
-	private function setupListeners() {
568
-		// note that this event handling is intentionally pessimistic
569
-		// clearing the cache to often is better than not enough
570
-
571
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
572
-			$this->cache->remove($event->getUser()->getUID());
573
-		});
574
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
575
-			$this->cache->remove($event->getUser()->getUID());
576
-		});
577
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
578
-			$this->cache->remove($event->getShare()->getSharedWith());
579
-		});
580
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
581
-		) {
582
-			if ($user = $event->getUser()) {
583
-				$this->cache->remove($user->getUID());
584
-			} else {
585
-				$this->cache->clear();
586
-			}
587
-		});
588
-
589
-		$genericEvents = [
590
-			'OCA\Circles\Events\CreatingCircleEvent',
591
-			'OCA\Circles\Events\DestroyingCircleEvent',
592
-			'OCA\Circles\Events\AddingCircleMemberEvent',
593
-			'OCA\Circles\Events\RemovingCircleMemberEvent',
594
-		];
595
-
596
-		foreach ($genericEvents as $genericEvent) {
597
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
598
-				$this->cache->clear();
599
-			});
600
-		}
601
-	}
170
+            if ($mount->getOption('readonly', false)) {
171
+                return new PermissionsMask([
172
+                    'storage' => $storage,
173
+                    'mask' => Constants::PERMISSION_ALL & ~(
174
+                        Constants::PERMISSION_UPDATE |
175
+                        Constants::PERMISSION_CREATE |
176
+                        Constants::PERMISSION_DELETE
177
+                    ),
178
+                ]);
179
+            }
180
+            return $storage;
181
+        });
182
+
183
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
184
+    }
185
+
186
+    /**
187
+     * Setup the full filesystem for the specified user
188
+     */
189
+    public function setupForUser(IUser $user): void {
190
+        if ($this->isSetupComplete($user)) {
191
+            return;
192
+        }
193
+        $this->setupUsersComplete[] = $user->getUID();
194
+
195
+        $this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
196
+
197
+        if (!isset($this->setupUserMountProviders[$user->getUID()])) {
198
+            $this->setupUserMountProviders[$user->getUID()] = [];
199
+        }
200
+
201
+        $previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
202
+
203
+        $this->setupForUserWith($user, function () use ($user) {
204
+            $this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
205
+                IMountProvider $provider,
206
+            ) use ($user) {
207
+                return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]);
208
+            });
209
+        });
210
+        $this->afterUserFullySetup($user, $previouslySetupProviders);
211
+        $this->eventLogger->end('fs:setup:user:full');
212
+    }
213
+
214
+    /**
215
+     * part of the user setup that is run only once per user
216
+     */
217
+    private function oneTimeUserSetup(IUser $user) {
218
+        if ($this->isSetupStarted($user)) {
219
+            return;
220
+        }
221
+        $this->setupUsers[] = $user->getUID();
222
+
223
+        $this->setupRoot();
224
+
225
+        $this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
226
+
227
+        $this->setupBuiltinWrappers();
228
+
229
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
230
+
231
+        // TODO remove hook
232
+        OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
233
+
234
+        $event = new BeforeFileSystemSetupEvent($user);
235
+        $this->eventDispatcher->dispatchTyped($event);
236
+
237
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
238
+
239
+        $userDir = '/' . $user->getUID() . '/files';
240
+
241
+        Filesystem::initInternal($userDir);
242
+
243
+        if ($this->lockdownManager->canAccessFilesystem()) {
244
+            $this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
245
+            // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
246
+            $homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
247
+            $this->mountManager->addMount($homeMount);
248
+
249
+            if ($homeMount->getStorageRootId() === -1) {
250
+                $this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
251
+                $homeMount->getStorage()->mkdir('');
252
+                $homeMount->getStorage()->getScanner()->scan('');
253
+                $this->eventLogger->end('fs:setup:user:home:scan');
254
+            }
255
+            $this->eventLogger->end('fs:setup:user:home');
256
+        } else {
257
+            $this->mountManager->addMount(new MountPoint(
258
+                new NullStorage([]),
259
+                '/' . $user->getUID()
260
+            ));
261
+            $this->mountManager->addMount(new MountPoint(
262
+                new NullStorage([]),
263
+                '/' . $user->getUID() . '/files'
264
+            ));
265
+            $this->setupUsersComplete[] = $user->getUID();
266
+        }
267
+
268
+        $this->listenForNewMountProviders();
269
+
270
+        $this->eventLogger->end('fs:setup:user:onetime');
271
+    }
272
+
273
+    /**
274
+     * Final housekeeping after a user has been fully setup
275
+     */
276
+    private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
277
+        $this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
278
+        $userRoot = '/' . $user->getUID() . '/';
279
+        $mounts = $this->mountManager->getAll();
280
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
281
+            return str_starts_with($mount->getMountPoint(), $userRoot);
282
+        });
283
+        $allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
284
+            return get_class($provider);
285
+        }, array_merge(
286
+            $this->mountProviderCollection->getProviders(),
287
+            $this->mountProviderCollection->getHomeProviders(),
288
+            $this->mountProviderCollection->getRootProviders(),
289
+        ));
290
+        $newProviders = array_diff($allProviders, $previouslySetupProviders);
291
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
292
+            return !in_array($mount->getMountProvider(), $previouslySetupProviders);
293
+        });
294
+        $this->userMountCache->registerMounts($user, $mounts, $newProviders);
295
+
296
+        $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
297
+        if ($cacheDuration > 0) {
298
+            $this->cache->set($user->getUID(), true, $cacheDuration);
299
+            $this->fullSetupRequired[$user->getUID()] = false;
300
+        }
301
+        $this->eventLogger->end('fs:setup:user:full:post');
302
+    }
303
+
304
+    /**
305
+     * @param IUser $user
306
+     * @param IMountPoint $mounts
307
+     * @return void
308
+     * @throws \OCP\HintException
309
+     * @throws \OC\ServerNotAvailableException
310
+     */
311
+    private function setupForUserWith(IUser $user, callable $mountCallback): void {
312
+        $this->oneTimeUserSetup($user);
313
+
314
+        if ($this->lockdownManager->canAccessFilesystem()) {
315
+            $mountCallback();
316
+        }
317
+        $this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
318
+        \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
319
+        $this->eventLogger->end('fs:setup:user:post-init-mountpoint');
320
+
321
+        $userDir = '/' . $user->getUID() . '/files';
322
+        $this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
323
+        OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
324
+        $this->eventLogger->end('fs:setup:user:setup-hook');
325
+    }
326
+
327
+    /**
328
+     * Set up the root filesystem
329
+     */
330
+    public function setupRoot(): void {
331
+        //setting up the filesystem twice can only lead to trouble
332
+        if ($this->rootSetup) {
333
+            return;
334
+        }
335
+
336
+        $this->setupBuiltinWrappers();
337
+
338
+        $this->rootSetup = true;
339
+
340
+        $this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
341
+
342
+        $rootMounts = $this->mountProviderCollection->getRootMounts();
343
+        foreach ($rootMounts as $rootMountProvider) {
344
+            $this->mountManager->addMount($rootMountProvider);
345
+        }
346
+
347
+        $this->eventLogger->end('fs:setup:root');
348
+    }
349
+
350
+    /**
351
+     * Get the user to setup for a path or `null` if the root needs to be setup
352
+     *
353
+     * @param string $path
354
+     * @return IUser|null
355
+     */
356
+    private function getUserForPath(string $path) {
357
+        if (str_starts_with($path, '/__groupfolders')) {
358
+            return null;
359
+        } elseif (substr_count($path, '/') < 2) {
360
+            if ($user = $this->userSession->getUser()) {
361
+                return $user;
362
+            } else {
363
+                return null;
364
+            }
365
+        } elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
366
+            return null;
367
+        } else {
368
+            [, $userId] = explode('/', $path);
369
+        }
370
+
371
+        return $this->userManager->get($userId);
372
+    }
373
+
374
+    /**
375
+     * Set up the filesystem for the specified path
376
+     */
377
+    public function setupForPath(string $path, bool $includeChildren = false): void {
378
+        $user = $this->getUserForPath($path);
379
+        if (!$user) {
380
+            $this->setupRoot();
381
+            return;
382
+        }
383
+
384
+        if ($this->isSetupComplete($user)) {
385
+            return;
386
+        }
387
+
388
+        if ($this->fullSetupRequired($user)) {
389
+            $this->setupForUser($user);
390
+            return;
391
+        }
392
+
393
+        // for the user's home folder, and includes children we need everything always
394
+        if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
395
+            $this->setupForUser($user);
396
+            return;
397
+        }
398
+
399
+        if (!isset($this->setupUserMountProviders[$user->getUID()])) {
400
+            $this->setupUserMountProviders[$user->getUID()] = [];
401
+        }
402
+        $setupProviders = &$this->setupUserMountProviders[$user->getUID()];
403
+        $currentProviders = [];
404
+
405
+        try {
406
+            $cachedMount = $this->userMountCache->getMountForPath($user, $path);
407
+        } catch (NotFoundException $e) {
408
+            $this->setupForUser($user);
409
+            return;
410
+        }
411
+
412
+        $this->oneTimeUserSetup($user);
413
+
414
+        $this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
415
+        $this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
416
+
417
+        $mounts = [];
418
+        if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
419
+            $currentProviders[] = $cachedMount->getMountProvider();
420
+            if ($cachedMount->getMountProvider()) {
421
+                $setupProviders[] = $cachedMount->getMountProvider();
422
+                $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]);
423
+            } else {
424
+                $this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
425
+                $this->eventLogger->end('fs:setup:user:path:find');
426
+                $this->setupForUser($user);
427
+                $this->eventLogger->end('fs:setup:user:path');
428
+                return;
429
+            }
430
+        }
431
+
432
+        if ($includeChildren) {
433
+            $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
434
+            $this->eventLogger->end('fs:setup:user:path:find');
435
+
436
+            $needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
437
+                return $needsFullSetup || $cachedMountInfo->getMountProvider() === '';
438
+            }, false);
439
+
440
+            if ($needsFullSetup) {
441
+                $this->logger->debug('mount has no provider set, performing full setup');
442
+                $this->setupForUser($user);
443
+                $this->eventLogger->end('fs:setup:user:path');
444
+                return;
445
+            } else {
446
+                foreach ($subCachedMounts as $cachedMount) {
447
+                    if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
448
+                        $currentProviders[] = $cachedMount->getMountProvider();
449
+                        $setupProviders[] = $cachedMount->getMountProvider();
450
+                        $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]));
451
+                    }
452
+                }
453
+            }
454
+        } else {
455
+            $this->eventLogger->end('fs:setup:user:path:find');
456
+        }
457
+
458
+        if (count($mounts)) {
459
+            $this->userMountCache->registerMounts($user, $mounts, $currentProviders);
460
+            $this->setupForUserWith($user, function () use ($mounts) {
461
+                array_walk($mounts, [$this->mountManager, 'addMount']);
462
+            });
463
+        } elseif (!$this->isSetupStarted($user)) {
464
+            $this->oneTimeUserSetup($user);
465
+        }
466
+        $this->eventLogger->end('fs:setup:user:path');
467
+    }
468
+
469
+    private function fullSetupRequired(IUser $user): bool {
470
+        if ($this->forceFullSetup) {
471
+            return true;
472
+        }
473
+
474
+        // we perform a "cached" setup only after having done the full setup recently
475
+        // this is also used to trigger a full setup after handling events that are likely
476
+        // to change the available mounts
477
+        if (!isset($this->fullSetupRequired[$user->getUID()])) {
478
+            $this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
479
+        }
480
+        return $this->fullSetupRequired[$user->getUID()];
481
+    }
482
+
483
+    /**
484
+     * @param string $path
485
+     * @param string[] $providers
486
+     */
487
+    public function setupForProvider(string $path, array $providers): void {
488
+        $user = $this->getUserForPath($path);
489
+        if (!$user) {
490
+            $this->setupRoot();
491
+            return;
492
+        }
493
+
494
+        if ($this->isSetupComplete($user)) {
495
+            return;
496
+        }
497
+
498
+        if ($this->fullSetupRequired($user)) {
499
+            $this->setupForUser($user);
500
+            return;
501
+        }
502
+
503
+        $this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
504
+
505
+        $this->oneTimeUserSetup($user);
506
+
507
+        // home providers are always used
508
+        $providers = array_filter($providers, function (string $provider) {
509
+            return !is_subclass_of($provider, IHomeMountProvider::class);
510
+        });
511
+
512
+        if (in_array('', $providers)) {
513
+            $this->setupForUser($user);
514
+            return;
515
+        }
516
+        $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
517
+
518
+        $providers = array_diff($providers, $setupProviders);
519
+        if (count($providers) === 0) {
520
+            if (!$this->isSetupStarted($user)) {
521
+                $this->oneTimeUserSetup($user);
522
+            }
523
+            $this->eventLogger->end('fs:setup:user:providers');
524
+            return;
525
+        } else {
526
+            $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
527
+            $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
528
+        }
529
+
530
+        $this->userMountCache->registerMounts($user, $mounts, $providers);
531
+        $this->setupForUserWith($user, function () use ($mounts) {
532
+            array_walk($mounts, [$this->mountManager, 'addMount']);
533
+        });
534
+        $this->eventLogger->end('fs:setup:user:providers');
535
+    }
536
+
537
+    public function tearDown() {
538
+        $this->setupUsers = [];
539
+        $this->setupUsersComplete = [];
540
+        $this->setupUserMountProviders = [];
541
+        $this->fullSetupRequired = [];
542
+        $this->rootSetup = false;
543
+        $this->mountManager->clear();
544
+        $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
545
+    }
546
+
547
+    /**
548
+     * Get mounts from mount providers that are registered after setup
549
+     */
550
+    private function listenForNewMountProviders() {
551
+        if (!$this->listeningForProviders) {
552
+            $this->listeningForProviders = true;
553
+            $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
554
+                IMountProvider $provider,
555
+            ) {
556
+                foreach ($this->setupUsers as $userId) {
557
+                    $user = $this->userManager->get($userId);
558
+                    if ($user) {
559
+                        $mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
560
+                        array_walk($mounts, [$this->mountManager, 'addMount']);
561
+                    }
562
+                }
563
+            });
564
+        }
565
+    }
566
+
567
+    private function setupListeners() {
568
+        // note that this event handling is intentionally pessimistic
569
+        // clearing the cache to often is better than not enough
570
+
571
+        $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
572
+            $this->cache->remove($event->getUser()->getUID());
573
+        });
574
+        $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
575
+            $this->cache->remove($event->getUser()->getUID());
576
+        });
577
+        $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
578
+            $this->cache->remove($event->getShare()->getSharedWith());
579
+        });
580
+        $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
581
+        ) {
582
+            if ($user = $event->getUser()) {
583
+                $this->cache->remove($user->getUID());
584
+            } else {
585
+                $this->cache->clear();
586
+            }
587
+        });
588
+
589
+        $genericEvents = [
590
+            'OCA\Circles\Events\CreatingCircleEvent',
591
+            'OCA\Circles\Events\DestroyingCircleEvent',
592
+            'OCA\Circles\Events\AddingCircleMemberEvent',
593
+            'OCA\Circles\Events\RemovingCircleMemberEvent',
594
+        ];
595
+
596
+        foreach ($genericEvents as $genericEvent) {
597
+            $this->eventDispatcher->addListener($genericEvent, function ($event) {
598
+                $this->cache->clear();
599
+            });
600
+        }
601
+    }
602 602
 }
Please login to merge, or discard this patch.
Spacing   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -107,7 +107,7 @@  discard block
 block discarded – undo
107 107
 		OC_App::loadApps(['filesystem']);
108 108
 		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
109 109
 
110
-		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
110
+		Filesystem::addStorageWrapper('mount_options', function($mountPoint, IStorage $storage, IMountPoint $mount) {
111 111
 			if ($storage->instanceOfStorage(Common::class)) {
112 112
 				$options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
113 113
 				$storage->setMountOptions($options);
@@ -120,7 +120,7 @@  discard block
 block discarded – undo
120 120
 		$sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
121 121
 		Filesystem::addStorageWrapper(
122 122
 			'sharing_mask',
123
-			function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
123
+			function($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
124 124
 				$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
125 125
 				$isShared = $mount instanceof ISharedMountPoint;
126 126
 				if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
@@ -134,7 +134,7 @@  discard block
 block discarded – undo
134 134
 		);
135 135
 
136 136
 		// install storage availability wrapper, before most other wrappers
137
-		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
137
+		Filesystem::addStorageWrapper('oc_availability', function($mountPoint, IStorage $storage, IMountPoint $mount) {
138 138
 			$externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
139 139
 			if ($externalMount && !$storage->isLocal()) {
140 140
 				return new Availability(['storage' => $storage]);
@@ -142,7 +142,7 @@  discard block
 block discarded – undo
142 142
 			return $storage;
143 143
 		});
144 144
 
145
-		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
145
+		Filesystem::addStorageWrapper('oc_encoding', function($mountPoint, IStorage $storage, IMountPoint $mount) {
146 146
 			if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
147 147
 				return new Encoding(['storage' => $storage]);
148 148
 			}
@@ -150,12 +150,12 @@  discard block
 block discarded – undo
150 150
 		});
151 151
 
152 152
 		$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
153
-		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
153
+		Filesystem::addStorageWrapper('oc_quota', function($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
154 154
 			// set up quota for home storages, even for other users
155 155
 			// which can happen when using sharing
156 156
 			if ($mount instanceof HomeMountPoint) {
157 157
 				$user = $mount->getUser();
158
-				return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
158
+				return new Quota(['storage' => $storage, 'quotaCallback' => function() use ($user) {
159 159
 					return $user->getQuotaBytes();
160 160
 				}, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
161 161
 			}
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
 			return $storage;
164 164
 		});
165 165
 
166
-		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
166
+		Filesystem::addStorageWrapper('readonly', function($mountPoint, IStorage $storage, IMountPoint $mount) {
167 167
 			/*
168 168
 			 * Do not allow any operations that modify the storage
169 169
 			 */
@@ -200,8 +200,8 @@  discard block
 block discarded – undo
200 200
 
201 201
 		$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
202 202
 
203
-		$this->setupForUserWith($user, function () use ($user) {
204
-			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
203
+		$this->setupForUserWith($user, function() use ($user) {
204
+			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function(
205 205
 				IMountProvider $provider,
206 206
 			) use ($user) {
207 207
 				return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]);
@@ -236,7 +236,7 @@  discard block
 block discarded – undo
236 236
 
237 237
 		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
238 238
 
239
-		$userDir = '/' . $user->getUID() . '/files';
239
+		$userDir = '/'.$user->getUID().'/files';
240 240
 
241 241
 		Filesystem::initInternal($userDir);
242 242
 
@@ -256,11 +256,11 @@  discard block
 block discarded – undo
256 256
 		} else {
257 257
 			$this->mountManager->addMount(new MountPoint(
258 258
 				new NullStorage([]),
259
-				'/' . $user->getUID()
259
+				'/'.$user->getUID()
260 260
 			));
261 261
 			$this->mountManager->addMount(new MountPoint(
262 262
 				new NullStorage([]),
263
-				'/' . $user->getUID() . '/files'
263
+				'/'.$user->getUID().'/files'
264 264
 			));
265 265
 			$this->setupUsersComplete[] = $user->getUID();
266 266
 		}
@@ -275,12 +275,12 @@  discard block
 block discarded – undo
275 275
 	 */
276 276
 	private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
277 277
 		$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
278
-		$userRoot = '/' . $user->getUID() . '/';
278
+		$userRoot = '/'.$user->getUID().'/';
279 279
 		$mounts = $this->mountManager->getAll();
280
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
280
+		$mounts = array_filter($mounts, function(IMountPoint $mount) use ($userRoot) {
281 281
 			return str_starts_with($mount->getMountPoint(), $userRoot);
282 282
 		});
283
-		$allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
283
+		$allProviders = array_map(function(IMountProvider | IHomeMountProvider | IRootMountProvider $provider) {
284 284
 			return get_class($provider);
285 285
 		}, array_merge(
286 286
 			$this->mountProviderCollection->getProviders(),
@@ -288,7 +288,7 @@  discard block
 block discarded – undo
288 288
 			$this->mountProviderCollection->getRootProviders(),
289 289
 		));
290 290
 		$newProviders = array_diff($allProviders, $previouslySetupProviders);
291
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
291
+		$mounts = array_filter($mounts, function(IMountPoint $mount) use ($previouslySetupProviders) {
292 292
 			return !in_array($mount->getMountProvider(), $previouslySetupProviders);
293 293
 		});
294 294
 		$this->userMountCache->registerMounts($user, $mounts, $newProviders);
@@ -318,7 +318,7 @@  discard block
 block discarded – undo
318 318
 		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
319 319
 		$this->eventLogger->end('fs:setup:user:post-init-mountpoint');
320 320
 
321
-		$userDir = '/' . $user->getUID() . '/files';
321
+		$userDir = '/'.$user->getUID().'/files';
322 322
 		$this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
323 323
 		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
324 324
 		$this->eventLogger->end('fs:setup:user:setup-hook');
@@ -362,7 +362,7 @@  discard block
 block discarded – undo
362 362
 			} else {
363 363
 				return null;
364 364
 			}
365
-		} elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
365
+		} elseif (str_starts_with($path, '/appdata_'.\OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
366 366
 			return null;
367 367
 		} else {
368 368
 			[, $userId] = explode('/', $path);
@@ -391,7 +391,7 @@  discard block
 block discarded – undo
391 391
 		}
392 392
 
393 393
 		// for the user's home folder, and includes children we need everything always
394
-		if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
394
+		if (rtrim($path) === '/'.$user->getUID().'/files' && $includeChildren) {
395 395
 			$this->setupForUser($user);
396 396
 			return;
397 397
 		}
@@ -421,7 +421,7 @@  discard block
 block discarded – undo
421 421
 				$setupProviders[] = $cachedMount->getMountProvider();
422 422
 				$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]);
423 423
 			} else {
424
-				$this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
424
+				$this->logger->debug('mount at '.$cachedMount->getMountPoint().' has no provider set, performing full setup');
425 425
 				$this->eventLogger->end('fs:setup:user:path:find');
426 426
 				$this->setupForUser($user);
427 427
 				$this->eventLogger->end('fs:setup:user:path');
@@ -433,7 +433,7 @@  discard block
 block discarded – undo
433 433
 			$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
434 434
 			$this->eventLogger->end('fs:setup:user:path:find');
435 435
 
436
-			$needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
436
+			$needsFullSetup = array_reduce($subCachedMounts, function(bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
437 437
 				return $needsFullSetup || $cachedMountInfo->getMountProvider() === '';
438 438
 			}, false);
439 439
 
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 
458 458
 		if (count($mounts)) {
459 459
 			$this->userMountCache->registerMounts($user, $mounts, $currentProviders);
460
-			$this->setupForUserWith($user, function () use ($mounts) {
460
+			$this->setupForUserWith($user, function() use ($mounts) {
461 461
 				array_walk($mounts, [$this->mountManager, 'addMount']);
462 462
 			});
463 463
 		} elseif (!$this->isSetupStarted($user)) {
@@ -500,12 +500,12 @@  discard block
 block discarded – undo
500 500
 			return;
501 501
 		}
502 502
 
503
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
503
+		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for '.implode(', ', $providers));
504 504
 
505 505
 		$this->oneTimeUserSetup($user);
506 506
 
507 507
 		// home providers are always used
508
-		$providers = array_filter($providers, function (string $provider) {
508
+		$providers = array_filter($providers, function(string $provider) {
509 509
 			return !is_subclass_of($provider, IHomeMountProvider::class);
510 510
 		});
511 511
 
@@ -528,7 +528,7 @@  discard block
 block discarded – undo
528 528
 		}
529 529
 
530 530
 		$this->userMountCache->registerMounts($user, $mounts, $providers);
531
-		$this->setupForUserWith($user, function () use ($mounts) {
531
+		$this->setupForUserWith($user, function() use ($mounts) {
532 532
 			array_walk($mounts, [$this->mountManager, 'addMount']);
533 533
 		});
534 534
 		$this->eventLogger->end('fs:setup:user:providers');
@@ -550,7 +550,7 @@  discard block
 block discarded – undo
550 550
 	private function listenForNewMountProviders() {
551 551
 		if (!$this->listeningForProviders) {
552 552
 			$this->listeningForProviders = true;
553
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
553
+			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function(
554 554
 				IMountProvider $provider,
555 555
 			) {
556 556
 				foreach ($this->setupUsers as $userId) {
@@ -568,16 +568,16 @@  discard block
 block discarded – undo
568 568
 		// note that this event handling is intentionally pessimistic
569 569
 		// clearing the cache to often is better than not enough
570 570
 
571
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
571
+		$this->eventDispatcher->addListener(UserAddedEvent::class, function(UserAddedEvent $event) {
572 572
 			$this->cache->remove($event->getUser()->getUID());
573 573
 		});
574
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
574
+		$this->eventDispatcher->addListener(UserRemovedEvent::class, function(UserRemovedEvent $event) {
575 575
 			$this->cache->remove($event->getUser()->getUID());
576 576
 		});
577
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
577
+		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function(ShareCreatedEvent $event) {
578 578
 			$this->cache->remove($event->getShare()->getSharedWith());
579 579
 		});
580
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
580
+		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function(InvalidateMountCacheEvent $event,
581 581
 		) {
582 582
 			if ($user = $event->getUser()) {
583 583
 				$this->cache->remove($user->getUID());
@@ -594,7 +594,7 @@  discard block
 block discarded – undo
594 594
 		];
595 595
 
596 596
 		foreach ($genericEvents as $genericEvent) {
597
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
597
+			$this->eventDispatcher->addListener($genericEvent, function($event) {
598 598
 				$this->cache->clear();
599 599
 			});
600 600
 		}
Please login to merge, or discard this patch.
tests/lib/User/UserTest.php 1 patch
Indentation   +996 added lines, -996 removed lines patch added patch discarded remove patch
@@ -34,1000 +34,1000 @@
 block discarded – undo
34 34
  * @package Test\User
35 35
  */
36 36
 class UserTest extends TestCase {
37
-	/** @var IEventDispatcher|MockObject */
38
-	protected $dispatcher;
39
-
40
-	protected function setUp(): void {
41
-		parent::setUp();
42
-		$this->dispatcher = Server::get(IEventDispatcher::class);
43
-	}
44
-
45
-	public function testDisplayName(): void {
46
-		/**
47
-		 * @var \OC\User\Backend | MockObject $backend
48
-		 */
49
-		$backend = $this->createMock(\OC\User\Backend::class);
50
-		$backend->expects($this->once())
51
-			->method('getDisplayName')
52
-			->with($this->equalTo('foo'))
53
-			->willReturn('Foo');
54
-
55
-		$backend->expects($this->any())
56
-			->method('implementsActions')
57
-			->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
58
-			->willReturn(true);
59
-
60
-		$user = new User('foo', $backend, $this->dispatcher);
61
-		$this->assertEquals('Foo', $user->getDisplayName());
62
-	}
63
-
64
-	/**
65
-	 * if the display name contain whitespaces only, we expect the uid as result
66
-	 */
67
-	public function testDisplayNameEmpty(): void {
68
-		/**
69
-		 * @var \OC\User\Backend | MockObject $backend
70
-		 */
71
-		$backend = $this->createMock(\OC\User\Backend::class);
72
-		$backend->expects($this->once())
73
-			->method('getDisplayName')
74
-			->with($this->equalTo('foo'))
75
-			->willReturn('  ');
76
-
77
-		$backend->expects($this->any())
78
-			->method('implementsActions')
79
-			->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
80
-			->willReturn(true);
81
-
82
-		$user = new User('foo', $backend, $this->dispatcher);
83
-		$this->assertEquals('foo', $user->getDisplayName());
84
-	}
85
-
86
-	public function testDisplayNameNotSupported(): void {
87
-		/**
88
-		 * @var \OC\User\Backend | MockObject $backend
89
-		 */
90
-		$backend = $this->createMock(\OC\User\Backend::class);
91
-		$backend->expects($this->never())
92
-			->method('getDisplayName');
93
-
94
-		$backend->expects($this->any())
95
-			->method('implementsActions')
96
-			->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
97
-			->willReturn(false);
98
-
99
-		$user = new User('foo', $backend, $this->dispatcher);
100
-		$this->assertEquals('foo', $user->getDisplayName());
101
-	}
102
-
103
-	public function testSetPassword(): void {
104
-		/**
105
-		 * @var Backend | MockObject $backend
106
-		 */
107
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
108
-		$backend->expects($this->once())
109
-			->method('setPassword')
110
-			->with($this->equalTo('foo'), $this->equalTo('bar'));
111
-
112
-		$backend->expects($this->any())
113
-			->method('implementsActions')
114
-			->willReturnCallback(function ($actions) {
115
-				if ($actions === \OC\User\Backend::SET_PASSWORD) {
116
-					return true;
117
-				} else {
118
-					return false;
119
-				}
120
-			});
121
-
122
-		$user = new User('foo', $backend, $this->dispatcher);
123
-		$this->assertTrue($user->setPassword('bar', ''));
124
-	}
125
-
126
-	public function testSetPasswordNotSupported(): void {
127
-		/**
128
-		 * @var Backend | MockObject $backend
129
-		 */
130
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
131
-		$backend->expects($this->never())
132
-			->method('setPassword');
133
-
134
-		$backend->expects($this->any())
135
-			->method('implementsActions')
136
-			->willReturn(false);
137
-
138
-		$user = new User('foo', $backend, $this->dispatcher);
139
-		$this->assertFalse($user->setPassword('bar', ''));
140
-	}
141
-
142
-	public function testChangeAvatarSupportedYes(): void {
143
-		/**
144
-		 * @var Backend | MockObject $backend
145
-		 */
146
-		$backend = $this->createMock(AvatarUserDummy::class);
147
-		$backend->expects($this->once())
148
-			->method('canChangeAvatar')
149
-			->with($this->equalTo('foo'))
150
-			->willReturn(true);
151
-
152
-		$backend->expects($this->any())
153
-			->method('implementsActions')
154
-			->willReturnCallback(function ($actions) {
155
-				if ($actions === \OC\User\Backend::PROVIDE_AVATAR) {
156
-					return true;
157
-				} else {
158
-					return false;
159
-				}
160
-			});
161
-
162
-		$user = new User('foo', $backend, $this->dispatcher);
163
-		$this->assertTrue($user->canChangeAvatar());
164
-	}
165
-
166
-	public function testChangeAvatarSupportedNo(): void {
167
-		/**
168
-		 * @var Backend | MockObject $backend
169
-		 */
170
-		$backend = $this->createMock(AvatarUserDummy::class);
171
-		$backend->expects($this->once())
172
-			->method('canChangeAvatar')
173
-			->with($this->equalTo('foo'))
174
-			->willReturn(false);
175
-
176
-		$backend->expects($this->any())
177
-			->method('implementsActions')
178
-			->willReturnCallback(function ($actions) {
179
-				if ($actions === \OC\User\Backend::PROVIDE_AVATAR) {
180
-					return true;
181
-				} else {
182
-					return false;
183
-				}
184
-			});
185
-
186
-		$user = new User('foo', $backend, $this->dispatcher);
187
-		$this->assertFalse($user->canChangeAvatar());
188
-	}
189
-
190
-	public function testChangeAvatarNotSupported(): void {
191
-		/**
192
-		 * @var Backend | MockObject $backend
193
-		 */
194
-		$backend = $this->createMock(AvatarUserDummy::class);
195
-		$backend->expects($this->never())
196
-			->method('canChangeAvatar');
197
-
198
-		$backend->expects($this->any())
199
-			->method('implementsActions')
200
-			->willReturn(false);
201
-
202
-		$user = new User('foo', $backend, $this->dispatcher);
203
-		$this->assertTrue($user->canChangeAvatar());
204
-	}
205
-
206
-	public function testDelete(): void {
207
-		/**
208
-		 * @var Backend | MockObject $backend
209
-		 */
210
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
211
-		$backend->expects($this->once())
212
-			->method('deleteUser')
213
-			->with($this->equalTo('foo'));
214
-
215
-		$user = new User('foo', $backend, $this->dispatcher);
216
-		$this->assertTrue($user->delete());
217
-	}
218
-
219
-	public function testDeleteWithDifferentHome(): void {
220
-		/** @var ObjectHomeMountProvider $homeProvider */
221
-		$homeProvider = \OC::$server->get(ObjectHomeMountProvider::class);
222
-		$user = $this->createMock(IUser::class);
223
-		$user->method('getUID')
224
-			->willReturn('foo');
225
-		if ($homeProvider->getHomeMountForUser($user, $this->createMock(IStorageFactory::class)) !== null) {
226
-			$this->markTestSkipped('Skipping test for non local home storage');
227
-		}
228
-
229
-		/**
230
-		 * @var Backend | MockObject $backend
231
-		 */
232
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
233
-
234
-		$backend->expects($this->once())
235
-			->method('implementsActions')
236
-			->willReturnCallback(function ($actions) {
237
-				if ($actions === \OC\User\Backend::GET_HOME) {
238
-					return true;
239
-				} else {
240
-					return false;
241
-				}
242
-			});
243
-
244
-		// important: getHome MUST be called before deleteUser because
245
-		// once the user is deleted, getHome implementations might not
246
-		// return anything
247
-		$backend->expects($this->once())
248
-			->method('getHome')
249
-			->with($this->equalTo('foo'))
250
-			->willReturn('/home/foo');
251
-
252
-		$backend->expects($this->once())
253
-			->method('deleteUser')
254
-			->with($this->equalTo('foo'));
255
-
256
-		$user = new User('foo', $backend, $this->dispatcher);
257
-		$this->assertTrue($user->delete());
258
-	}
259
-
260
-	public function testGetHome(): void {
261
-		/**
262
-		 * @var Backend | MockObject $backend
263
-		 */
264
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
265
-		$backend->expects($this->once())
266
-			->method('getHome')
267
-			->with($this->equalTo('foo'))
268
-			->willReturn('/home/foo');
269
-
270
-		$backend->expects($this->any())
271
-			->method('implementsActions')
272
-			->willReturnCallback(function ($actions) {
273
-				if ($actions === \OC\User\Backend::GET_HOME) {
274
-					return true;
275
-				} else {
276
-					return false;
277
-				}
278
-			});
279
-
280
-		$user = new User('foo', $backend, $this->dispatcher);
281
-		$this->assertEquals('/home/foo', $user->getHome());
282
-	}
283
-
284
-	public function testGetBackendClassName(): void {
285
-		$user = new User('foo', new \Test\Util\User\Dummy(), $this->dispatcher);
286
-		$this->assertEquals('Dummy', $user->getBackendClassName());
287
-		$user = new User('foo', new \OC\User\Database(), $this->dispatcher);
288
-		$this->assertEquals('Database', $user->getBackendClassName());
289
-	}
290
-
291
-	public function testGetHomeNotSupported(): void {
292
-		/**
293
-		 * @var Backend | MockObject $backend
294
-		 */
295
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
296
-		$backend->expects($this->never())
297
-			->method('getHome');
298
-
299
-		$backend->expects($this->any())
300
-			->method('implementsActions')
301
-			->willReturn(false);
302
-
303
-		$allConfig = $this->getMockBuilder(IConfig::class)
304
-			->disableOriginalConstructor()
305
-			->getMock();
306
-		$allConfig->expects($this->any())
307
-			->method('getUserValue')
308
-			->willReturn(true);
309
-		$allConfig->expects($this->any())
310
-			->method('getSystemValueString')
311
-			->with($this->equalTo('datadirectory'))
312
-			->willReturn('arbitrary/path');
313
-
314
-		$user = new User('foo', $backend, $this->dispatcher, null, $allConfig);
315
-		$this->assertEquals('arbitrary/path/foo', $user->getHome());
316
-	}
317
-
318
-	public function testCanChangePassword(): void {
319
-		/**
320
-		 * @var Backend | MockObject $backend
321
-		 */
322
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
323
-
324
-		$backend->expects($this->any())
325
-			->method('implementsActions')
326
-			->willReturnCallback(function ($actions) {
327
-				if ($actions === \OC\User\Backend::SET_PASSWORD) {
328
-					return true;
329
-				} else {
330
-					return false;
331
-				}
332
-			});
333
-
334
-		$user = new User('foo', $backend, $this->dispatcher);
335
-		$this->assertTrue($user->canChangePassword());
336
-	}
337
-
338
-	public function testCanChangePasswordNotSupported(): void {
339
-		/**
340
-		 * @var Backend | MockObject $backend
341
-		 */
342
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
343
-
344
-		$backend->expects($this->any())
345
-			->method('implementsActions')
346
-			->willReturn(false);
347
-
348
-		$user = new User('foo', $backend, $this->dispatcher);
349
-		$this->assertFalse($user->canChangePassword());
350
-	}
351
-
352
-	public function testCanChangeDisplayName(): void {
353
-		/**
354
-		 * @var Backend | MockObject $backend
355
-		 */
356
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
357
-
358
-		$backend->expects($this->any())
359
-			->method('implementsActions')
360
-			->willReturnCallback(function ($actions) {
361
-				if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
362
-					return true;
363
-				} else {
364
-					return false;
365
-				}
366
-			});
367
-
368
-		$config = $this->createMock(IConfig::class);
369
-		$config->method('getSystemValueBool')
370
-			->with('allow_user_to_change_display_name')
371
-			->willReturn(true);
372
-
373
-		$user = new User('foo', $backend, $this->dispatcher, null, $config);
374
-		$this->assertTrue($user->canChangeDisplayName());
375
-	}
376
-
377
-	public function testCanChangeDisplayNameNotSupported(): void {
378
-		/**
379
-		 * @var Backend | MockObject $backend
380
-		 */
381
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
382
-
383
-		$backend->expects($this->any())
384
-			->method('implementsActions')
385
-			->willReturn(false);
386
-
387
-		$user = new User('foo', $backend, $this->dispatcher);
388
-		$this->assertFalse($user->canChangeDisplayName());
389
-	}
390
-
391
-	public function testSetDisplayNameSupported(): void {
392
-		/**
393
-		 * @var Backend | MockObject $backend
394
-		 */
395
-		$backend = $this->createMock(\OC\User\Database::class);
396
-
397
-		$backend->expects($this->any())
398
-			->method('implementsActions')
399
-			->willReturnCallback(function ($actions) {
400
-				if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
401
-					return true;
402
-				} else {
403
-					return false;
404
-				}
405
-			});
406
-
407
-		$backend->expects($this->once())
408
-			->method('setDisplayName')
409
-			->with('foo', 'Foo')
410
-			->willReturn(true);
411
-
412
-		$user = new User('foo', $backend, $this->createMock(IEventDispatcher::class));
413
-		$this->assertTrue($user->setDisplayName('Foo'));
414
-		$this->assertEquals('Foo', $user->getDisplayName());
415
-	}
416
-
417
-	/**
418
-	 * don't allow display names containing whitespaces only
419
-	 */
420
-	public function testSetDisplayNameEmpty(): void {
421
-		/**
422
-		 * @var Backend | MockObject $backend
423
-		 */
424
-		$backend = $this->createMock(\OC\User\Database::class);
425
-
426
-		$backend->expects($this->any())
427
-			->method('implementsActions')
428
-			->willReturnCallback(function ($actions) {
429
-				if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
430
-					return true;
431
-				} else {
432
-					return false;
433
-				}
434
-			});
435
-
436
-		$user = new User('foo', $backend, $this->dispatcher);
437
-		$this->assertFalse($user->setDisplayName(' '));
438
-		$this->assertEquals('foo', $user->getDisplayName());
439
-	}
440
-
441
-	public function testSetDisplayNameNotSupported(): void {
442
-		/**
443
-		 * @var Backend | MockObject $backend
444
-		 */
445
-		$backend = $this->createMock(\OC\User\Database::class);
446
-
447
-		$backend->expects($this->any())
448
-			->method('implementsActions')
449
-			->willReturn(false);
450
-
451
-		$backend->expects($this->never())
452
-			->method('setDisplayName');
453
-
454
-		$user = new User('foo', $backend, $this->dispatcher);
455
-		$this->assertFalse($user->setDisplayName('Foo'));
456
-		$this->assertEquals('foo', $user->getDisplayName());
457
-	}
458
-
459
-	public function testSetPasswordHooks(): void {
460
-		$hooksCalled = 0;
461
-		$test = $this;
462
-
463
-		/**
464
-		 * @var Backend | MockObject $backend
465
-		 */
466
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
467
-		$backend->expects($this->once())
468
-			->method('setPassword');
469
-
470
-		/**
471
-		 * @param User $user
472
-		 * @param string $password
473
-		 */
474
-		$hook = function ($user, $password) use ($test, &$hooksCalled) {
475
-			$hooksCalled++;
476
-			$test->assertEquals('foo', $user->getUID());
477
-			$test->assertEquals('bar', $password);
478
-		};
479
-
480
-		$emitter = new PublicEmitter();
481
-		$emitter->listen('\OC\User', 'preSetPassword', $hook);
482
-		$emitter->listen('\OC\User', 'postSetPassword', $hook);
483
-
484
-		$backend->expects($this->any())
485
-			->method('implementsActions')
486
-			->willReturnCallback(function ($actions) {
487
-				if ($actions === \OC\User\Backend::SET_PASSWORD) {
488
-					return true;
489
-				} else {
490
-					return false;
491
-				}
492
-			});
493
-
494
-		$user = new User('foo', $backend, $this->dispatcher, $emitter);
495
-
496
-		$user->setPassword('bar', '');
497
-		$this->assertEquals(2, $hooksCalled);
498
-	}
499
-
500
-	public static function dataDeleteHooks(): array {
501
-		return [
502
-			[true, 2],
503
-			[false, 1],
504
-		];
505
-	}
506
-
507
-	/**
508
-	 * @dataProvider dataDeleteHooks
509
-	 * @param bool $result
510
-	 * @param int $expectedHooks
511
-	 */
512
-	public function testDeleteHooks($result, $expectedHooks): void {
513
-		$hooksCalled = 0;
514
-		$test = $this;
515
-
516
-		/**
517
-		 * @var UserInterface&MockObject $backend
518
-		 */
519
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
520
-		$backend->expects($this->once())
521
-			->method('deleteUser')
522
-			->willReturn($result);
523
-
524
-		$config = $this->createMock(IConfig::class);
525
-		$config->method('getSystemValue')
526
-			->willReturnArgument(1);
527
-		$config->method('getSystemValueString')
528
-			->willReturnArgument(1);
529
-		$config->method('getSystemValueBool')
530
-			->willReturnArgument(1);
531
-		$config->method('getSystemValueInt')
532
-			->willReturnArgument(1);
533
-
534
-		$emitter = new PublicEmitter();
535
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
536
-
537
-		/**
538
-		 * @param User $user
539
-		 */
540
-		$hook = function ($user) use ($test, &$hooksCalled) {
541
-			$hooksCalled++;
542
-			$test->assertEquals('foo', $user->getUID());
543
-		};
544
-
545
-		$emitter->listen('\OC\User', 'preDelete', $hook);
546
-		$emitter->listen('\OC\User', 'postDelete', $hook);
547
-
548
-		$commentsManager = $this->createMock(ICommentsManager::class);
549
-		$notificationManager = $this->createMock(INotificationManager::class);
550
-
551
-		if ($result) {
552
-			$config->expects($this->atLeastOnce())
553
-				->method('deleteAllUserValues')
554
-				->with('foo');
555
-
556
-			$commentsManager->expects($this->once())
557
-				->method('deleteReferencesOfActor')
558
-				->with('users', 'foo');
559
-			$commentsManager->expects($this->once())
560
-				->method('deleteReadMarksFromUser')
561
-				->with($user);
562
-
563
-			$notification = $this->createMock(INotification::class);
564
-			$notification->expects($this->once())
565
-				->method('setUser')
566
-				->with('foo');
567
-
568
-			$notificationManager->expects($this->once())
569
-				->method('createNotification')
570
-				->willReturn($notification);
571
-			$notificationManager->expects($this->once())
572
-				->method('markProcessed')
573
-				->with($notification);
574
-		} else {
575
-			$config->expects($this->never())
576
-				->method('deleteAllUserValues');
577
-
578
-			$commentsManager->expects($this->never())
579
-				->method('deleteReferencesOfActor');
580
-			$commentsManager->expects($this->never())
581
-				->method('deleteReadMarksFromUser');
582
-
583
-			$notificationManager->expects($this->never())
584
-				->method('createNotification');
585
-			$notificationManager->expects($this->never())
586
-				->method('markProcessed');
587
-		}
588
-
589
-		$this->overwriteService(\OCP\Notification\IManager::class, $notificationManager);
590
-		$this->overwriteService(\OCP\Comments\ICommentsManager::class, $commentsManager);
591
-
592
-		$this->assertSame($result, $user->delete());
593
-
594
-		$this->restoreService(AllConfig::class);
595
-		$this->restoreService(\OCP\Comments\ICommentsManager::class);
596
-		$this->restoreService(\OCP\Notification\IManager::class);
597
-
598
-		$this->assertEquals($expectedHooks, $hooksCalled);
599
-	}
600
-
601
-	public function testDeleteRecoverState() {
602
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
603
-		$backend->expects($this->once())
604
-			->method('deleteUser')
605
-			->willReturn(true);
606
-
607
-		$config = $this->createMock(IConfig::class);
608
-		$config->method('getSystemValue')
609
-			->willReturnArgument(1);
610
-		$config->method('getSystemValueString')
611
-			->willReturnArgument(1);
612
-		$config->method('getSystemValueBool')
613
-			->willReturnArgument(1);
614
-		$config->method('getSystemValueInt')
615
-			->willReturnArgument(1);
616
-
617
-		$userConfig = [];
618
-		$config->expects(self::atLeast(2))
619
-			->method('setUserValue')
620
-			->willReturnCallback(function () {
621
-				$userConfig[] = func_get_args();
622
-			});
623
-
624
-		$commentsManager = $this->createMock(ICommentsManager::class);
625
-		$commentsManager->expects($this->once())
626
-			->method('deleteReferencesOfActor')
627
-			->willThrowException(new \Error('Test exception'));
628
-
629
-		$this->overwriteService(\OCP\Comments\ICommentsManager::class, $commentsManager);
630
-		$this->expectException(\Error::class);
631
-
632
-		$user = $this->getMockBuilder(User::class)
633
-			->onlyMethods(['getHome'])
634
-			->setConstructorArgs(['foo', $backend, $this->dispatcher, null, $config])
635
-			->getMock();
636
-
637
-		$user->expects(self::atLeastOnce())
638
-			->method('getHome')
639
-			->willReturn('/home/path');
640
-
641
-		$user->delete();
642
-
643
-		$this->assertEqualsCanonicalizing(
644
-			[
645
-				['foo', 'core', 'deleted', 'true', null],
646
-				['foo', 'core', 'deleted.backup-home', '/home/path', null],
647
-			],
648
-			$userConfig,
649
-		);
650
-
651
-		$this->restoreService(\OCP\Comments\ICommentsManager::class);
652
-	}
653
-
654
-	public static function dataGetCloudId(): array {
655
-		return [
656
-			['https://localhost:8888/nextcloud', 'foo@localhost:8888/nextcloud'],
657
-			['http://localhost:8888/nextcloud', 'foo@http://localhost:8888/nextcloud'],
658
-		];
659
-	}
660
-
661
-	/**
662
-	 * @dataProvider dataGetCloudId
663
-	 */
664
-	public function testGetCloudId(string $absoluteUrl, string $cloudId): void {
665
-		/** @var Backend|MockObject $backend */
666
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
667
-		$urlGenerator = $this->createMock(IURLGenerator::class);
668
-		$urlGenerator->method('getAbsoluteURL')
669
-			->withAnyParameters()
670
-			->willReturn($absoluteUrl);
671
-		$user = new User('foo', $backend, $this->dispatcher, null, null, $urlGenerator);
672
-		$this->assertEquals($cloudId, $user->getCloudId());
673
-	}
674
-
675
-	public function testSetEMailAddressEmpty(): void {
676
-		/**
677
-		 * @var Backend | MockObject $backend
678
-		 */
679
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
680
-
681
-		$test = $this;
682
-		$hooksCalled = 0;
683
-
684
-		/**
685
-		 * @param IUser $user
686
-		 * @param string $feature
687
-		 * @param string $value
688
-		 */
689
-		$hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled) {
690
-			$hooksCalled++;
691
-			$test->assertEquals('eMailAddress', $feature);
692
-			$test->assertEquals('', $value);
693
-		};
694
-
695
-		$emitter = new PublicEmitter();
696
-		$emitter->listen('\OC\User', 'changeUser', $hook);
697
-
698
-		$config = $this->createMock(IConfig::class);
699
-		$config->expects($this->once())
700
-			->method('deleteUserValue')
701
-			->with(
702
-				'foo',
703
-				'settings',
704
-				'email'
705
-			);
706
-
707
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
708
-		$user->setEMailAddress('');
709
-	}
710
-
711
-	public function testSetEMailAddress(): void {
712
-		/**
713
-		 * @var UserInterface | MockObject $backend
714
-		 */
715
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
716
-
717
-		$test = $this;
718
-		$hooksCalled = 0;
719
-
720
-		/**
721
-		 * @param IUser $user
722
-		 * @param string $feature
723
-		 * @param string $value
724
-		 */
725
-		$hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled) {
726
-			$hooksCalled++;
727
-			$test->assertEquals('eMailAddress', $feature);
728
-			$test->assertEquals('[email protected]', $value);
729
-		};
730
-
731
-		$emitter = new PublicEmitter();
732
-		$emitter->listen('\OC\User', 'changeUser', $hook);
733
-
734
-		$config = $this->createMock(IConfig::class);
735
-		$config->expects($this->once())
736
-			->method('setUserValue')
737
-			->with(
738
-				'foo',
739
-				'settings',
740
-				'email',
741
-				'[email protected]'
742
-			);
743
-
744
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
745
-		$user->setEMailAddress('[email protected]');
746
-	}
747
-
748
-	public function testSetEMailAddressNoChange(): void {
749
-		/**
750
-		 * @var UserInterface | MockObject $backend
751
-		 */
752
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
753
-
754
-		/** @var PublicEmitter|MockObject $emitter */
755
-		$emitter = $this->createMock(PublicEmitter::class);
756
-		$emitter->expects($this->never())
757
-			->method('emit');
758
-
759
-		$dispatcher = $this->createMock(IEventDispatcher::class);
760
-		$dispatcher->expects($this->never())
761
-			->method('dispatch');
762
-
763
-		$config = $this->createMock(IConfig::class);
764
-		$config->expects($this->any())
765
-			->method('getUserValue')
766
-			->willReturn('[email protected]');
767
-		$config->expects($this->any())
768
-			->method('setUserValue');
769
-
770
-		$user = new User('foo', $backend, $dispatcher, $emitter, $config);
771
-		$user->setEMailAddress('[email protected]');
772
-	}
773
-
774
-	public function testSetQuota(): void {
775
-		/**
776
-		 * @var UserInterface | MockObject $backend
777
-		 */
778
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
779
-
780
-		$test = $this;
781
-		$hooksCalled = 0;
782
-
783
-		/**
784
-		 * @param IUser $user
785
-		 * @param string $feature
786
-		 * @param string $value
787
-		 */
788
-		$hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled) {
789
-			$hooksCalled++;
790
-			$test->assertEquals('quota', $feature);
791
-			$test->assertEquals('23 TB', $value);
792
-		};
793
-
794
-		$emitter = new PublicEmitter();
795
-		$emitter->listen('\OC\User', 'changeUser', $hook);
796
-
797
-		$config = $this->createMock(IConfig::class);
798
-		$config->expects($this->once())
799
-			->method('setUserValue')
800
-			->with(
801
-				'foo',
802
-				'files',
803
-				'quota',
804
-				'23 TB'
805
-			);
806
-
807
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
808
-		$user->setQuota('23 TB');
809
-	}
810
-
811
-	public function testGetDefaultUnlimitedQuota(): void {
812
-		/**
813
-		 * @var UserInterface | MockObject $backend
814
-		 */
815
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
816
-
817
-		/** @var PublicEmitter|MockObject $emitter */
818
-		$emitter = $this->createMock(PublicEmitter::class);
819
-		$emitter->expects($this->never())
820
-			->method('emit');
821
-
822
-		$config = $this->createMock(IConfig::class);
823
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
824
-
825
-		$userValueMap = [
826
-			['foo', 'files', 'quota', 'default', 'default'],
827
-		];
828
-		$appValueMap = [
829
-			['files', 'default_quota', 'none', 'none'],
830
-			// allow unlimited quota
831
-			['files', 'allow_unlimited_quota', '1', '1'],
832
-		];
833
-		$config->method('getUserValue')
834
-			->will($this->returnValueMap($userValueMap));
835
-		$config->method('getAppValue')
836
-			->will($this->returnValueMap($appValueMap));
837
-
838
-		$this->assertEquals('none', $user->getQuota());
839
-		$this->assertEquals(FileInfo::SPACE_UNLIMITED, $user->getQuotaBytes());
840
-	}
841
-
842
-	public function testGetDefaultUnlimitedQuotaForbidden(): void {
843
-		/**
844
-		 * @var UserInterface | MockObject $backend
845
-		 */
846
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
847
-
848
-		/** @var PublicEmitter|MockObject $emitter */
849
-		$emitter = $this->createMock(PublicEmitter::class);
850
-		$emitter->expects($this->never())
851
-			->method('emit');
852
-
853
-		$config = $this->createMock(IConfig::class);
854
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
855
-
856
-		$userValueMap = [
857
-			['foo', 'files', 'quota', 'default', 'default'],
858
-		];
859
-		$appValueMap = [
860
-			['files', 'default_quota', 'none', 'none'],
861
-			// do not allow unlimited quota
862
-			['files', 'allow_unlimited_quota', '1', '0'],
863
-			['files', 'quota_preset', '1 GB, 5 GB, 10 GB', '1 GB, 5 GB, 10 GB'],
864
-			// expect seeing 1 GB used as fallback value
865
-			['files', 'default_quota', '1 GB', '1 GB'],
866
-		];
867
-		$config->method('getUserValue')
868
-			->will($this->returnValueMap($userValueMap));
869
-		$config->method('getAppValue')
870
-			->will($this->returnValueMap($appValueMap));
871
-
872
-		$this->assertEquals('1 GB', $user->getQuota());
873
-		$this->assertEquals(1024 * 1024 * 1024, $user->getQuotaBytes());
874
-	}
875
-
876
-	public function testSetQuotaAddressNoChange(): void {
877
-		/**
878
-		 * @var UserInterface | MockObject $backend
879
-		 */
880
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
881
-
882
-		/** @var PublicEmitter|MockObject $emitter */
883
-		$emitter = $this->createMock(PublicEmitter::class);
884
-		$emitter->expects($this->never())
885
-			->method('emit');
886
-
887
-		$config = $this->createMock(IConfig::class);
888
-		$config->expects($this->any())
889
-			->method('getUserValue')
890
-			->willReturn('23 TB');
891
-		$config->expects($this->never())
892
-			->method('setUserValue');
893
-
894
-		$user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
895
-		$user->setQuota('23 TB');
896
-	}
897
-
898
-	public function testGetLastLogin(): void {
899
-		/**
900
-		 * @var Backend | MockObject $backend
901
-		 */
902
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
903
-
904
-		$config = $this->createMock(IConfig::class);
905
-		$config->method('getUserValue')
906
-			->willReturnCallback(function ($uid, $app, $key, $default) {
907
-				if ($uid === 'foo' && $app === 'login' && $key === 'lastLogin') {
908
-					return 42;
909
-				} else {
910
-					return $default;
911
-				}
912
-			});
913
-
914
-		$user = new User('foo', $backend, $this->dispatcher, null, $config);
915
-		$this->assertSame(42, $user->getLastLogin());
916
-	}
917
-
918
-	public function testSetEnabled(): void {
919
-		/**
920
-		 * @var Backend | MockObject $backend
921
-		 */
922
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
923
-
924
-		$config = $this->createMock(IConfig::class);
925
-		$config->expects($this->once())
926
-			->method('setUserValue')
927
-			->with(
928
-				$this->equalTo('foo'),
929
-				$this->equalTo('core'),
930
-				$this->equalTo('enabled'),
931
-				'true'
932
-			);
933
-		/* dav event listener gets the manager list from config */
934
-		$config->expects(self::any())
935
-			->method('getUserValue')
936
-			->willReturnCallback(
937
-				fn ($user, $app, $key, $default) => ($key === 'enabled' ? 'false' : $default)
938
-			);
939
-
940
-		$user = new User('foo', $backend, $this->dispatcher, null, $config);
941
-		$user->setEnabled(true);
942
-	}
943
-
944
-	public function testSetDisabled(): void {
945
-		/**
946
-		 * @var Backend | MockObject $backend
947
-		 */
948
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
949
-
950
-		$config = $this->createMock(IConfig::class);
951
-		$config->expects($this->once())
952
-			->method('setUserValue')
953
-			->with(
954
-				$this->equalTo('foo'),
955
-				$this->equalTo('core'),
956
-				$this->equalTo('enabled'),
957
-				'false'
958
-			);
959
-
960
-		$user = $this->getMockBuilder(User::class)
961
-			->setConstructorArgs([
962
-				'foo',
963
-				$backend,
964
-				$this->dispatcher,
965
-				null,
966
-				$config,
967
-			])
968
-			->onlyMethods(['isEnabled', 'triggerChange'])
969
-			->getMock();
970
-
971
-		$user->expects($this->once())
972
-			->method('isEnabled')
973
-			->willReturn(true);
974
-		$user->expects($this->once())
975
-			->method('triggerChange')
976
-			->with(
977
-				'enabled',
978
-				false
979
-			);
980
-
981
-		$user->setEnabled(false);
982
-	}
983
-
984
-	public function testSetDisabledAlreadyDisabled(): void {
985
-		/**
986
-		 * @var Backend | MockObject $backend
987
-		 */
988
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
989
-
990
-		$config = $this->createMock(IConfig::class);
991
-		$config->expects($this->never())
992
-			->method('setUserValue');
993
-
994
-		$user = $this->getMockBuilder(User::class)
995
-			->setConstructorArgs([
996
-				'foo',
997
-				$backend,
998
-				$this->dispatcher,
999
-				null,
1000
-				$config,
1001
-			])
1002
-			->onlyMethods(['isEnabled', 'triggerChange'])
1003
-			->getMock();
1004
-
1005
-		$user->expects($this->once())
1006
-			->method('isEnabled')
1007
-			->willReturn(false);
1008
-		$user->expects($this->never())
1009
-			->method('triggerChange');
1010
-
1011
-		$user->setEnabled(false);
1012
-	}
1013
-
1014
-	public function testGetEMailAddress(): void {
1015
-		/**
1016
-		 * @var Backend | MockObject $backend
1017
-		 */
1018
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
1019
-
1020
-		$config = $this->createMock(IConfig::class);
1021
-		$config->method('getUserValue')
1022
-			->willReturnCallback(function ($uid, $app, $key, $default) {
1023
-				if ($uid === 'foo' && $app === 'settings' && $key === 'email') {
1024
-					return '[email protected]';
1025
-				} else {
1026
-					return $default;
1027
-				}
1028
-			});
1029
-
1030
-		$user = new User('foo', $backend, $this->dispatcher, null, $config);
1031
-		$this->assertSame('[email protected]', $user->getEMailAddress());
1032
-	}
37
+    /** @var IEventDispatcher|MockObject */
38
+    protected $dispatcher;
39
+
40
+    protected function setUp(): void {
41
+        parent::setUp();
42
+        $this->dispatcher = Server::get(IEventDispatcher::class);
43
+    }
44
+
45
+    public function testDisplayName(): void {
46
+        /**
47
+         * @var \OC\User\Backend | MockObject $backend
48
+         */
49
+        $backend = $this->createMock(\OC\User\Backend::class);
50
+        $backend->expects($this->once())
51
+            ->method('getDisplayName')
52
+            ->with($this->equalTo('foo'))
53
+            ->willReturn('Foo');
54
+
55
+        $backend->expects($this->any())
56
+            ->method('implementsActions')
57
+            ->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
58
+            ->willReturn(true);
59
+
60
+        $user = new User('foo', $backend, $this->dispatcher);
61
+        $this->assertEquals('Foo', $user->getDisplayName());
62
+    }
63
+
64
+    /**
65
+     * if the display name contain whitespaces only, we expect the uid as result
66
+     */
67
+    public function testDisplayNameEmpty(): void {
68
+        /**
69
+         * @var \OC\User\Backend | MockObject $backend
70
+         */
71
+        $backend = $this->createMock(\OC\User\Backend::class);
72
+        $backend->expects($this->once())
73
+            ->method('getDisplayName')
74
+            ->with($this->equalTo('foo'))
75
+            ->willReturn('  ');
76
+
77
+        $backend->expects($this->any())
78
+            ->method('implementsActions')
79
+            ->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
80
+            ->willReturn(true);
81
+
82
+        $user = new User('foo', $backend, $this->dispatcher);
83
+        $this->assertEquals('foo', $user->getDisplayName());
84
+    }
85
+
86
+    public function testDisplayNameNotSupported(): void {
87
+        /**
88
+         * @var \OC\User\Backend | MockObject $backend
89
+         */
90
+        $backend = $this->createMock(\OC\User\Backend::class);
91
+        $backend->expects($this->never())
92
+            ->method('getDisplayName');
93
+
94
+        $backend->expects($this->any())
95
+            ->method('implementsActions')
96
+            ->with($this->equalTo(\OC\User\Backend::GET_DISPLAYNAME))
97
+            ->willReturn(false);
98
+
99
+        $user = new User('foo', $backend, $this->dispatcher);
100
+        $this->assertEquals('foo', $user->getDisplayName());
101
+    }
102
+
103
+    public function testSetPassword(): void {
104
+        /**
105
+         * @var Backend | MockObject $backend
106
+         */
107
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
108
+        $backend->expects($this->once())
109
+            ->method('setPassword')
110
+            ->with($this->equalTo('foo'), $this->equalTo('bar'));
111
+
112
+        $backend->expects($this->any())
113
+            ->method('implementsActions')
114
+            ->willReturnCallback(function ($actions) {
115
+                if ($actions === \OC\User\Backend::SET_PASSWORD) {
116
+                    return true;
117
+                } else {
118
+                    return false;
119
+                }
120
+            });
121
+
122
+        $user = new User('foo', $backend, $this->dispatcher);
123
+        $this->assertTrue($user->setPassword('bar', ''));
124
+    }
125
+
126
+    public function testSetPasswordNotSupported(): void {
127
+        /**
128
+         * @var Backend | MockObject $backend
129
+         */
130
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
131
+        $backend->expects($this->never())
132
+            ->method('setPassword');
133
+
134
+        $backend->expects($this->any())
135
+            ->method('implementsActions')
136
+            ->willReturn(false);
137
+
138
+        $user = new User('foo', $backend, $this->dispatcher);
139
+        $this->assertFalse($user->setPassword('bar', ''));
140
+    }
141
+
142
+    public function testChangeAvatarSupportedYes(): void {
143
+        /**
144
+         * @var Backend | MockObject $backend
145
+         */
146
+        $backend = $this->createMock(AvatarUserDummy::class);
147
+        $backend->expects($this->once())
148
+            ->method('canChangeAvatar')
149
+            ->with($this->equalTo('foo'))
150
+            ->willReturn(true);
151
+
152
+        $backend->expects($this->any())
153
+            ->method('implementsActions')
154
+            ->willReturnCallback(function ($actions) {
155
+                if ($actions === \OC\User\Backend::PROVIDE_AVATAR) {
156
+                    return true;
157
+                } else {
158
+                    return false;
159
+                }
160
+            });
161
+
162
+        $user = new User('foo', $backend, $this->dispatcher);
163
+        $this->assertTrue($user->canChangeAvatar());
164
+    }
165
+
166
+    public function testChangeAvatarSupportedNo(): void {
167
+        /**
168
+         * @var Backend | MockObject $backend
169
+         */
170
+        $backend = $this->createMock(AvatarUserDummy::class);
171
+        $backend->expects($this->once())
172
+            ->method('canChangeAvatar')
173
+            ->with($this->equalTo('foo'))
174
+            ->willReturn(false);
175
+
176
+        $backend->expects($this->any())
177
+            ->method('implementsActions')
178
+            ->willReturnCallback(function ($actions) {
179
+                if ($actions === \OC\User\Backend::PROVIDE_AVATAR) {
180
+                    return true;
181
+                } else {
182
+                    return false;
183
+                }
184
+            });
185
+
186
+        $user = new User('foo', $backend, $this->dispatcher);
187
+        $this->assertFalse($user->canChangeAvatar());
188
+    }
189
+
190
+    public function testChangeAvatarNotSupported(): void {
191
+        /**
192
+         * @var Backend | MockObject $backend
193
+         */
194
+        $backend = $this->createMock(AvatarUserDummy::class);
195
+        $backend->expects($this->never())
196
+            ->method('canChangeAvatar');
197
+
198
+        $backend->expects($this->any())
199
+            ->method('implementsActions')
200
+            ->willReturn(false);
201
+
202
+        $user = new User('foo', $backend, $this->dispatcher);
203
+        $this->assertTrue($user->canChangeAvatar());
204
+    }
205
+
206
+    public function testDelete(): void {
207
+        /**
208
+         * @var Backend | MockObject $backend
209
+         */
210
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
211
+        $backend->expects($this->once())
212
+            ->method('deleteUser')
213
+            ->with($this->equalTo('foo'));
214
+
215
+        $user = new User('foo', $backend, $this->dispatcher);
216
+        $this->assertTrue($user->delete());
217
+    }
218
+
219
+    public function testDeleteWithDifferentHome(): void {
220
+        /** @var ObjectHomeMountProvider $homeProvider */
221
+        $homeProvider = \OC::$server->get(ObjectHomeMountProvider::class);
222
+        $user = $this->createMock(IUser::class);
223
+        $user->method('getUID')
224
+            ->willReturn('foo');
225
+        if ($homeProvider->getHomeMountForUser($user, $this->createMock(IStorageFactory::class)) !== null) {
226
+            $this->markTestSkipped('Skipping test for non local home storage');
227
+        }
228
+
229
+        /**
230
+         * @var Backend | MockObject $backend
231
+         */
232
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
233
+
234
+        $backend->expects($this->once())
235
+            ->method('implementsActions')
236
+            ->willReturnCallback(function ($actions) {
237
+                if ($actions === \OC\User\Backend::GET_HOME) {
238
+                    return true;
239
+                } else {
240
+                    return false;
241
+                }
242
+            });
243
+
244
+        // important: getHome MUST be called before deleteUser because
245
+        // once the user is deleted, getHome implementations might not
246
+        // return anything
247
+        $backend->expects($this->once())
248
+            ->method('getHome')
249
+            ->with($this->equalTo('foo'))
250
+            ->willReturn('/home/foo');
251
+
252
+        $backend->expects($this->once())
253
+            ->method('deleteUser')
254
+            ->with($this->equalTo('foo'));
255
+
256
+        $user = new User('foo', $backend, $this->dispatcher);
257
+        $this->assertTrue($user->delete());
258
+    }
259
+
260
+    public function testGetHome(): void {
261
+        /**
262
+         * @var Backend | MockObject $backend
263
+         */
264
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
265
+        $backend->expects($this->once())
266
+            ->method('getHome')
267
+            ->with($this->equalTo('foo'))
268
+            ->willReturn('/home/foo');
269
+
270
+        $backend->expects($this->any())
271
+            ->method('implementsActions')
272
+            ->willReturnCallback(function ($actions) {
273
+                if ($actions === \OC\User\Backend::GET_HOME) {
274
+                    return true;
275
+                } else {
276
+                    return false;
277
+                }
278
+            });
279
+
280
+        $user = new User('foo', $backend, $this->dispatcher);
281
+        $this->assertEquals('/home/foo', $user->getHome());
282
+    }
283
+
284
+    public function testGetBackendClassName(): void {
285
+        $user = new User('foo', new \Test\Util\User\Dummy(), $this->dispatcher);
286
+        $this->assertEquals('Dummy', $user->getBackendClassName());
287
+        $user = new User('foo', new \OC\User\Database(), $this->dispatcher);
288
+        $this->assertEquals('Database', $user->getBackendClassName());
289
+    }
290
+
291
+    public function testGetHomeNotSupported(): void {
292
+        /**
293
+         * @var Backend | MockObject $backend
294
+         */
295
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
296
+        $backend->expects($this->never())
297
+            ->method('getHome');
298
+
299
+        $backend->expects($this->any())
300
+            ->method('implementsActions')
301
+            ->willReturn(false);
302
+
303
+        $allConfig = $this->getMockBuilder(IConfig::class)
304
+            ->disableOriginalConstructor()
305
+            ->getMock();
306
+        $allConfig->expects($this->any())
307
+            ->method('getUserValue')
308
+            ->willReturn(true);
309
+        $allConfig->expects($this->any())
310
+            ->method('getSystemValueString')
311
+            ->with($this->equalTo('datadirectory'))
312
+            ->willReturn('arbitrary/path');
313
+
314
+        $user = new User('foo', $backend, $this->dispatcher, null, $allConfig);
315
+        $this->assertEquals('arbitrary/path/foo', $user->getHome());
316
+    }
317
+
318
+    public function testCanChangePassword(): void {
319
+        /**
320
+         * @var Backend | MockObject $backend
321
+         */
322
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
323
+
324
+        $backend->expects($this->any())
325
+            ->method('implementsActions')
326
+            ->willReturnCallback(function ($actions) {
327
+                if ($actions === \OC\User\Backend::SET_PASSWORD) {
328
+                    return true;
329
+                } else {
330
+                    return false;
331
+                }
332
+            });
333
+
334
+        $user = new User('foo', $backend, $this->dispatcher);
335
+        $this->assertTrue($user->canChangePassword());
336
+    }
337
+
338
+    public function testCanChangePasswordNotSupported(): void {
339
+        /**
340
+         * @var Backend | MockObject $backend
341
+         */
342
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
343
+
344
+        $backend->expects($this->any())
345
+            ->method('implementsActions')
346
+            ->willReturn(false);
347
+
348
+        $user = new User('foo', $backend, $this->dispatcher);
349
+        $this->assertFalse($user->canChangePassword());
350
+    }
351
+
352
+    public function testCanChangeDisplayName(): void {
353
+        /**
354
+         * @var Backend | MockObject $backend
355
+         */
356
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
357
+
358
+        $backend->expects($this->any())
359
+            ->method('implementsActions')
360
+            ->willReturnCallback(function ($actions) {
361
+                if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
362
+                    return true;
363
+                } else {
364
+                    return false;
365
+                }
366
+            });
367
+
368
+        $config = $this->createMock(IConfig::class);
369
+        $config->method('getSystemValueBool')
370
+            ->with('allow_user_to_change_display_name')
371
+            ->willReturn(true);
372
+
373
+        $user = new User('foo', $backend, $this->dispatcher, null, $config);
374
+        $this->assertTrue($user->canChangeDisplayName());
375
+    }
376
+
377
+    public function testCanChangeDisplayNameNotSupported(): void {
378
+        /**
379
+         * @var Backend | MockObject $backend
380
+         */
381
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
382
+
383
+        $backend->expects($this->any())
384
+            ->method('implementsActions')
385
+            ->willReturn(false);
386
+
387
+        $user = new User('foo', $backend, $this->dispatcher);
388
+        $this->assertFalse($user->canChangeDisplayName());
389
+    }
390
+
391
+    public function testSetDisplayNameSupported(): void {
392
+        /**
393
+         * @var Backend | MockObject $backend
394
+         */
395
+        $backend = $this->createMock(\OC\User\Database::class);
396
+
397
+        $backend->expects($this->any())
398
+            ->method('implementsActions')
399
+            ->willReturnCallback(function ($actions) {
400
+                if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
401
+                    return true;
402
+                } else {
403
+                    return false;
404
+                }
405
+            });
406
+
407
+        $backend->expects($this->once())
408
+            ->method('setDisplayName')
409
+            ->with('foo', 'Foo')
410
+            ->willReturn(true);
411
+
412
+        $user = new User('foo', $backend, $this->createMock(IEventDispatcher::class));
413
+        $this->assertTrue($user->setDisplayName('Foo'));
414
+        $this->assertEquals('Foo', $user->getDisplayName());
415
+    }
416
+
417
+    /**
418
+     * don't allow display names containing whitespaces only
419
+     */
420
+    public function testSetDisplayNameEmpty(): void {
421
+        /**
422
+         * @var Backend | MockObject $backend
423
+         */
424
+        $backend = $this->createMock(\OC\User\Database::class);
425
+
426
+        $backend->expects($this->any())
427
+            ->method('implementsActions')
428
+            ->willReturnCallback(function ($actions) {
429
+                if ($actions === \OC\User\Backend::SET_DISPLAYNAME) {
430
+                    return true;
431
+                } else {
432
+                    return false;
433
+                }
434
+            });
435
+
436
+        $user = new User('foo', $backend, $this->dispatcher);
437
+        $this->assertFalse($user->setDisplayName(' '));
438
+        $this->assertEquals('foo', $user->getDisplayName());
439
+    }
440
+
441
+    public function testSetDisplayNameNotSupported(): void {
442
+        /**
443
+         * @var Backend | MockObject $backend
444
+         */
445
+        $backend = $this->createMock(\OC\User\Database::class);
446
+
447
+        $backend->expects($this->any())
448
+            ->method('implementsActions')
449
+            ->willReturn(false);
450
+
451
+        $backend->expects($this->never())
452
+            ->method('setDisplayName');
453
+
454
+        $user = new User('foo', $backend, $this->dispatcher);
455
+        $this->assertFalse($user->setDisplayName('Foo'));
456
+        $this->assertEquals('foo', $user->getDisplayName());
457
+    }
458
+
459
+    public function testSetPasswordHooks(): void {
460
+        $hooksCalled = 0;
461
+        $test = $this;
462
+
463
+        /**
464
+         * @var Backend | MockObject $backend
465
+         */
466
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
467
+        $backend->expects($this->once())
468
+            ->method('setPassword');
469
+
470
+        /**
471
+         * @param User $user
472
+         * @param string $password
473
+         */
474
+        $hook = function ($user, $password) use ($test, &$hooksCalled) {
475
+            $hooksCalled++;
476
+            $test->assertEquals('foo', $user->getUID());
477
+            $test->assertEquals('bar', $password);
478
+        };
479
+
480
+        $emitter = new PublicEmitter();
481
+        $emitter->listen('\OC\User', 'preSetPassword', $hook);
482
+        $emitter->listen('\OC\User', 'postSetPassword', $hook);
483
+
484
+        $backend->expects($this->any())
485
+            ->method('implementsActions')
486
+            ->willReturnCallback(function ($actions) {
487
+                if ($actions === \OC\User\Backend::SET_PASSWORD) {
488
+                    return true;
489
+                } else {
490
+                    return false;
491
+                }
492
+            });
493
+
494
+        $user = new User('foo', $backend, $this->dispatcher, $emitter);
495
+
496
+        $user->setPassword('bar', '');
497
+        $this->assertEquals(2, $hooksCalled);
498
+    }
499
+
500
+    public static function dataDeleteHooks(): array {
501
+        return [
502
+            [true, 2],
503
+            [false, 1],
504
+        ];
505
+    }
506
+
507
+    /**
508
+     * @dataProvider dataDeleteHooks
509
+     * @param bool $result
510
+     * @param int $expectedHooks
511
+     */
512
+    public function testDeleteHooks($result, $expectedHooks): void {
513
+        $hooksCalled = 0;
514
+        $test = $this;
515
+
516
+        /**
517
+         * @var UserInterface&MockObject $backend
518
+         */
519
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
520
+        $backend->expects($this->once())
521
+            ->method('deleteUser')
522
+            ->willReturn($result);
523
+
524
+        $config = $this->createMock(IConfig::class);
525
+        $config->method('getSystemValue')
526
+            ->willReturnArgument(1);
527
+        $config->method('getSystemValueString')
528
+            ->willReturnArgument(1);
529
+        $config->method('getSystemValueBool')
530
+            ->willReturnArgument(1);
531
+        $config->method('getSystemValueInt')
532
+            ->willReturnArgument(1);
533
+
534
+        $emitter = new PublicEmitter();
535
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
536
+
537
+        /**
538
+         * @param User $user
539
+         */
540
+        $hook = function ($user) use ($test, &$hooksCalled) {
541
+            $hooksCalled++;
542
+            $test->assertEquals('foo', $user->getUID());
543
+        };
544
+
545
+        $emitter->listen('\OC\User', 'preDelete', $hook);
546
+        $emitter->listen('\OC\User', 'postDelete', $hook);
547
+
548
+        $commentsManager = $this->createMock(ICommentsManager::class);
549
+        $notificationManager = $this->createMock(INotificationManager::class);
550
+
551
+        if ($result) {
552
+            $config->expects($this->atLeastOnce())
553
+                ->method('deleteAllUserValues')
554
+                ->with('foo');
555
+
556
+            $commentsManager->expects($this->once())
557
+                ->method('deleteReferencesOfActor')
558
+                ->with('users', 'foo');
559
+            $commentsManager->expects($this->once())
560
+                ->method('deleteReadMarksFromUser')
561
+                ->with($user);
562
+
563
+            $notification = $this->createMock(INotification::class);
564
+            $notification->expects($this->once())
565
+                ->method('setUser')
566
+                ->with('foo');
567
+
568
+            $notificationManager->expects($this->once())
569
+                ->method('createNotification')
570
+                ->willReturn($notification);
571
+            $notificationManager->expects($this->once())
572
+                ->method('markProcessed')
573
+                ->with($notification);
574
+        } else {
575
+            $config->expects($this->never())
576
+                ->method('deleteAllUserValues');
577
+
578
+            $commentsManager->expects($this->never())
579
+                ->method('deleteReferencesOfActor');
580
+            $commentsManager->expects($this->never())
581
+                ->method('deleteReadMarksFromUser');
582
+
583
+            $notificationManager->expects($this->never())
584
+                ->method('createNotification');
585
+            $notificationManager->expects($this->never())
586
+                ->method('markProcessed');
587
+        }
588
+
589
+        $this->overwriteService(\OCP\Notification\IManager::class, $notificationManager);
590
+        $this->overwriteService(\OCP\Comments\ICommentsManager::class, $commentsManager);
591
+
592
+        $this->assertSame($result, $user->delete());
593
+
594
+        $this->restoreService(AllConfig::class);
595
+        $this->restoreService(\OCP\Comments\ICommentsManager::class);
596
+        $this->restoreService(\OCP\Notification\IManager::class);
597
+
598
+        $this->assertEquals($expectedHooks, $hooksCalled);
599
+    }
600
+
601
+    public function testDeleteRecoverState() {
602
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
603
+        $backend->expects($this->once())
604
+            ->method('deleteUser')
605
+            ->willReturn(true);
606
+
607
+        $config = $this->createMock(IConfig::class);
608
+        $config->method('getSystemValue')
609
+            ->willReturnArgument(1);
610
+        $config->method('getSystemValueString')
611
+            ->willReturnArgument(1);
612
+        $config->method('getSystemValueBool')
613
+            ->willReturnArgument(1);
614
+        $config->method('getSystemValueInt')
615
+            ->willReturnArgument(1);
616
+
617
+        $userConfig = [];
618
+        $config->expects(self::atLeast(2))
619
+            ->method('setUserValue')
620
+            ->willReturnCallback(function () {
621
+                $userConfig[] = func_get_args();
622
+            });
623
+
624
+        $commentsManager = $this->createMock(ICommentsManager::class);
625
+        $commentsManager->expects($this->once())
626
+            ->method('deleteReferencesOfActor')
627
+            ->willThrowException(new \Error('Test exception'));
628
+
629
+        $this->overwriteService(\OCP\Comments\ICommentsManager::class, $commentsManager);
630
+        $this->expectException(\Error::class);
631
+
632
+        $user = $this->getMockBuilder(User::class)
633
+            ->onlyMethods(['getHome'])
634
+            ->setConstructorArgs(['foo', $backend, $this->dispatcher, null, $config])
635
+            ->getMock();
636
+
637
+        $user->expects(self::atLeastOnce())
638
+            ->method('getHome')
639
+            ->willReturn('/home/path');
640
+
641
+        $user->delete();
642
+
643
+        $this->assertEqualsCanonicalizing(
644
+            [
645
+                ['foo', 'core', 'deleted', 'true', null],
646
+                ['foo', 'core', 'deleted.backup-home', '/home/path', null],
647
+            ],
648
+            $userConfig,
649
+        );
650
+
651
+        $this->restoreService(\OCP\Comments\ICommentsManager::class);
652
+    }
653
+
654
+    public static function dataGetCloudId(): array {
655
+        return [
656
+            ['https://localhost:8888/nextcloud', 'foo@localhost:8888/nextcloud'],
657
+            ['http://localhost:8888/nextcloud', 'foo@http://localhost:8888/nextcloud'],
658
+        ];
659
+    }
660
+
661
+    /**
662
+     * @dataProvider dataGetCloudId
663
+     */
664
+    public function testGetCloudId(string $absoluteUrl, string $cloudId): void {
665
+        /** @var Backend|MockObject $backend */
666
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
667
+        $urlGenerator = $this->createMock(IURLGenerator::class);
668
+        $urlGenerator->method('getAbsoluteURL')
669
+            ->withAnyParameters()
670
+            ->willReturn($absoluteUrl);
671
+        $user = new User('foo', $backend, $this->dispatcher, null, null, $urlGenerator);
672
+        $this->assertEquals($cloudId, $user->getCloudId());
673
+    }
674
+
675
+    public function testSetEMailAddressEmpty(): void {
676
+        /**
677
+         * @var Backend | MockObject $backend
678
+         */
679
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
680
+
681
+        $test = $this;
682
+        $hooksCalled = 0;
683
+
684
+        /**
685
+         * @param IUser $user
686
+         * @param string $feature
687
+         * @param string $value
688
+         */
689
+        $hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled) {
690
+            $hooksCalled++;
691
+            $test->assertEquals('eMailAddress', $feature);
692
+            $test->assertEquals('', $value);
693
+        };
694
+
695
+        $emitter = new PublicEmitter();
696
+        $emitter->listen('\OC\User', 'changeUser', $hook);
697
+
698
+        $config = $this->createMock(IConfig::class);
699
+        $config->expects($this->once())
700
+            ->method('deleteUserValue')
701
+            ->with(
702
+                'foo',
703
+                'settings',
704
+                'email'
705
+            );
706
+
707
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
708
+        $user->setEMailAddress('');
709
+    }
710
+
711
+    public function testSetEMailAddress(): void {
712
+        /**
713
+         * @var UserInterface | MockObject $backend
714
+         */
715
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
716
+
717
+        $test = $this;
718
+        $hooksCalled = 0;
719
+
720
+        /**
721
+         * @param IUser $user
722
+         * @param string $feature
723
+         * @param string $value
724
+         */
725
+        $hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled) {
726
+            $hooksCalled++;
727
+            $test->assertEquals('eMailAddress', $feature);
728
+            $test->assertEquals('[email protected]', $value);
729
+        };
730
+
731
+        $emitter = new PublicEmitter();
732
+        $emitter->listen('\OC\User', 'changeUser', $hook);
733
+
734
+        $config = $this->createMock(IConfig::class);
735
+        $config->expects($this->once())
736
+            ->method('setUserValue')
737
+            ->with(
738
+                'foo',
739
+                'settings',
740
+                'email',
741
+                '[email protected]'
742
+            );
743
+
744
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
745
+        $user->setEMailAddress('[email protected]');
746
+    }
747
+
748
+    public function testSetEMailAddressNoChange(): void {
749
+        /**
750
+         * @var UserInterface | MockObject $backend
751
+         */
752
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
753
+
754
+        /** @var PublicEmitter|MockObject $emitter */
755
+        $emitter = $this->createMock(PublicEmitter::class);
756
+        $emitter->expects($this->never())
757
+            ->method('emit');
758
+
759
+        $dispatcher = $this->createMock(IEventDispatcher::class);
760
+        $dispatcher->expects($this->never())
761
+            ->method('dispatch');
762
+
763
+        $config = $this->createMock(IConfig::class);
764
+        $config->expects($this->any())
765
+            ->method('getUserValue')
766
+            ->willReturn('[email protected]');
767
+        $config->expects($this->any())
768
+            ->method('setUserValue');
769
+
770
+        $user = new User('foo', $backend, $dispatcher, $emitter, $config);
771
+        $user->setEMailAddress('[email protected]');
772
+    }
773
+
774
+    public function testSetQuota(): void {
775
+        /**
776
+         * @var UserInterface | MockObject $backend
777
+         */
778
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
779
+
780
+        $test = $this;
781
+        $hooksCalled = 0;
782
+
783
+        /**
784
+         * @param IUser $user
785
+         * @param string $feature
786
+         * @param string $value
787
+         */
788
+        $hook = function (IUser $user, $feature, $value) use ($test, &$hooksCalled) {
789
+            $hooksCalled++;
790
+            $test->assertEquals('quota', $feature);
791
+            $test->assertEquals('23 TB', $value);
792
+        };
793
+
794
+        $emitter = new PublicEmitter();
795
+        $emitter->listen('\OC\User', 'changeUser', $hook);
796
+
797
+        $config = $this->createMock(IConfig::class);
798
+        $config->expects($this->once())
799
+            ->method('setUserValue')
800
+            ->with(
801
+                'foo',
802
+                'files',
803
+                'quota',
804
+                '23 TB'
805
+            );
806
+
807
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
808
+        $user->setQuota('23 TB');
809
+    }
810
+
811
+    public function testGetDefaultUnlimitedQuota(): void {
812
+        /**
813
+         * @var UserInterface | MockObject $backend
814
+         */
815
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
816
+
817
+        /** @var PublicEmitter|MockObject $emitter */
818
+        $emitter = $this->createMock(PublicEmitter::class);
819
+        $emitter->expects($this->never())
820
+            ->method('emit');
821
+
822
+        $config = $this->createMock(IConfig::class);
823
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
824
+
825
+        $userValueMap = [
826
+            ['foo', 'files', 'quota', 'default', 'default'],
827
+        ];
828
+        $appValueMap = [
829
+            ['files', 'default_quota', 'none', 'none'],
830
+            // allow unlimited quota
831
+            ['files', 'allow_unlimited_quota', '1', '1'],
832
+        ];
833
+        $config->method('getUserValue')
834
+            ->will($this->returnValueMap($userValueMap));
835
+        $config->method('getAppValue')
836
+            ->will($this->returnValueMap($appValueMap));
837
+
838
+        $this->assertEquals('none', $user->getQuota());
839
+        $this->assertEquals(FileInfo::SPACE_UNLIMITED, $user->getQuotaBytes());
840
+    }
841
+
842
+    public function testGetDefaultUnlimitedQuotaForbidden(): void {
843
+        /**
844
+         * @var UserInterface | MockObject $backend
845
+         */
846
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
847
+
848
+        /** @var PublicEmitter|MockObject $emitter */
849
+        $emitter = $this->createMock(PublicEmitter::class);
850
+        $emitter->expects($this->never())
851
+            ->method('emit');
852
+
853
+        $config = $this->createMock(IConfig::class);
854
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
855
+
856
+        $userValueMap = [
857
+            ['foo', 'files', 'quota', 'default', 'default'],
858
+        ];
859
+        $appValueMap = [
860
+            ['files', 'default_quota', 'none', 'none'],
861
+            // do not allow unlimited quota
862
+            ['files', 'allow_unlimited_quota', '1', '0'],
863
+            ['files', 'quota_preset', '1 GB, 5 GB, 10 GB', '1 GB, 5 GB, 10 GB'],
864
+            // expect seeing 1 GB used as fallback value
865
+            ['files', 'default_quota', '1 GB', '1 GB'],
866
+        ];
867
+        $config->method('getUserValue')
868
+            ->will($this->returnValueMap($userValueMap));
869
+        $config->method('getAppValue')
870
+            ->will($this->returnValueMap($appValueMap));
871
+
872
+        $this->assertEquals('1 GB', $user->getQuota());
873
+        $this->assertEquals(1024 * 1024 * 1024, $user->getQuotaBytes());
874
+    }
875
+
876
+    public function testSetQuotaAddressNoChange(): void {
877
+        /**
878
+         * @var UserInterface | MockObject $backend
879
+         */
880
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
881
+
882
+        /** @var PublicEmitter|MockObject $emitter */
883
+        $emitter = $this->createMock(PublicEmitter::class);
884
+        $emitter->expects($this->never())
885
+            ->method('emit');
886
+
887
+        $config = $this->createMock(IConfig::class);
888
+        $config->expects($this->any())
889
+            ->method('getUserValue')
890
+            ->willReturn('23 TB');
891
+        $config->expects($this->never())
892
+            ->method('setUserValue');
893
+
894
+        $user = new User('foo', $backend, $this->dispatcher, $emitter, $config);
895
+        $user->setQuota('23 TB');
896
+    }
897
+
898
+    public function testGetLastLogin(): void {
899
+        /**
900
+         * @var Backend | MockObject $backend
901
+         */
902
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
903
+
904
+        $config = $this->createMock(IConfig::class);
905
+        $config->method('getUserValue')
906
+            ->willReturnCallback(function ($uid, $app, $key, $default) {
907
+                if ($uid === 'foo' && $app === 'login' && $key === 'lastLogin') {
908
+                    return 42;
909
+                } else {
910
+                    return $default;
911
+                }
912
+            });
913
+
914
+        $user = new User('foo', $backend, $this->dispatcher, null, $config);
915
+        $this->assertSame(42, $user->getLastLogin());
916
+    }
917
+
918
+    public function testSetEnabled(): void {
919
+        /**
920
+         * @var Backend | MockObject $backend
921
+         */
922
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
923
+
924
+        $config = $this->createMock(IConfig::class);
925
+        $config->expects($this->once())
926
+            ->method('setUserValue')
927
+            ->with(
928
+                $this->equalTo('foo'),
929
+                $this->equalTo('core'),
930
+                $this->equalTo('enabled'),
931
+                'true'
932
+            );
933
+        /* dav event listener gets the manager list from config */
934
+        $config->expects(self::any())
935
+            ->method('getUserValue')
936
+            ->willReturnCallback(
937
+                fn ($user, $app, $key, $default) => ($key === 'enabled' ? 'false' : $default)
938
+            );
939
+
940
+        $user = new User('foo', $backend, $this->dispatcher, null, $config);
941
+        $user->setEnabled(true);
942
+    }
943
+
944
+    public function testSetDisabled(): void {
945
+        /**
946
+         * @var Backend | MockObject $backend
947
+         */
948
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
949
+
950
+        $config = $this->createMock(IConfig::class);
951
+        $config->expects($this->once())
952
+            ->method('setUserValue')
953
+            ->with(
954
+                $this->equalTo('foo'),
955
+                $this->equalTo('core'),
956
+                $this->equalTo('enabled'),
957
+                'false'
958
+            );
959
+
960
+        $user = $this->getMockBuilder(User::class)
961
+            ->setConstructorArgs([
962
+                'foo',
963
+                $backend,
964
+                $this->dispatcher,
965
+                null,
966
+                $config,
967
+            ])
968
+            ->onlyMethods(['isEnabled', 'triggerChange'])
969
+            ->getMock();
970
+
971
+        $user->expects($this->once())
972
+            ->method('isEnabled')
973
+            ->willReturn(true);
974
+        $user->expects($this->once())
975
+            ->method('triggerChange')
976
+            ->with(
977
+                'enabled',
978
+                false
979
+            );
980
+
981
+        $user->setEnabled(false);
982
+    }
983
+
984
+    public function testSetDisabledAlreadyDisabled(): void {
985
+        /**
986
+         * @var Backend | MockObject $backend
987
+         */
988
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
989
+
990
+        $config = $this->createMock(IConfig::class);
991
+        $config->expects($this->never())
992
+            ->method('setUserValue');
993
+
994
+        $user = $this->getMockBuilder(User::class)
995
+            ->setConstructorArgs([
996
+                'foo',
997
+                $backend,
998
+                $this->dispatcher,
999
+                null,
1000
+                $config,
1001
+            ])
1002
+            ->onlyMethods(['isEnabled', 'triggerChange'])
1003
+            ->getMock();
1004
+
1005
+        $user->expects($this->once())
1006
+            ->method('isEnabled')
1007
+            ->willReturn(false);
1008
+        $user->expects($this->never())
1009
+            ->method('triggerChange');
1010
+
1011
+        $user->setEnabled(false);
1012
+    }
1013
+
1014
+    public function testGetEMailAddress(): void {
1015
+        /**
1016
+         * @var Backend | MockObject $backend
1017
+         */
1018
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
1019
+
1020
+        $config = $this->createMock(IConfig::class);
1021
+        $config->method('getUserValue')
1022
+            ->willReturnCallback(function ($uid, $app, $key, $default) {
1023
+                if ($uid === 'foo' && $app === 'settings' && $key === 'email') {
1024
+                    return '[email protected]';
1025
+                } else {
1026
+                    return $default;
1027
+                }
1028
+            });
1029
+
1030
+        $user = new User('foo', $backend, $this->dispatcher, null, $config);
1031
+        $this->assertSame('[email protected]', $user->getEMailAddress());
1032
+    }
1033 1033
 }
Please login to merge, or discard this patch.