Completed
Push — master ( a01e85...1c7e4a )
by
unknown
32:48 queued 15s
created
lib/public/Util.php 2 patches
Indentation   +603 added lines, -603 removed lines patch added patch discarded remove patch
@@ -25,607 +25,607 @@
 block discarded – undo
25 25
  * @since 4.0.0
26 26
  */
27 27
 class Util {
28
-	private static ?IManager $shareManager = null;
29
-
30
-	private static array $scriptsInit = [];
31
-	private static array $scripts = [];
32
-	private static array $scriptDeps = [];
33
-
34
-	/**
35
-	 * get the current installed version of Nextcloud
36
-	 * @return array
37
-	 * @since 4.0.0
38
-	 * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion
39
-	 */
40
-	public static function getVersion() {
41
-		return Server::get(ServerVersion::class)->getVersion();
42
-	}
43
-
44
-	/**
45
-	 * @since 17.0.0
46
-	 */
47
-	public static function hasExtendedSupport(): bool {
48
-		try {
49
-			/** @var \OCP\Support\Subscription\IRegistry */
50
-			$subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
51
-			return $subscriptionRegistry->delegateHasExtendedSupport();
52
-		} catch (ContainerExceptionInterface $e) {
53
-		}
54
-		return \OC::$server->getConfig()->getSystemValueBool('extendedSupport', false);
55
-	}
56
-
57
-	/**
58
-	 * Set current update channel
59
-	 * @param string $channel
60
-	 * @since 8.1.0
61
-	 */
62
-	public static function setChannel($channel) {
63
-		\OC::$server->getConfig()->setSystemValue('updater.release.channel', $channel);
64
-	}
65
-
66
-	/**
67
-	 * Get current update channel
68
-	 * @return string
69
-	 * @since 8.1.0
70
-	 * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel
71
-	 */
72
-	public static function getChannel() {
73
-		return \OCP\Server::get(ServerVersion::class)->getChannel();
74
-	}
75
-
76
-	/**
77
-	 * check if sharing is disabled for the current user
78
-	 *
79
-	 * @return boolean
80
-	 * @since 7.0.0
81
-	 * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser
82
-	 */
83
-	public static function isSharingDisabledForUser() {
84
-		if (self::$shareManager === null) {
85
-			self::$shareManager = Server::get(IManager::class);
86
-		}
87
-
88
-		$user = Server::get(\OCP\IUserSession::class)->getUser();
89
-
90
-		return self::$shareManager->sharingDisabledForUser($user?->getUID());
91
-	}
92
-
93
-	/**
94
-	 * get l10n object
95
-	 * @since 6.0.0 - parameter $language was added in 8.0.0
96
-	 */
97
-	public static function getL10N(string $application, ?string $language = null): IL10N {
98
-		return Server::get(\OCP\L10N\IFactory::class)->get($application, $language);
99
-	}
100
-
101
-	/**
102
-	 * Add a css file
103
-	 *
104
-	 * @param string $application application id
105
-	 * @param ?string $file filename
106
-	 * @param bool $prepend prepend the style to the beginning of the list
107
-	 * @since 4.0.0
108
-	 */
109
-	public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void {
110
-		\OC_Util::addStyle($application, $file, $prepend);
111
-	}
112
-
113
-	/**
114
-	 * Add a standalone init js file that is loaded for initialization
115
-	 *
116
-	 * Be careful loading scripts using this method as they are loaded early
117
-	 * and block the initial page rendering. They should not have dependencies
118
-	 * on any other scripts than core-common and core-main.
119
-	 *
120
-	 * @since 28.0.0
121
-	 */
122
-	public static function addInitScript(string $application, string $file): void {
123
-		if (!empty($application)) {
124
-			$path = "$application/js/$file";
125
-		} else {
126
-			$path = "js/$file";
127
-		}
128
-
129
-		// We need to handle the translation BEFORE the init script
130
-		// is loaded, as the init script might use translations
131
-		if ($application !== 'core' && !str_contains($file, 'l10n')) {
132
-			self::addTranslations($application, null, true);
133
-		}
134
-
135
-		self::$scriptsInit[] = $path;
136
-	}
137
-
138
-	/**
139
-	 * add a javascript file
140
-	 *
141
-	 * @param string $application
142
-	 * @param string|null $file
143
-	 * @param string $afterAppId
144
-	 * @param bool $prepend
145
-	 * @since 4.0.0
146
-	 */
147
-	public static function addScript(string $application, ?string $file = null, string $afterAppId = 'core', bool $prepend = false): void {
148
-		if (!empty($application)) {
149
-			$path = "$application/js/$file";
150
-		} else {
151
-			$path = "js/$file";
152
-		}
153
-
154
-		// Inject js translations if we load a script for
155
-		// a specific app that is not core, as those js files
156
-		// need separate handling
157
-		if ($application !== 'core'
158
-			&& $file !== null
159
-			&& !str_contains($file, 'l10n')) {
160
-			self::addTranslations($application);
161
-		}
162
-
163
-		// store app in dependency list
164
-		if (!array_key_exists($application, self::$scriptDeps)) {
165
-			self::$scriptDeps[$application] = new AppScriptDependency($application, [$afterAppId]);
166
-		} else {
167
-			self::$scriptDeps[$application]->addDep($afterAppId);
168
-		}
169
-
170
-		if ($prepend) {
171
-			array_unshift(self::$scripts[$application], $path);
172
-		} else {
173
-			self::$scripts[$application][] = $path;
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * Return the list of scripts injected to the page
179
-	 *
180
-	 * @return array
181
-	 * @since 24.0.0
182
-	 */
183
-	public static function getScripts(): array {
184
-		// Sort scriptDeps into sortedScriptDeps
185
-		$scriptSort = \OC::$server->get(AppScriptSort::class);
186
-		$sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps);
187
-
188
-		// Flatten array and remove duplicates
189
-		$sortedScripts = array_merge([self::$scriptsInit], $sortedScripts);
190
-		$sortedScripts = array_merge(...array_values($sortedScripts));
191
-
192
-		// Override core-common and core-main order
193
-		if (in_array('core/js/main', $sortedScripts)) {
194
-			array_unshift($sortedScripts, 'core/js/main');
195
-		}
196
-		if (in_array('core/js/common', $sortedScripts)) {
197
-			array_unshift($sortedScripts, 'core/js/common');
198
-		}
199
-
200
-		return array_unique($sortedScripts);
201
-	}
202
-
203
-	/**
204
-	 * Add a translation JS file
205
-	 * @param string $application application id
206
-	 * @param string $languageCode language code, defaults to the current locale
207
-	 * @param bool $init whether the translations should be loaded early or not
208
-	 * @since 8.0.0
209
-	 */
210
-	public static function addTranslations($application, $languageCode = null, $init = false) {
211
-		if (is_null($languageCode)) {
212
-			$languageCode = \OC::$server->get(IFactory::class)->findLanguage($application);
213
-		}
214
-		if (!empty($application)) {
215
-			$path = "$application/l10n/$languageCode";
216
-		} else {
217
-			$path = "l10n/$languageCode";
218
-		}
219
-
220
-		if ($init) {
221
-			self::$scriptsInit[] = $path;
222
-		} else {
223
-			self::$scripts[$application][] = $path;
224
-		}
225
-	}
226
-
227
-	/**
228
-	 * Add a custom element to the header
229
-	 * If $text is null then the element will be written as empty element.
230
-	 * So use "" to get a closing tag.
231
-	 * @param string $tag tag name of the element
232
-	 * @param array $attributes array of attributes for the element
233
-	 * @param string $text the text content for the element
234
-	 * @since 4.0.0
235
-	 */
236
-	public static function addHeader($tag, $attributes, $text = null) {
237
-		\OC_Util::addHeader($tag, $attributes, $text);
238
-	}
239
-
240
-	/**
241
-	 * Creates an absolute url to the given app and file.
242
-	 * @param string $app app
243
-	 * @param string $file file
244
-	 * @param array $args array with param=>value, will be appended to the returned url
245
-	 *                    The value of $args will be urlencoded
246
-	 * @return string the url
247
-	 * @since 4.0.0 - parameter $args was added in 4.5.0
248
-	 */
249
-	public static function linkToAbsolute($app, $file, $args = []) {
250
-		$urlGenerator = \OC::$server->getURLGenerator();
251
-		return $urlGenerator->getAbsoluteURL(
252
-			$urlGenerator->linkTo($app, $file, $args)
253
-		);
254
-	}
255
-
256
-	/**
257
-	 * Creates an absolute url for remote use.
258
-	 * @param string $service id
259
-	 * @return string the url
260
-	 * @since 4.0.0
261
-	 */
262
-	public static function linkToRemote($service) {
263
-		$urlGenerator = \OC::$server->getURLGenerator();
264
-		$remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service;
265
-		return $urlGenerator->getAbsoluteURL(
266
-			$remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '')
267
-		);
268
-	}
269
-
270
-	/**
271
-	 * Returns the server host name without an eventual port number
272
-	 * @return string the server hostname
273
-	 * @since 5.0.0
274
-	 */
275
-	public static function getServerHostName() {
276
-		$host_name = \OC::$server->getRequest()->getServerHost();
277
-		// strip away port number (if existing)
278
-		$colon_pos = strpos($host_name, ':');
279
-		if ($colon_pos != false) {
280
-			$host_name = substr($host_name, 0, $colon_pos);
281
-		}
282
-		return $host_name;
283
-	}
284
-
285
-	/**
286
-	 * Returns the default email address
287
-	 * @param string $user_part the user part of the address
288
-	 * @return string the default email address
289
-	 *
290
-	 * Assembles a default email address (using the server hostname
291
-	 * and the given user part, and returns it
292
-	 * Example: when given lostpassword-noreply as $user_part param,
293
-	 *     and is currently accessed via http(s)://example.com/,
294
-	 *     it would return '[email protected]'
295
-	 *
296
-	 * If the configuration value 'mail_from_address' is set in
297
-	 * config.php, this value will override the $user_part that
298
-	 * is passed to this function
299
-	 * @since 5.0.0
300
-	 */
301
-	public static function getDefaultEmailAddress(string $user_part): string {
302
-		$config = \OC::$server->getConfig();
303
-		$user_part = $config->getSystemValueString('mail_from_address', $user_part);
304
-		$host_name = self::getServerHostName();
305
-		$host_name = $config->getSystemValueString('mail_domain', $host_name);
306
-		$defaultEmailAddress = $user_part . '@' . $host_name;
307
-
308
-		$mailer = \OC::$server->get(IMailer::class);
309
-		if ($mailer->validateMailAddress($defaultEmailAddress)) {
310
-			return $defaultEmailAddress;
311
-		}
312
-
313
-		// in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain'
314
-		return $user_part . '@localhost.localdomain';
315
-	}
316
-
317
-	/**
318
-	 * Converts string to int of float depending if it fits an int
319
-	 * @param numeric-string|float|int $number numeric string
320
-	 * @return int|float int if it fits, float if it is too big
321
-	 * @since 26.0.0
322
-	 */
323
-	public static function numericToNumber(string|float|int $number): int|float {
324
-		/* This is a hack to cast to (int|float) */
325
-		return 0 + (string)$number;
326
-	}
327
-
328
-	/**
329
-	 * Make a human file size (2048 to 2 kB)
330
-	 * @param int|float $bytes file size in bytes
331
-	 * @return string a human readable file size
332
-	 * @since 4.0.0
333
-	 */
334
-	public static function humanFileSize(int|float $bytes): string {
335
-		if ($bytes < 0) {
336
-			return '?';
337
-		}
338
-		if ($bytes < 1024) {
339
-			return "$bytes B";
340
-		}
341
-		$bytes = round($bytes / 1024, 0);
342
-		if ($bytes < 1024) {
343
-			return "$bytes KB";
344
-		}
345
-		$bytes = round($bytes / 1024, 1);
346
-		if ($bytes < 1024) {
347
-			return "$bytes MB";
348
-		}
349
-		$bytes = round($bytes / 1024, 1);
350
-		if ($bytes < 1024) {
351
-			return "$bytes GB";
352
-		}
353
-		$bytes = round($bytes / 1024, 1);
354
-		if ($bytes < 1024) {
355
-			return "$bytes TB";
356
-		}
357
-
358
-		$bytes = round($bytes / 1024, 1);
359
-		return "$bytes PB";
360
-	}
361
-
362
-	/**
363
-	 * Make a computer file size (2 kB to 2048)
364
-	 * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
365
-	 *
366
-	 * @param string $str file size in a fancy format
367
-	 * @return false|int|float a file size in bytes
368
-	 * @since 4.0.0
369
-	 */
370
-	public static function computerFileSize(string $str): false|int|float {
371
-		$str = strtolower($str);
372
-		if (is_numeric($str)) {
373
-			return Util::numericToNumber($str);
374
-		}
375
-
376
-		$bytes_array = [
377
-			'b' => 1,
378
-			'k' => 1024,
379
-			'kb' => 1024,
380
-			'mb' => 1024 * 1024,
381
-			'm' => 1024 * 1024,
382
-			'gb' => 1024 * 1024 * 1024,
383
-			'g' => 1024 * 1024 * 1024,
384
-			'tb' => 1024 * 1024 * 1024 * 1024,
385
-			't' => 1024 * 1024 * 1024 * 1024,
386
-			'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
387
-			'p' => 1024 * 1024 * 1024 * 1024 * 1024,
388
-		];
389
-
390
-		$bytes = (float)$str;
391
-
392
-		if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) {
393
-			$bytes *= $bytes_array[$matches[1]];
394
-		} else {
395
-			return false;
396
-		}
397
-
398
-		return Util::numericToNumber(round($bytes));
399
-	}
400
-
401
-	/**
402
-	 * connects a function to a hook
403
-	 *
404
-	 * @param string $signalClass class name of emitter
405
-	 * @param string $signalName name of signal
406
-	 * @param string|object $slotClass class name of slot
407
-	 * @param string $slotName name of slot
408
-	 * @return bool
409
-	 *
410
-	 * This function makes it very easy to connect to use hooks.
411
-	 *
412
-	 * TODO: write example
413
-	 * @since 4.0.0
414
-	 * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener
415
-	 */
416
-	public static function connectHook($signalClass, $signalName, $slotClass, $slotName) {
417
-		return \OC_Hook::connect($signalClass, $signalName, $slotClass, $slotName);
418
-	}
419
-
420
-	/**
421
-	 * Emits a signal. To get data from the slot use references!
422
-	 * @param string $signalclass class name of emitter
423
-	 * @param string $signalname name of signal
424
-	 * @param array $params default: array() array with additional data
425
-	 * @return bool true if slots exists or false if not
426
-	 *
427
-	 * TODO: write example
428
-	 * @since 4.0.0
429
-	 * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTypedEvent
430
-	 */
431
-	public static function emitHook($signalclass, $signalname, $params = []) {
432
-		return \OC_Hook::emit($signalclass, $signalname, $params);
433
-	}
434
-
435
-	/**
436
-	 * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare
437
-	 * multiple Template elements which invoke `callRegister`. If the value
438
-	 * would not be cached these unit-tests would fail.
439
-	 * @var string
440
-	 */
441
-	private static $token = '';
442
-
443
-	/**
444
-	 * Register an get/post call. This is important to prevent CSRF attacks
445
-	 * @since 4.5.0
446
-	 * @deprecated 32.0.0 directly use CsrfTokenManager instead
447
-	 */
448
-	public static function callRegister() {
449
-		if (self::$token === '') {
450
-			self::$token = \OC::$server->get(CsrfTokenManager::class)->getToken()->getEncryptedValue();
451
-		}
452
-		return self::$token;
453
-	}
454
-
455
-	/**
456
-	 * Used to sanitize HTML
457
-	 *
458
-	 * This function is used to sanitize HTML and should be applied on any
459
-	 * string or array of strings before displaying it on a web page.
460
-	 *
461
-	 * @param string|string[] $value
462
-	 * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter.
463
-	 * @since 4.5.0
464
-	 */
465
-	public static function sanitizeHTML($value) {
466
-		return \OC_Util::sanitizeHTML($value);
467
-	}
468
-
469
-	/**
470
-	 * Public function to encode url parameters
471
-	 *
472
-	 * This function is used to encode path to file before output.
473
-	 * Encoding is done according to RFC 3986 with one exception:
474
-	 * Character '/' is preserved as is.
475
-	 *
476
-	 * @param string $component part of URI to encode
477
-	 * @return string
478
-	 * @since 6.0.0
479
-	 */
480
-	public static function encodePath($component) {
481
-		return \OC_Util::encodePath($component);
482
-	}
483
-
484
-	/**
485
-	 * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
486
-	 *
487
-	 * @param array $input The array to work on
488
-	 * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
489
-	 * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
490
-	 * @return array
491
-	 * @since 4.5.0
492
-	 */
493
-	public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
494
-		return \OC_Helper::mb_array_change_key_case($input, $case, $encoding);
495
-	}
496
-
497
-	/**
498
-	 * performs a search in a nested array
499
-	 *
500
-	 * @param array $haystack the array to be searched
501
-	 * @param string $needle the search string
502
-	 * @param mixed $index optional, only search this key name
503
-	 * @return mixed the key of the matching field, otherwise false
504
-	 * @since 4.5.0
505
-	 * @deprecated 15.0.0
506
-	 */
507
-	public static function recursiveArraySearch($haystack, $needle, $index = null) {
508
-		return \OC_Helper::recursiveArraySearch($haystack, $needle, $index);
509
-	}
510
-
511
-	/**
512
-	 * calculates the maximum upload size respecting system settings, free space and user quota
513
-	 *
514
-	 * @param string $dir the current folder where the user currently operates
515
-	 * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
516
-	 * @return int|float number of bytes representing
517
-	 * @since 5.0.0
518
-	 */
519
-	public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float {
520
-		return \OC_Helper::maxUploadFilesize($dir, $free);
521
-	}
522
-
523
-	/**
524
-	 * Calculate free space left within user quota
525
-	 * @param string $dir the current folder where the user currently operates
526
-	 * @return int|float number of bytes representing
527
-	 * @since 7.0.0
528
-	 */
529
-	public static function freeSpace(string $dir): int|float {
530
-		return \OC_Helper::freeSpace($dir);
531
-	}
532
-
533
-	/**
534
-	 * Calculate PHP upload limit
535
-	 *
536
-	 * @return int|float number of bytes representing
537
-	 * @since 7.0.0
538
-	 */
539
-	public static function uploadLimit(): int|float {
540
-		return \OC_Helper::uploadLimit();
541
-	}
542
-
543
-	/**
544
-	 * Compare two strings to provide a natural sort
545
-	 * @param string $a first string to compare
546
-	 * @param string $b second string to compare
547
-	 * @return int -1 if $b comes before $a, 1 if $a comes before $b
548
-	 *             or 0 if the strings are identical
549
-	 * @since 7.0.0
550
-	 */
551
-	public static function naturalSortCompare($a, $b) {
552
-		return \OC\NaturalSort::getInstance()->compare($a, $b);
553
-	}
554
-
555
-	/**
556
-	 * Check if a password is required for each public link
557
-	 *
558
-	 * @param bool $checkGroupMembership Check group membership exclusion
559
-	 * @return boolean
560
-	 * @since 7.0.0
561
-	 */
562
-	public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
563
-		return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership);
564
-	}
565
-
566
-	/**
567
-	 * check if share API enforces a default expire date
568
-	 * @return boolean
569
-	 * @since 8.0.0
570
-	 */
571
-	public static function isDefaultExpireDateEnforced() {
572
-		return \OC_Util::isDefaultExpireDateEnforced();
573
-	}
574
-
575
-	protected static $needUpgradeCache = null;
576
-
577
-	/**
578
-	 * Checks whether the current version needs upgrade.
579
-	 *
580
-	 * @return bool true if upgrade is needed, false otherwise
581
-	 * @since 7.0.0
582
-	 */
583
-	public static function needUpgrade() {
584
-		if (!isset(self::$needUpgradeCache)) {
585
-			self::$needUpgradeCache = \OC_Util::needUpgrade(\OC::$server->getSystemConfig());
586
-		}
587
-		return self::$needUpgradeCache;
588
-	}
589
-
590
-	/**
591
-	 * Sometimes a string has to be shortened to fit within a certain maximum
592
-	 * data length in bytes. substr() you may break multibyte characters,
593
-	 * because it operates on single byte level. mb_substr() operates on
594
-	 * characters, so does not ensure that the shortened string satisfies the
595
-	 * max length in bytes.
596
-	 *
597
-	 * For example, json_encode is messing with multibyte characters a lot,
598
-	 * replacing them with something along "\u1234".
599
-	 *
600
-	 * This function shortens the string with by $accuracy (-5) from
601
-	 * $dataLength characters, until it fits within $dataLength bytes.
602
-	 *
603
-	 * @since 23.0.0
604
-	 */
605
-	public static function shortenMultibyteString(string $subject, int $dataLength, int $accuracy = 5): string {
606
-		$temp = mb_substr($subject, 0, $dataLength);
607
-		// json encodes encapsulates the string in double quotes, they need to be substracted
608
-		while ((strlen(json_encode($temp)) - 2) > $dataLength) {
609
-			$temp = mb_substr($temp, 0, -$accuracy);
610
-		}
611
-		return $temp;
612
-	}
613
-
614
-	/**
615
-	 * Check if a function is enabled in the php configuration
616
-	 *
617
-	 * @since 25.0.0
618
-	 */
619
-	public static function isFunctionEnabled(string $functionName): bool {
620
-		if (!function_exists($functionName)) {
621
-			return false;
622
-		}
623
-		$ini = Server::get(IniGetWrapper::class);
624
-		$disabled = explode(',', $ini->get('disable_functions') ?: '');
625
-		$disabled = array_map('trim', $disabled);
626
-		if (in_array($functionName, $disabled)) {
627
-			return false;
628
-		}
629
-		return true;
630
-	}
28
+    private static ?IManager $shareManager = null;
29
+
30
+    private static array $scriptsInit = [];
31
+    private static array $scripts = [];
32
+    private static array $scriptDeps = [];
33
+
34
+    /**
35
+     * get the current installed version of Nextcloud
36
+     * @return array
37
+     * @since 4.0.0
38
+     * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion
39
+     */
40
+    public static function getVersion() {
41
+        return Server::get(ServerVersion::class)->getVersion();
42
+    }
43
+
44
+    /**
45
+     * @since 17.0.0
46
+     */
47
+    public static function hasExtendedSupport(): bool {
48
+        try {
49
+            /** @var \OCP\Support\Subscription\IRegistry */
50
+            $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
51
+            return $subscriptionRegistry->delegateHasExtendedSupport();
52
+        } catch (ContainerExceptionInterface $e) {
53
+        }
54
+        return \OC::$server->getConfig()->getSystemValueBool('extendedSupport', false);
55
+    }
56
+
57
+    /**
58
+     * Set current update channel
59
+     * @param string $channel
60
+     * @since 8.1.0
61
+     */
62
+    public static function setChannel($channel) {
63
+        \OC::$server->getConfig()->setSystemValue('updater.release.channel', $channel);
64
+    }
65
+
66
+    /**
67
+     * Get current update channel
68
+     * @return string
69
+     * @since 8.1.0
70
+     * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel
71
+     */
72
+    public static function getChannel() {
73
+        return \OCP\Server::get(ServerVersion::class)->getChannel();
74
+    }
75
+
76
+    /**
77
+     * check if sharing is disabled for the current user
78
+     *
79
+     * @return boolean
80
+     * @since 7.0.0
81
+     * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser
82
+     */
83
+    public static function isSharingDisabledForUser() {
84
+        if (self::$shareManager === null) {
85
+            self::$shareManager = Server::get(IManager::class);
86
+        }
87
+
88
+        $user = Server::get(\OCP\IUserSession::class)->getUser();
89
+
90
+        return self::$shareManager->sharingDisabledForUser($user?->getUID());
91
+    }
92
+
93
+    /**
94
+     * get l10n object
95
+     * @since 6.0.0 - parameter $language was added in 8.0.0
96
+     */
97
+    public static function getL10N(string $application, ?string $language = null): IL10N {
98
+        return Server::get(\OCP\L10N\IFactory::class)->get($application, $language);
99
+    }
100
+
101
+    /**
102
+     * Add a css file
103
+     *
104
+     * @param string $application application id
105
+     * @param ?string $file filename
106
+     * @param bool $prepend prepend the style to the beginning of the list
107
+     * @since 4.0.0
108
+     */
109
+    public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void {
110
+        \OC_Util::addStyle($application, $file, $prepend);
111
+    }
112
+
113
+    /**
114
+     * Add a standalone init js file that is loaded for initialization
115
+     *
116
+     * Be careful loading scripts using this method as they are loaded early
117
+     * and block the initial page rendering. They should not have dependencies
118
+     * on any other scripts than core-common and core-main.
119
+     *
120
+     * @since 28.0.0
121
+     */
122
+    public static function addInitScript(string $application, string $file): void {
123
+        if (!empty($application)) {
124
+            $path = "$application/js/$file";
125
+        } else {
126
+            $path = "js/$file";
127
+        }
128
+
129
+        // We need to handle the translation BEFORE the init script
130
+        // is loaded, as the init script might use translations
131
+        if ($application !== 'core' && !str_contains($file, 'l10n')) {
132
+            self::addTranslations($application, null, true);
133
+        }
134
+
135
+        self::$scriptsInit[] = $path;
136
+    }
137
+
138
+    /**
139
+     * add a javascript file
140
+     *
141
+     * @param string $application
142
+     * @param string|null $file
143
+     * @param string $afterAppId
144
+     * @param bool $prepend
145
+     * @since 4.0.0
146
+     */
147
+    public static function addScript(string $application, ?string $file = null, string $afterAppId = 'core', bool $prepend = false): void {
148
+        if (!empty($application)) {
149
+            $path = "$application/js/$file";
150
+        } else {
151
+            $path = "js/$file";
152
+        }
153
+
154
+        // Inject js translations if we load a script for
155
+        // a specific app that is not core, as those js files
156
+        // need separate handling
157
+        if ($application !== 'core'
158
+            && $file !== null
159
+            && !str_contains($file, 'l10n')) {
160
+            self::addTranslations($application);
161
+        }
162
+
163
+        // store app in dependency list
164
+        if (!array_key_exists($application, self::$scriptDeps)) {
165
+            self::$scriptDeps[$application] = new AppScriptDependency($application, [$afterAppId]);
166
+        } else {
167
+            self::$scriptDeps[$application]->addDep($afterAppId);
168
+        }
169
+
170
+        if ($prepend) {
171
+            array_unshift(self::$scripts[$application], $path);
172
+        } else {
173
+            self::$scripts[$application][] = $path;
174
+        }
175
+    }
176
+
177
+    /**
178
+     * Return the list of scripts injected to the page
179
+     *
180
+     * @return array
181
+     * @since 24.0.0
182
+     */
183
+    public static function getScripts(): array {
184
+        // Sort scriptDeps into sortedScriptDeps
185
+        $scriptSort = \OC::$server->get(AppScriptSort::class);
186
+        $sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps);
187
+
188
+        // Flatten array and remove duplicates
189
+        $sortedScripts = array_merge([self::$scriptsInit], $sortedScripts);
190
+        $sortedScripts = array_merge(...array_values($sortedScripts));
191
+
192
+        // Override core-common and core-main order
193
+        if (in_array('core/js/main', $sortedScripts)) {
194
+            array_unshift($sortedScripts, 'core/js/main');
195
+        }
196
+        if (in_array('core/js/common', $sortedScripts)) {
197
+            array_unshift($sortedScripts, 'core/js/common');
198
+        }
199
+
200
+        return array_unique($sortedScripts);
201
+    }
202
+
203
+    /**
204
+     * Add a translation JS file
205
+     * @param string $application application id
206
+     * @param string $languageCode language code, defaults to the current locale
207
+     * @param bool $init whether the translations should be loaded early or not
208
+     * @since 8.0.0
209
+     */
210
+    public static function addTranslations($application, $languageCode = null, $init = false) {
211
+        if (is_null($languageCode)) {
212
+            $languageCode = \OC::$server->get(IFactory::class)->findLanguage($application);
213
+        }
214
+        if (!empty($application)) {
215
+            $path = "$application/l10n/$languageCode";
216
+        } else {
217
+            $path = "l10n/$languageCode";
218
+        }
219
+
220
+        if ($init) {
221
+            self::$scriptsInit[] = $path;
222
+        } else {
223
+            self::$scripts[$application][] = $path;
224
+        }
225
+    }
226
+
227
+    /**
228
+     * Add a custom element to the header
229
+     * If $text is null then the element will be written as empty element.
230
+     * So use "" to get a closing tag.
231
+     * @param string $tag tag name of the element
232
+     * @param array $attributes array of attributes for the element
233
+     * @param string $text the text content for the element
234
+     * @since 4.0.0
235
+     */
236
+    public static function addHeader($tag, $attributes, $text = null) {
237
+        \OC_Util::addHeader($tag, $attributes, $text);
238
+    }
239
+
240
+    /**
241
+     * Creates an absolute url to the given app and file.
242
+     * @param string $app app
243
+     * @param string $file file
244
+     * @param array $args array with param=>value, will be appended to the returned url
245
+     *                    The value of $args will be urlencoded
246
+     * @return string the url
247
+     * @since 4.0.0 - parameter $args was added in 4.5.0
248
+     */
249
+    public static function linkToAbsolute($app, $file, $args = []) {
250
+        $urlGenerator = \OC::$server->getURLGenerator();
251
+        return $urlGenerator->getAbsoluteURL(
252
+            $urlGenerator->linkTo($app, $file, $args)
253
+        );
254
+    }
255
+
256
+    /**
257
+     * Creates an absolute url for remote use.
258
+     * @param string $service id
259
+     * @return string the url
260
+     * @since 4.0.0
261
+     */
262
+    public static function linkToRemote($service) {
263
+        $urlGenerator = \OC::$server->getURLGenerator();
264
+        $remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service;
265
+        return $urlGenerator->getAbsoluteURL(
266
+            $remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '')
267
+        );
268
+    }
269
+
270
+    /**
271
+     * Returns the server host name without an eventual port number
272
+     * @return string the server hostname
273
+     * @since 5.0.0
274
+     */
275
+    public static function getServerHostName() {
276
+        $host_name = \OC::$server->getRequest()->getServerHost();
277
+        // strip away port number (if existing)
278
+        $colon_pos = strpos($host_name, ':');
279
+        if ($colon_pos != false) {
280
+            $host_name = substr($host_name, 0, $colon_pos);
281
+        }
282
+        return $host_name;
283
+    }
284
+
285
+    /**
286
+     * Returns the default email address
287
+     * @param string $user_part the user part of the address
288
+     * @return string the default email address
289
+     *
290
+     * Assembles a default email address (using the server hostname
291
+     * and the given user part, and returns it
292
+     * Example: when given lostpassword-noreply as $user_part param,
293
+     *     and is currently accessed via http(s)://example.com/,
294
+     *     it would return '[email protected]'
295
+     *
296
+     * If the configuration value 'mail_from_address' is set in
297
+     * config.php, this value will override the $user_part that
298
+     * is passed to this function
299
+     * @since 5.0.0
300
+     */
301
+    public static function getDefaultEmailAddress(string $user_part): string {
302
+        $config = \OC::$server->getConfig();
303
+        $user_part = $config->getSystemValueString('mail_from_address', $user_part);
304
+        $host_name = self::getServerHostName();
305
+        $host_name = $config->getSystemValueString('mail_domain', $host_name);
306
+        $defaultEmailAddress = $user_part . '@' . $host_name;
307
+
308
+        $mailer = \OC::$server->get(IMailer::class);
309
+        if ($mailer->validateMailAddress($defaultEmailAddress)) {
310
+            return $defaultEmailAddress;
311
+        }
312
+
313
+        // in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain'
314
+        return $user_part . '@localhost.localdomain';
315
+    }
316
+
317
+    /**
318
+     * Converts string to int of float depending if it fits an int
319
+     * @param numeric-string|float|int $number numeric string
320
+     * @return int|float int if it fits, float if it is too big
321
+     * @since 26.0.0
322
+     */
323
+    public static function numericToNumber(string|float|int $number): int|float {
324
+        /* This is a hack to cast to (int|float) */
325
+        return 0 + (string)$number;
326
+    }
327
+
328
+    /**
329
+     * Make a human file size (2048 to 2 kB)
330
+     * @param int|float $bytes file size in bytes
331
+     * @return string a human readable file size
332
+     * @since 4.0.0
333
+     */
334
+    public static function humanFileSize(int|float $bytes): string {
335
+        if ($bytes < 0) {
336
+            return '?';
337
+        }
338
+        if ($bytes < 1024) {
339
+            return "$bytes B";
340
+        }
341
+        $bytes = round($bytes / 1024, 0);
342
+        if ($bytes < 1024) {
343
+            return "$bytes KB";
344
+        }
345
+        $bytes = round($bytes / 1024, 1);
346
+        if ($bytes < 1024) {
347
+            return "$bytes MB";
348
+        }
349
+        $bytes = round($bytes / 1024, 1);
350
+        if ($bytes < 1024) {
351
+            return "$bytes GB";
352
+        }
353
+        $bytes = round($bytes / 1024, 1);
354
+        if ($bytes < 1024) {
355
+            return "$bytes TB";
356
+        }
357
+
358
+        $bytes = round($bytes / 1024, 1);
359
+        return "$bytes PB";
360
+    }
361
+
362
+    /**
363
+     * Make a computer file size (2 kB to 2048)
364
+     * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
365
+     *
366
+     * @param string $str file size in a fancy format
367
+     * @return false|int|float a file size in bytes
368
+     * @since 4.0.0
369
+     */
370
+    public static function computerFileSize(string $str): false|int|float {
371
+        $str = strtolower($str);
372
+        if (is_numeric($str)) {
373
+            return Util::numericToNumber($str);
374
+        }
375
+
376
+        $bytes_array = [
377
+            'b' => 1,
378
+            'k' => 1024,
379
+            'kb' => 1024,
380
+            'mb' => 1024 * 1024,
381
+            'm' => 1024 * 1024,
382
+            'gb' => 1024 * 1024 * 1024,
383
+            'g' => 1024 * 1024 * 1024,
384
+            'tb' => 1024 * 1024 * 1024 * 1024,
385
+            't' => 1024 * 1024 * 1024 * 1024,
386
+            'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
387
+            'p' => 1024 * 1024 * 1024 * 1024 * 1024,
388
+        ];
389
+
390
+        $bytes = (float)$str;
391
+
392
+        if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) {
393
+            $bytes *= $bytes_array[$matches[1]];
394
+        } else {
395
+            return false;
396
+        }
397
+
398
+        return Util::numericToNumber(round($bytes));
399
+    }
400
+
401
+    /**
402
+     * connects a function to a hook
403
+     *
404
+     * @param string $signalClass class name of emitter
405
+     * @param string $signalName name of signal
406
+     * @param string|object $slotClass class name of slot
407
+     * @param string $slotName name of slot
408
+     * @return bool
409
+     *
410
+     * This function makes it very easy to connect to use hooks.
411
+     *
412
+     * TODO: write example
413
+     * @since 4.0.0
414
+     * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener
415
+     */
416
+    public static function connectHook($signalClass, $signalName, $slotClass, $slotName) {
417
+        return \OC_Hook::connect($signalClass, $signalName, $slotClass, $slotName);
418
+    }
419
+
420
+    /**
421
+     * Emits a signal. To get data from the slot use references!
422
+     * @param string $signalclass class name of emitter
423
+     * @param string $signalname name of signal
424
+     * @param array $params default: array() array with additional data
425
+     * @return bool true if slots exists or false if not
426
+     *
427
+     * TODO: write example
428
+     * @since 4.0.0
429
+     * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTypedEvent
430
+     */
431
+    public static function emitHook($signalclass, $signalname, $params = []) {
432
+        return \OC_Hook::emit($signalclass, $signalname, $params);
433
+    }
434
+
435
+    /**
436
+     * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare
437
+     * multiple Template elements which invoke `callRegister`. If the value
438
+     * would not be cached these unit-tests would fail.
439
+     * @var string
440
+     */
441
+    private static $token = '';
442
+
443
+    /**
444
+     * Register an get/post call. This is important to prevent CSRF attacks
445
+     * @since 4.5.0
446
+     * @deprecated 32.0.0 directly use CsrfTokenManager instead
447
+     */
448
+    public static function callRegister() {
449
+        if (self::$token === '') {
450
+            self::$token = \OC::$server->get(CsrfTokenManager::class)->getToken()->getEncryptedValue();
451
+        }
452
+        return self::$token;
453
+    }
454
+
455
+    /**
456
+     * Used to sanitize HTML
457
+     *
458
+     * This function is used to sanitize HTML and should be applied on any
459
+     * string or array of strings before displaying it on a web page.
460
+     *
461
+     * @param string|string[] $value
462
+     * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter.
463
+     * @since 4.5.0
464
+     */
465
+    public static function sanitizeHTML($value) {
466
+        return \OC_Util::sanitizeHTML($value);
467
+    }
468
+
469
+    /**
470
+     * Public function to encode url parameters
471
+     *
472
+     * This function is used to encode path to file before output.
473
+     * Encoding is done according to RFC 3986 with one exception:
474
+     * Character '/' is preserved as is.
475
+     *
476
+     * @param string $component part of URI to encode
477
+     * @return string
478
+     * @since 6.0.0
479
+     */
480
+    public static function encodePath($component) {
481
+        return \OC_Util::encodePath($component);
482
+    }
483
+
484
+    /**
485
+     * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
486
+     *
487
+     * @param array $input The array to work on
488
+     * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
489
+     * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
490
+     * @return array
491
+     * @since 4.5.0
492
+     */
493
+    public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
494
+        return \OC_Helper::mb_array_change_key_case($input, $case, $encoding);
495
+    }
496
+
497
+    /**
498
+     * performs a search in a nested array
499
+     *
500
+     * @param array $haystack the array to be searched
501
+     * @param string $needle the search string
502
+     * @param mixed $index optional, only search this key name
503
+     * @return mixed the key of the matching field, otherwise false
504
+     * @since 4.5.0
505
+     * @deprecated 15.0.0
506
+     */
507
+    public static function recursiveArraySearch($haystack, $needle, $index = null) {
508
+        return \OC_Helper::recursiveArraySearch($haystack, $needle, $index);
509
+    }
510
+
511
+    /**
512
+     * calculates the maximum upload size respecting system settings, free space and user quota
513
+     *
514
+     * @param string $dir the current folder where the user currently operates
515
+     * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
516
+     * @return int|float number of bytes representing
517
+     * @since 5.0.0
518
+     */
519
+    public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float {
520
+        return \OC_Helper::maxUploadFilesize($dir, $free);
521
+    }
522
+
523
+    /**
524
+     * Calculate free space left within user quota
525
+     * @param string $dir the current folder where the user currently operates
526
+     * @return int|float number of bytes representing
527
+     * @since 7.0.0
528
+     */
529
+    public static function freeSpace(string $dir): int|float {
530
+        return \OC_Helper::freeSpace($dir);
531
+    }
532
+
533
+    /**
534
+     * Calculate PHP upload limit
535
+     *
536
+     * @return int|float number of bytes representing
537
+     * @since 7.0.0
538
+     */
539
+    public static function uploadLimit(): int|float {
540
+        return \OC_Helper::uploadLimit();
541
+    }
542
+
543
+    /**
544
+     * Compare two strings to provide a natural sort
545
+     * @param string $a first string to compare
546
+     * @param string $b second string to compare
547
+     * @return int -1 if $b comes before $a, 1 if $a comes before $b
548
+     *             or 0 if the strings are identical
549
+     * @since 7.0.0
550
+     */
551
+    public static function naturalSortCompare($a, $b) {
552
+        return \OC\NaturalSort::getInstance()->compare($a, $b);
553
+    }
554
+
555
+    /**
556
+     * Check if a password is required for each public link
557
+     *
558
+     * @param bool $checkGroupMembership Check group membership exclusion
559
+     * @return boolean
560
+     * @since 7.0.0
561
+     */
562
+    public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
563
+        return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership);
564
+    }
565
+
566
+    /**
567
+     * check if share API enforces a default expire date
568
+     * @return boolean
569
+     * @since 8.0.0
570
+     */
571
+    public static function isDefaultExpireDateEnforced() {
572
+        return \OC_Util::isDefaultExpireDateEnforced();
573
+    }
574
+
575
+    protected static $needUpgradeCache = null;
576
+
577
+    /**
578
+     * Checks whether the current version needs upgrade.
579
+     *
580
+     * @return bool true if upgrade is needed, false otherwise
581
+     * @since 7.0.0
582
+     */
583
+    public static function needUpgrade() {
584
+        if (!isset(self::$needUpgradeCache)) {
585
+            self::$needUpgradeCache = \OC_Util::needUpgrade(\OC::$server->getSystemConfig());
586
+        }
587
+        return self::$needUpgradeCache;
588
+    }
589
+
590
+    /**
591
+     * Sometimes a string has to be shortened to fit within a certain maximum
592
+     * data length in bytes. substr() you may break multibyte characters,
593
+     * because it operates on single byte level. mb_substr() operates on
594
+     * characters, so does not ensure that the shortened string satisfies the
595
+     * max length in bytes.
596
+     *
597
+     * For example, json_encode is messing with multibyte characters a lot,
598
+     * replacing them with something along "\u1234".
599
+     *
600
+     * This function shortens the string with by $accuracy (-5) from
601
+     * $dataLength characters, until it fits within $dataLength bytes.
602
+     *
603
+     * @since 23.0.0
604
+     */
605
+    public static function shortenMultibyteString(string $subject, int $dataLength, int $accuracy = 5): string {
606
+        $temp = mb_substr($subject, 0, $dataLength);
607
+        // json encodes encapsulates the string in double quotes, they need to be substracted
608
+        while ((strlen(json_encode($temp)) - 2) > $dataLength) {
609
+            $temp = mb_substr($temp, 0, -$accuracy);
610
+        }
611
+        return $temp;
612
+    }
613
+
614
+    /**
615
+     * Check if a function is enabled in the php configuration
616
+     *
617
+     * @since 25.0.0
618
+     */
619
+    public static function isFunctionEnabled(string $functionName): bool {
620
+        if (!function_exists($functionName)) {
621
+            return false;
622
+        }
623
+        $ini = Server::get(IniGetWrapper::class);
624
+        $disabled = explode(',', $ini->get('disable_functions') ?: '');
625
+        $disabled = array_map('trim', $disabled);
626
+        if (in_array($functionName, $disabled)) {
627
+            return false;
628
+        }
629
+        return true;
630
+    }
631 631
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -261,9 +261,9 @@  discard block
 block discarded – undo
261 261
 	 */
262 262
 	public static function linkToRemote($service) {
263 263
 		$urlGenerator = \OC::$server->getURLGenerator();
264
-		$remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service;
264
+		$remoteBase = $urlGenerator->linkTo('', 'remote.php').'/'.$service;
265 265
 		return $urlGenerator->getAbsoluteURL(
266
-			$remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '')
266
+			$remoteBase.(($service[strlen($service) - 1] != '/') ? '/' : '')
267 267
 		);
268 268
 	}
269 269
 
@@ -303,7 +303,7 @@  discard block
 block discarded – undo
303 303
 		$user_part = $config->getSystemValueString('mail_from_address', $user_part);
304 304
 		$host_name = self::getServerHostName();
305 305
 		$host_name = $config->getSystemValueString('mail_domain', $host_name);
306
-		$defaultEmailAddress = $user_part . '@' . $host_name;
306
+		$defaultEmailAddress = $user_part.'@'.$host_name;
307 307
 
308 308
 		$mailer = \OC::$server->get(IMailer::class);
309 309
 		if ($mailer->validateMailAddress($defaultEmailAddress)) {
@@ -311,7 +311,7 @@  discard block
 block discarded – undo
311 311
 		}
312 312
 
313 313
 		// in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain'
314
-		return $user_part . '@localhost.localdomain';
314
+		return $user_part.'@localhost.localdomain';
315 315
 	}
316 316
 
317 317
 	/**
@@ -320,9 +320,9 @@  discard block
 block discarded – undo
320 320
 	 * @return int|float int if it fits, float if it is too big
321 321
 	 * @since 26.0.0
322 322
 	 */
323
-	public static function numericToNumber(string|float|int $number): int|float {
323
+	public static function numericToNumber(string | float | int $number): int | float {
324 324
 		/* This is a hack to cast to (int|float) */
325
-		return 0 + (string)$number;
325
+		return 0 + (string) $number;
326 326
 	}
327 327
 
328 328
 	/**
@@ -331,7 +331,7 @@  discard block
 block discarded – undo
331 331
 	 * @return string a human readable file size
332 332
 	 * @since 4.0.0
333 333
 	 */
334
-	public static function humanFileSize(int|float $bytes): string {
334
+	public static function humanFileSize(int | float $bytes): string {
335 335
 		if ($bytes < 0) {
336 336
 			return '?';
337 337
 		}
@@ -367,7 +367,7 @@  discard block
 block discarded – undo
367 367
 	 * @return false|int|float a file size in bytes
368 368
 	 * @since 4.0.0
369 369
 	 */
370
-	public static function computerFileSize(string $str): false|int|float {
370
+	public static function computerFileSize(string $str): false | int | float {
371 371
 		$str = strtolower($str);
372 372
 		if (is_numeric($str)) {
373 373
 			return Util::numericToNumber($str);
@@ -387,7 +387,7 @@  discard block
 block discarded – undo
387 387
 			'p' => 1024 * 1024 * 1024 * 1024 * 1024,
388 388
 		];
389 389
 
390
-		$bytes = (float)$str;
390
+		$bytes = (float) $str;
391 391
 
392 392
 		if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) {
393 393
 			$bytes *= $bytes_array[$matches[1]];
@@ -516,7 +516,7 @@  discard block
 block discarded – undo
516 516
 	 * @return int|float number of bytes representing
517 517
 	 * @since 5.0.0
518 518
 	 */
519
-	public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float {
519
+	public static function maxUploadFilesize(string $dir, int | float | null $free = null): int | float {
520 520
 		return \OC_Helper::maxUploadFilesize($dir, $free);
521 521
 	}
522 522
 
@@ -526,7 +526,7 @@  discard block
 block discarded – undo
526 526
 	 * @return int|float number of bytes representing
527 527
 	 * @since 7.0.0
528 528
 	 */
529
-	public static function freeSpace(string $dir): int|float {
529
+	public static function freeSpace(string $dir): int | float {
530 530
 		return \OC_Helper::freeSpace($dir);
531 531
 	}
532 532
 
@@ -536,7 +536,7 @@  discard block
 block discarded – undo
536 536
 	 * @return int|float number of bytes representing
537 537
 	 * @since 7.0.0
538 538
 	 */
539
-	public static function uploadLimit(): int|float {
539
+	public static function uploadLimit(): int | float {
540 540
 		return \OC_Helper::uploadLimit();
541 541
 	}
542 542
 
Please login to merge, or discard this patch.
lib/public/Template.php 1 patch
Indentation   +87 added lines, -87 removed lines patch added patch discarded remove patch
@@ -22,98 +22,98 @@
 block discarded – undo
22 22
  * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead
23 23
  */
24 24
 class Template extends \OC_Template implements ITemplate {
25
-	/**
26
-	 * Make OC_Helper::imagePath available as a simple function
27
-	 *
28
-	 * @see \OCP\IURLGenerator::imagePath
29
-	 *
30
-	 * @param string $app
31
-	 * @param string $image
32
-	 * @return string to the image
33
-	 * @since 8.0.0
34
-	 * @deprecated 32.0.0 Use the function directly instead
35
-	 */
36
-	public static function image_path($app, $image) {
37
-		return \image_path($app, $image);
38
-	}
25
+    /**
26
+     * Make OC_Helper::imagePath available as a simple function
27
+     *
28
+     * @see \OCP\IURLGenerator::imagePath
29
+     *
30
+     * @param string $app
31
+     * @param string $image
32
+     * @return string to the image
33
+     * @since 8.0.0
34
+     * @deprecated 32.0.0 Use the function directly instead
35
+     */
36
+    public static function image_path($app, $image) {
37
+        return \image_path($app, $image);
38
+    }
39 39
 
40 40
 
41
-	/**
42
-	 * Make OC_Helper::mimetypeIcon available as a simple function
43
-	 *
44
-	 * @param string $mimetype
45
-	 * @return string to the image of this file type.
46
-	 * @since 8.0.0
47
-	 * @deprecated 32.0.0 Use the function directly instead
48
-	 */
49
-	public static function mimetype_icon($mimetype) {
50
-		return \mimetype_icon($mimetype);
51
-	}
41
+    /**
42
+     * Make OC_Helper::mimetypeIcon available as a simple function
43
+     *
44
+     * @param string $mimetype
45
+     * @return string to the image of this file type.
46
+     * @since 8.0.0
47
+     * @deprecated 32.0.0 Use the function directly instead
48
+     */
49
+    public static function mimetype_icon($mimetype) {
50
+        return \mimetype_icon($mimetype);
51
+    }
52 52
 
53
-	/**
54
-	 * Make preview_icon available as a simple function
55
-	 *
56
-	 * @param string $path path to file
57
-	 * @return string to the preview of the image
58
-	 * @since 8.0.0
59
-	 * @deprecated 32.0.0 Use the function directly instead
60
-	 */
61
-	public static function preview_icon($path) {
62
-		return \preview_icon($path);
63
-	}
53
+    /**
54
+     * Make preview_icon available as a simple function
55
+     *
56
+     * @param string $path path to file
57
+     * @return string to the preview of the image
58
+     * @since 8.0.0
59
+     * @deprecated 32.0.0 Use the function directly instead
60
+     */
61
+    public static function preview_icon($path) {
62
+        return \preview_icon($path);
63
+    }
64 64
 
65
-	/**
66
-	 * Make publicpreview_icon available as a simple function
67
-	 * Returns the path to the preview of the image.
68
-	 *
69
-	 * @param string $path of file
70
-	 * @param string $token
71
-	 * @return string link to the preview
72
-	 * @since 8.0.0
73
-	 * @deprecated 32.0.0 Use the function directly instead
74
-	 */
75
-	public static function publicPreview_icon($path, $token) {
76
-		return \publicPreview_icon($path, $token);
77
-	}
65
+    /**
66
+     * Make publicpreview_icon available as a simple function
67
+     * Returns the path to the preview of the image.
68
+     *
69
+     * @param string $path of file
70
+     * @param string $token
71
+     * @return string link to the preview
72
+     * @since 8.0.0
73
+     * @deprecated 32.0.0 Use the function directly instead
74
+     */
75
+    public static function publicPreview_icon($path, $token) {
76
+        return \publicPreview_icon($path, $token);
77
+    }
78 78
 
79
-	/**
80
-	 * Make \OCP\Util::humanFileSize available as a simple function
81
-	 * Example: 2048 to 2 kB.
82
-	 *
83
-	 * @param int $bytes in bytes
84
-	 * @return string size as string
85
-	 * @since 8.0.0
86
-	 * @deprecated 32.0.0 Use \OCP\Util::humanFileSize instead
87
-	 */
88
-	public static function human_file_size($bytes) {
89
-		return Util::humanFileSize($bytes);
90
-	}
79
+    /**
80
+     * Make \OCP\Util::humanFileSize available as a simple function
81
+     * Example: 2048 to 2 kB.
82
+     *
83
+     * @param int $bytes in bytes
84
+     * @return string size as string
85
+     * @since 8.0.0
86
+     * @deprecated 32.0.0 Use \OCP\Util::humanFileSize instead
87
+     */
88
+    public static function human_file_size($bytes) {
89
+        return Util::humanFileSize($bytes);
90
+    }
91 91
 
92
-	/**
93
-	 * Return the relative date in relation to today. Returns something like "last hour" or "two month ago"
94
-	 *
95
-	 * @param int $timestamp unix timestamp
96
-	 * @param boolean $dateOnly
97
-	 * @return string human readable interpretation of the timestamp
98
-	 * @since 8.0.0
99
-	 * @suppress PhanTypeMismatchArgument
100
-	 * @deprecated 32.0.0 Use the function directly instead
101
-	 */
102
-	public static function relative_modified_date($timestamp, $dateOnly = false) {
103
-		return \relative_modified_date($timestamp, null, $dateOnly);
104
-	}
92
+    /**
93
+     * Return the relative date in relation to today. Returns something like "last hour" or "two month ago"
94
+     *
95
+     * @param int $timestamp unix timestamp
96
+     * @param boolean $dateOnly
97
+     * @return string human readable interpretation of the timestamp
98
+     * @since 8.0.0
99
+     * @suppress PhanTypeMismatchArgument
100
+     * @deprecated 32.0.0 Use the function directly instead
101
+     */
102
+    public static function relative_modified_date($timestamp, $dateOnly = false) {
103
+        return \relative_modified_date($timestamp, null, $dateOnly);
104
+    }
105 105
 
106
-	/**
107
-	 * Generate html code for an options block.
108
-	 *
109
-	 * @param array $options the options
110
-	 * @param mixed $selected which one is selected?
111
-	 * @param array $params the parameters
112
-	 * @return string html options
113
-	 * @since 8.0.0
114
-	 * @deprecated 32.0.0 Use the function directly instead
115
-	 */
116
-	public static function html_select_options($options, $selected, $params = []) {
117
-		return \html_select_options($options, $selected, $params);
118
-	}
106
+    /**
107
+     * Generate html code for an options block.
108
+     *
109
+     * @param array $options the options
110
+     * @param mixed $selected which one is selected?
111
+     * @param array $params the parameters
112
+     * @return string html options
113
+     * @since 8.0.0
114
+     * @deprecated 32.0.0 Use the function directly instead
115
+     */
116
+    public static function html_select_options($options, $selected, $params = []) {
117
+        return \html_select_options($options, $selected, $params);
118
+    }
119 119
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_Helper.php 2 patches
Indentation   +563 added lines, -563 removed lines patch added patch discarded remove patch
@@ -31,570 +31,570 @@
 block discarded – undo
31 31
  * }
32 32
  */
33 33
 class OC_Helper {
34
-	private static $templateManager;
35
-	private static ?ICacheFactory $cacheFactory = null;
36
-	private static ?bool $quotaIncludeExternalStorage = null;
37
-
38
-	/**
39
-	 * Make a human file size
40
-	 * @param int|float $bytes file size in bytes
41
-	 * @return string a human readable file size
42
-	 * @deprecated 4.0.0 replaced with \OCP\Util::humanFileSize
43
-	 *
44
-	 * Makes 2048 to 2 kB.
45
-	 */
46
-	public static function humanFileSize(int|float $bytes): string {
47
-		return \OCP\Util::humanFileSize($bytes);
48
-	}
49
-
50
-	/**
51
-	 * Make a computer file size
52
-	 * @param string $str file size in human readable format
53
-	 * @return false|int|float a file size in bytes
54
-	 * @deprecated 4.0.0 Use \OCP\Util::computerFileSize
55
-	 *
56
-	 * Makes 2kB to 2048.
57
-	 *
58
-	 * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
59
-	 */
60
-	public static function computerFileSize(string $str): false|int|float {
61
-		return \OCP\Util::computerFileSize($str);
62
-	}
63
-
64
-	/**
65
-	 * Recursive copying of folders
66
-	 * @param string $src source folder
67
-	 * @param string $dest target folder
68
-	 * @return void
69
-	 */
70
-	public static function copyr($src, $dest) {
71
-		if (!file_exists($src)) {
72
-			return;
73
-		}
74
-
75
-		if (is_dir($src)) {
76
-			if (!is_dir($dest)) {
77
-				mkdir($dest);
78
-			}
79
-			$files = scandir($src);
80
-			foreach ($files as $file) {
81
-				if ($file != '.' && $file != '..') {
82
-					self::copyr("$src/$file", "$dest/$file");
83
-				}
84
-			}
85
-		} else {
86
-			$validator = \OCP\Server::get(FilenameValidator::class);
87
-			if (!$validator->isForbidden($src)) {
88
-				copy($src, $dest);
89
-			}
90
-		}
91
-	}
92
-
93
-	/**
94
-	 * Recursive deletion of folders
95
-	 * @param string $dir path to the folder
96
-	 * @param bool $deleteSelf if set to false only the content of the folder will be deleted
97
-	 * @return bool
98
-	 */
99
-	public static function rmdirr($dir, $deleteSelf = true) {
100
-		if (is_dir($dir)) {
101
-			$files = new RecursiveIteratorIterator(
102
-				new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
103
-				RecursiveIteratorIterator::CHILD_FIRST
104
-			);
105
-
106
-			foreach ($files as $fileInfo) {
107
-				/** @var SplFileInfo $fileInfo */
108
-				if ($fileInfo->isLink()) {
109
-					unlink($fileInfo->getPathname());
110
-				} elseif ($fileInfo->isDir()) {
111
-					rmdir($fileInfo->getRealPath());
112
-				} else {
113
-					unlink($fileInfo->getRealPath());
114
-				}
115
-			}
116
-			if ($deleteSelf) {
117
-				rmdir($dir);
118
-			}
119
-		} elseif (file_exists($dir)) {
120
-			if ($deleteSelf) {
121
-				unlink($dir);
122
-			}
123
-		}
124
-		if (!$deleteSelf) {
125
-			return true;
126
-		}
127
-
128
-		return !file_exists($dir);
129
-	}
130
-
131
-	/**
132
-	 * @deprecated 18.0.0
133
-	 * @return \OC\Files\Type\TemplateManager
134
-	 */
135
-	public static function getFileTemplateManager() {
136
-		if (!self::$templateManager) {
137
-			self::$templateManager = new \OC\Files\Type\TemplateManager();
138
-		}
139
-		return self::$templateManager;
140
-	}
141
-
142
-	/**
143
-	 * detect if a given program is found in the search PATH
144
-	 *
145
-	 * @param string $name
146
-	 * @param bool $path
147
-	 * @internal param string $program name
148
-	 * @internal param string $optional search path, defaults to $PATH
149
-	 * @return bool true if executable program found in path
150
-	 * @deprecated 32.0.0 use the \OCP\IBinaryFinder
151
-	 */
152
-	public static function canExecute($name, $path = false) {
153
-		// path defaults to PATH from environment if not set
154
-		if ($path === false) {
155
-			$path = getenv('PATH');
156
-		}
157
-		// we look for an executable file of that name
158
-		$exts = [''];
159
-		$check_fn = 'is_executable';
160
-		// Default check will be done with $path directories :
161
-		$dirs = explode(PATH_SEPARATOR, $path);
162
-		// WARNING : We have to check if open_basedir is enabled :
163
-		$obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
164
-		if ($obd != 'none') {
165
-			$obd_values = explode(PATH_SEPARATOR, $obd);
166
-			if (count($obd_values) > 0 and $obd_values[0]) {
167
-				// open_basedir is in effect !
168
-				// We need to check if the program is in one of these dirs :
169
-				$dirs = $obd_values;
170
-			}
171
-		}
172
-		foreach ($dirs as $dir) {
173
-			foreach ($exts as $ext) {
174
-				if ($check_fn("$dir/$name" . $ext)) {
175
-					return true;
176
-				}
177
-			}
178
-		}
179
-		return false;
180
-	}
181
-
182
-	/**
183
-	 * copy the contents of one stream to another
184
-	 *
185
-	 * @param resource $source
186
-	 * @param resource $target
187
-	 * @return array the number of bytes copied and result
188
-	 */
189
-	public static function streamCopy($source, $target) {
190
-		if (!$source or !$target) {
191
-			return [0, false];
192
-		}
193
-		$bufSize = 8192;
194
-		$result = true;
195
-		$count = 0;
196
-		while (!feof($source)) {
197
-			$buf = fread($source, $bufSize);
198
-			$bytesWritten = fwrite($target, $buf);
199
-			if ($bytesWritten !== false) {
200
-				$count += $bytesWritten;
201
-			}
202
-			// note: strlen is expensive so only use it when necessary,
203
-			// on the last block
204
-			if ($bytesWritten === false
205
-				|| ($bytesWritten < $bufSize && $bytesWritten < strlen($buf))
206
-			) {
207
-				// write error, could be disk full ?
208
-				$result = false;
209
-				break;
210
-			}
211
-		}
212
-		return [$count, $result];
213
-	}
214
-
215
-	/**
216
-	 * Adds a suffix to the name in case the file exists
217
-	 *
218
-	 * @param string $path
219
-	 * @param string $filename
220
-	 * @return string
221
-	 */
222
-	public static function buildNotExistingFileName($path, $filename) {
223
-		$view = \OC\Files\Filesystem::getView();
224
-		return self::buildNotExistingFileNameForView($path, $filename, $view);
225
-	}
226
-
227
-	/**
228
-	 * Adds a suffix to the name in case the file exists
229
-	 *
230
-	 * @param string $path
231
-	 * @param string $filename
232
-	 * @return string
233
-	 */
234
-	public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
235
-		if ($path === '/') {
236
-			$path = '';
237
-		}
238
-		if ($pos = strrpos($filename, '.')) {
239
-			$name = substr($filename, 0, $pos);
240
-			$ext = substr($filename, $pos);
241
-		} else {
242
-			$name = $filename;
243
-			$ext = '';
244
-		}
245
-
246
-		$newpath = $path . '/' . $filename;
247
-		if ($view->file_exists($newpath)) {
248
-			if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
249
-				//Replace the last "(number)" with "(number+1)"
250
-				$last_match = count($matches[0]) - 1;
251
-				$counter = $matches[1][$last_match][0] + 1;
252
-				$offset = $matches[0][$last_match][1];
253
-				$match_length = strlen($matches[0][$last_match][0]);
254
-			} else {
255
-				$counter = 2;
256
-				$match_length = 0;
257
-				$offset = false;
258
-			}
259
-			do {
260
-				if ($offset) {
261
-					//Replace the last "(number)" with "(number+1)"
262
-					$newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
263
-				} else {
264
-					$newname = $name . ' (' . $counter . ')';
265
-				}
266
-				$newpath = $path . '/' . $newname . $ext;
267
-				$counter++;
268
-			} while ($view->file_exists($newpath));
269
-		}
270
-
271
-		return $newpath;
272
-	}
273
-
274
-	/**
275
-	 * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
276
-	 *
277
-	 * @param array $input The array to work on
278
-	 * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
279
-	 * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
280
-	 * @return array
281
-	 *
282
-	 * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
283
-	 * based on https://www.php.net/manual/en/function.array-change-key-case.php#107715
284
-	 *
285
-	 */
286
-	public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
287
-		$case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
288
-		$ret = [];
289
-		foreach ($input as $k => $v) {
290
-			$ret[mb_convert_case($k, $case, $encoding)] = $v;
291
-		}
292
-		return $ret;
293
-	}
294
-
295
-	/**
296
-	 * performs a search in a nested array
297
-	 * @param array $haystack the array to be searched
298
-	 * @param string $needle the search string
299
-	 * @param mixed $index optional, only search this key name
300
-	 * @return mixed the key of the matching field, otherwise false
301
-	 *
302
-	 * performs a search in a nested array
303
-	 *
304
-	 * taken from https://www.php.net/manual/en/function.array-search.php#97645
305
-	 */
306
-	public static function recursiveArraySearch($haystack, $needle, $index = null) {
307
-		$aIt = new RecursiveArrayIterator($haystack);
308
-		$it = new RecursiveIteratorIterator($aIt);
309
-
310
-		while ($it->valid()) {
311
-			if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) {
312
-				return $aIt->key();
313
-			}
314
-
315
-			$it->next();
316
-		}
317
-
318
-		return false;
319
-	}
320
-
321
-	/**
322
-	 * calculates the maximum upload size respecting system settings, free space and user quota
323
-	 *
324
-	 * @param string $dir the current folder where the user currently operates
325
-	 * @param int|float $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
326
-	 * @return int|float number of bytes representing
327
-	 */
328
-	public static function maxUploadFilesize($dir, $freeSpace = null) {
329
-		if (is_null($freeSpace) || $freeSpace < 0) {
330
-			$freeSpace = self::freeSpace($dir);
331
-		}
332
-		return min($freeSpace, self::uploadLimit());
333
-	}
334
-
335
-	/**
336
-	 * Calculate free space left within user quota
337
-	 *
338
-	 * @param string $dir the current folder where the user currently operates
339
-	 * @return int|float number of bytes representing
340
-	 */
341
-	public static function freeSpace($dir) {
342
-		$freeSpace = \OC\Files\Filesystem::free_space($dir);
343
-		if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
344
-			$freeSpace = max($freeSpace, 0);
345
-			return $freeSpace;
346
-		} else {
347
-			return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
348
-		}
349
-	}
350
-
351
-	/**
352
-	 * Calculate PHP upload limit
353
-	 *
354
-	 * @return int|float PHP upload file size limit
355
-	 */
356
-	public static function uploadLimit() {
357
-		$ini = \OC::$server->get(IniGetWrapper::class);
358
-		$upload_max_filesize = Util::computerFileSize($ini->get('upload_max_filesize')) ?: 0;
359
-		$post_max_size = Util::computerFileSize($ini->get('post_max_size')) ?: 0;
360
-		if ($upload_max_filesize === 0 && $post_max_size === 0) {
361
-			return INF;
362
-		} elseif ($upload_max_filesize === 0 || $post_max_size === 0) {
363
-			return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
364
-		} else {
365
-			return min($upload_max_filesize, $post_max_size);
366
-		}
367
-	}
368
-
369
-	/**
370
-	 * Checks if a function is available
371
-	 *
372
-	 * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead
373
-	 */
374
-	public static function is_function_enabled(string $function_name): bool {
375
-		return \OCP\Util::isFunctionEnabled($function_name);
376
-	}
377
-
378
-	/**
379
-	 * Try to find a program
380
-	 * @deprecated 25.0.0 Use \OC\BinaryFinder directly
381
-	 */
382
-	public static function findBinaryPath(string $program): ?string {
383
-		$result = \OCP\Server::get(IBinaryFinder::class)->findBinaryPath($program);
384
-		return $result !== false ? $result : null;
385
-	}
386
-
387
-	/**
388
-	 * Calculate the disc space for the given path
389
-	 *
390
-	 * BEWARE: this requires that Util::setupFS() was called
391
-	 * already !
392
-	 *
393
-	 * @param string $path
394
-	 * @param \OCP\Files\FileInfo $rootInfo (optional)
395
-	 * @param bool $includeMountPoints whether to include mount points in the size calculation
396
-	 * @param bool $useCache whether to use the cached quota values
397
-	 * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
398
-	 * @return StorageInfo
399
-	 * @throws \OCP\Files\NotFoundException
400
-	 */
401
-	public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
402
-		if (!self::$cacheFactory) {
403
-			self::$cacheFactory = \OC::$server->get(ICacheFactory::class);
404
-		}
405
-		$memcache = self::$cacheFactory->createLocal('storage_info');
406
-
407
-		// return storage info without adding mount points
408
-		if (self::$quotaIncludeExternalStorage === null) {
409
-			self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
410
-		}
411
-
412
-		$view = Filesystem::getView();
413
-		if (!$view) {
414
-			throw new \OCP\Files\NotFoundException();
415
-		}
416
-		$fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
417
-
418
-		$cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude');
419
-		if ($useCache) {
420
-			$cached = $memcache->get($cacheKey);
421
-			if ($cached) {
422
-				return $cached;
423
-			}
424
-		}
425
-
426
-		if (!$rootInfo) {
427
-			$rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false);
428
-		}
429
-		if (!$rootInfo instanceof \OCP\Files\FileInfo) {
430
-			throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing');
431
-		}
432
-		$used = $rootInfo->getSize($includeMountPoints);
433
-		if ($used < 0) {
434
-			$used = 0.0;
435
-		}
436
-		/** @var int|float $quota */
437
-		$quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
438
-		$mount = $rootInfo->getMountPoint();
439
-		$storage = $mount->getStorage();
440
-		$sourceStorage = $storage;
441
-		if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
442
-			self::$quotaIncludeExternalStorage = false;
443
-		}
444
-		if (self::$quotaIncludeExternalStorage) {
445
-			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
446
-				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
447
-			) {
448
-				/** @var \OC\Files\Storage\Home $storage */
449
-				$user = $storage->getUser();
450
-			} else {
451
-				$user = \OC::$server->getUserSession()->getUser();
452
-			}
453
-			$quota = OC_Util::getUserQuota($user);
454
-			if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
455
-				// always get free space / total space from root + mount points
456
-				return self::getGlobalStorageInfo($quota, $user, $mount);
457
-			}
458
-		}
459
-
460
-		// TODO: need a better way to get total space from storage
461
-		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
462
-			/** @var \OC\Files\Storage\Wrapper\Quota $storage */
463
-			$quota = $sourceStorage->getQuota();
464
-		}
465
-		try {
466
-			$free = $sourceStorage->free_space($rootInfo->getInternalPath());
467
-			if (is_bool($free)) {
468
-				$free = 0.0;
469
-			}
470
-		} catch (\Exception $e) {
471
-			if ($path === '') {
472
-				throw $e;
473
-			}
474
-			/** @var LoggerInterface $logger */
475
-			$logger = \OC::$server->get(LoggerInterface::class);
476
-			$logger->warning('Error while getting quota info, using root quota', ['exception' => $e]);
477
-			$rootInfo = self::getStorageInfo('');
478
-			$memcache->set($cacheKey, $rootInfo, 5 * 60);
479
-			return $rootInfo;
480
-		}
481
-		if ($free >= 0) {
482
-			$total = $free + $used;
483
-		} else {
484
-			$total = $free; //either unknown or unlimited
485
-		}
486
-		if ($total > 0) {
487
-			if ($quota > 0 && $total > $quota) {
488
-				$total = $quota;
489
-			}
490
-			// prevent division by zero or error codes (negative values)
491
-			$relative = round(($used / $total) * 10000) / 100;
492
-		} else {
493
-			$relative = 0;
494
-		}
495
-
496
-		/*
34
+    private static $templateManager;
35
+    private static ?ICacheFactory $cacheFactory = null;
36
+    private static ?bool $quotaIncludeExternalStorage = null;
37
+
38
+    /**
39
+     * Make a human file size
40
+     * @param int|float $bytes file size in bytes
41
+     * @return string a human readable file size
42
+     * @deprecated 4.0.0 replaced with \OCP\Util::humanFileSize
43
+     *
44
+     * Makes 2048 to 2 kB.
45
+     */
46
+    public static function humanFileSize(int|float $bytes): string {
47
+        return \OCP\Util::humanFileSize($bytes);
48
+    }
49
+
50
+    /**
51
+     * Make a computer file size
52
+     * @param string $str file size in human readable format
53
+     * @return false|int|float a file size in bytes
54
+     * @deprecated 4.0.0 Use \OCP\Util::computerFileSize
55
+     *
56
+     * Makes 2kB to 2048.
57
+     *
58
+     * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
59
+     */
60
+    public static function computerFileSize(string $str): false|int|float {
61
+        return \OCP\Util::computerFileSize($str);
62
+    }
63
+
64
+    /**
65
+     * Recursive copying of folders
66
+     * @param string $src source folder
67
+     * @param string $dest target folder
68
+     * @return void
69
+     */
70
+    public static function copyr($src, $dest) {
71
+        if (!file_exists($src)) {
72
+            return;
73
+        }
74
+
75
+        if (is_dir($src)) {
76
+            if (!is_dir($dest)) {
77
+                mkdir($dest);
78
+            }
79
+            $files = scandir($src);
80
+            foreach ($files as $file) {
81
+                if ($file != '.' && $file != '..') {
82
+                    self::copyr("$src/$file", "$dest/$file");
83
+                }
84
+            }
85
+        } else {
86
+            $validator = \OCP\Server::get(FilenameValidator::class);
87
+            if (!$validator->isForbidden($src)) {
88
+                copy($src, $dest);
89
+            }
90
+        }
91
+    }
92
+
93
+    /**
94
+     * Recursive deletion of folders
95
+     * @param string $dir path to the folder
96
+     * @param bool $deleteSelf if set to false only the content of the folder will be deleted
97
+     * @return bool
98
+     */
99
+    public static function rmdirr($dir, $deleteSelf = true) {
100
+        if (is_dir($dir)) {
101
+            $files = new RecursiveIteratorIterator(
102
+                new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
103
+                RecursiveIteratorIterator::CHILD_FIRST
104
+            );
105
+
106
+            foreach ($files as $fileInfo) {
107
+                /** @var SplFileInfo $fileInfo */
108
+                if ($fileInfo->isLink()) {
109
+                    unlink($fileInfo->getPathname());
110
+                } elseif ($fileInfo->isDir()) {
111
+                    rmdir($fileInfo->getRealPath());
112
+                } else {
113
+                    unlink($fileInfo->getRealPath());
114
+                }
115
+            }
116
+            if ($deleteSelf) {
117
+                rmdir($dir);
118
+            }
119
+        } elseif (file_exists($dir)) {
120
+            if ($deleteSelf) {
121
+                unlink($dir);
122
+            }
123
+        }
124
+        if (!$deleteSelf) {
125
+            return true;
126
+        }
127
+
128
+        return !file_exists($dir);
129
+    }
130
+
131
+    /**
132
+     * @deprecated 18.0.0
133
+     * @return \OC\Files\Type\TemplateManager
134
+     */
135
+    public static function getFileTemplateManager() {
136
+        if (!self::$templateManager) {
137
+            self::$templateManager = new \OC\Files\Type\TemplateManager();
138
+        }
139
+        return self::$templateManager;
140
+    }
141
+
142
+    /**
143
+     * detect if a given program is found in the search PATH
144
+     *
145
+     * @param string $name
146
+     * @param bool $path
147
+     * @internal param string $program name
148
+     * @internal param string $optional search path, defaults to $PATH
149
+     * @return bool true if executable program found in path
150
+     * @deprecated 32.0.0 use the \OCP\IBinaryFinder
151
+     */
152
+    public static function canExecute($name, $path = false) {
153
+        // path defaults to PATH from environment if not set
154
+        if ($path === false) {
155
+            $path = getenv('PATH');
156
+        }
157
+        // we look for an executable file of that name
158
+        $exts = [''];
159
+        $check_fn = 'is_executable';
160
+        // Default check will be done with $path directories :
161
+        $dirs = explode(PATH_SEPARATOR, $path);
162
+        // WARNING : We have to check if open_basedir is enabled :
163
+        $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
164
+        if ($obd != 'none') {
165
+            $obd_values = explode(PATH_SEPARATOR, $obd);
166
+            if (count($obd_values) > 0 and $obd_values[0]) {
167
+                // open_basedir is in effect !
168
+                // We need to check if the program is in one of these dirs :
169
+                $dirs = $obd_values;
170
+            }
171
+        }
172
+        foreach ($dirs as $dir) {
173
+            foreach ($exts as $ext) {
174
+                if ($check_fn("$dir/$name" . $ext)) {
175
+                    return true;
176
+                }
177
+            }
178
+        }
179
+        return false;
180
+    }
181
+
182
+    /**
183
+     * copy the contents of one stream to another
184
+     *
185
+     * @param resource $source
186
+     * @param resource $target
187
+     * @return array the number of bytes copied and result
188
+     */
189
+    public static function streamCopy($source, $target) {
190
+        if (!$source or !$target) {
191
+            return [0, false];
192
+        }
193
+        $bufSize = 8192;
194
+        $result = true;
195
+        $count = 0;
196
+        while (!feof($source)) {
197
+            $buf = fread($source, $bufSize);
198
+            $bytesWritten = fwrite($target, $buf);
199
+            if ($bytesWritten !== false) {
200
+                $count += $bytesWritten;
201
+            }
202
+            // note: strlen is expensive so only use it when necessary,
203
+            // on the last block
204
+            if ($bytesWritten === false
205
+                || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf))
206
+            ) {
207
+                // write error, could be disk full ?
208
+                $result = false;
209
+                break;
210
+            }
211
+        }
212
+        return [$count, $result];
213
+    }
214
+
215
+    /**
216
+     * Adds a suffix to the name in case the file exists
217
+     *
218
+     * @param string $path
219
+     * @param string $filename
220
+     * @return string
221
+     */
222
+    public static function buildNotExistingFileName($path, $filename) {
223
+        $view = \OC\Files\Filesystem::getView();
224
+        return self::buildNotExistingFileNameForView($path, $filename, $view);
225
+    }
226
+
227
+    /**
228
+     * Adds a suffix to the name in case the file exists
229
+     *
230
+     * @param string $path
231
+     * @param string $filename
232
+     * @return string
233
+     */
234
+    public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
235
+        if ($path === '/') {
236
+            $path = '';
237
+        }
238
+        if ($pos = strrpos($filename, '.')) {
239
+            $name = substr($filename, 0, $pos);
240
+            $ext = substr($filename, $pos);
241
+        } else {
242
+            $name = $filename;
243
+            $ext = '';
244
+        }
245
+
246
+        $newpath = $path . '/' . $filename;
247
+        if ($view->file_exists($newpath)) {
248
+            if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
249
+                //Replace the last "(number)" with "(number+1)"
250
+                $last_match = count($matches[0]) - 1;
251
+                $counter = $matches[1][$last_match][0] + 1;
252
+                $offset = $matches[0][$last_match][1];
253
+                $match_length = strlen($matches[0][$last_match][0]);
254
+            } else {
255
+                $counter = 2;
256
+                $match_length = 0;
257
+                $offset = false;
258
+            }
259
+            do {
260
+                if ($offset) {
261
+                    //Replace the last "(number)" with "(number+1)"
262
+                    $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
263
+                } else {
264
+                    $newname = $name . ' (' . $counter . ')';
265
+                }
266
+                $newpath = $path . '/' . $newname . $ext;
267
+                $counter++;
268
+            } while ($view->file_exists($newpath));
269
+        }
270
+
271
+        return $newpath;
272
+    }
273
+
274
+    /**
275
+     * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
276
+     *
277
+     * @param array $input The array to work on
278
+     * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
279
+     * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
280
+     * @return array
281
+     *
282
+     * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
283
+     * based on https://www.php.net/manual/en/function.array-change-key-case.php#107715
284
+     *
285
+     */
286
+    public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
287
+        $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
288
+        $ret = [];
289
+        foreach ($input as $k => $v) {
290
+            $ret[mb_convert_case($k, $case, $encoding)] = $v;
291
+        }
292
+        return $ret;
293
+    }
294
+
295
+    /**
296
+     * performs a search in a nested array
297
+     * @param array $haystack the array to be searched
298
+     * @param string $needle the search string
299
+     * @param mixed $index optional, only search this key name
300
+     * @return mixed the key of the matching field, otherwise false
301
+     *
302
+     * performs a search in a nested array
303
+     *
304
+     * taken from https://www.php.net/manual/en/function.array-search.php#97645
305
+     */
306
+    public static function recursiveArraySearch($haystack, $needle, $index = null) {
307
+        $aIt = new RecursiveArrayIterator($haystack);
308
+        $it = new RecursiveIteratorIterator($aIt);
309
+
310
+        while ($it->valid()) {
311
+            if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) {
312
+                return $aIt->key();
313
+            }
314
+
315
+            $it->next();
316
+        }
317
+
318
+        return false;
319
+    }
320
+
321
+    /**
322
+     * calculates the maximum upload size respecting system settings, free space and user quota
323
+     *
324
+     * @param string $dir the current folder where the user currently operates
325
+     * @param int|float $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
326
+     * @return int|float number of bytes representing
327
+     */
328
+    public static function maxUploadFilesize($dir, $freeSpace = null) {
329
+        if (is_null($freeSpace) || $freeSpace < 0) {
330
+            $freeSpace = self::freeSpace($dir);
331
+        }
332
+        return min($freeSpace, self::uploadLimit());
333
+    }
334
+
335
+    /**
336
+     * Calculate free space left within user quota
337
+     *
338
+     * @param string $dir the current folder where the user currently operates
339
+     * @return int|float number of bytes representing
340
+     */
341
+    public static function freeSpace($dir) {
342
+        $freeSpace = \OC\Files\Filesystem::free_space($dir);
343
+        if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
344
+            $freeSpace = max($freeSpace, 0);
345
+            return $freeSpace;
346
+        } else {
347
+            return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
348
+        }
349
+    }
350
+
351
+    /**
352
+     * Calculate PHP upload limit
353
+     *
354
+     * @return int|float PHP upload file size limit
355
+     */
356
+    public static function uploadLimit() {
357
+        $ini = \OC::$server->get(IniGetWrapper::class);
358
+        $upload_max_filesize = Util::computerFileSize($ini->get('upload_max_filesize')) ?: 0;
359
+        $post_max_size = Util::computerFileSize($ini->get('post_max_size')) ?: 0;
360
+        if ($upload_max_filesize === 0 && $post_max_size === 0) {
361
+            return INF;
362
+        } elseif ($upload_max_filesize === 0 || $post_max_size === 0) {
363
+            return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
364
+        } else {
365
+            return min($upload_max_filesize, $post_max_size);
366
+        }
367
+    }
368
+
369
+    /**
370
+     * Checks if a function is available
371
+     *
372
+     * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead
373
+     */
374
+    public static function is_function_enabled(string $function_name): bool {
375
+        return \OCP\Util::isFunctionEnabled($function_name);
376
+    }
377
+
378
+    /**
379
+     * Try to find a program
380
+     * @deprecated 25.0.0 Use \OC\BinaryFinder directly
381
+     */
382
+    public static function findBinaryPath(string $program): ?string {
383
+        $result = \OCP\Server::get(IBinaryFinder::class)->findBinaryPath($program);
384
+        return $result !== false ? $result : null;
385
+    }
386
+
387
+    /**
388
+     * Calculate the disc space for the given path
389
+     *
390
+     * BEWARE: this requires that Util::setupFS() was called
391
+     * already !
392
+     *
393
+     * @param string $path
394
+     * @param \OCP\Files\FileInfo $rootInfo (optional)
395
+     * @param bool $includeMountPoints whether to include mount points in the size calculation
396
+     * @param bool $useCache whether to use the cached quota values
397
+     * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
398
+     * @return StorageInfo
399
+     * @throws \OCP\Files\NotFoundException
400
+     */
401
+    public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
402
+        if (!self::$cacheFactory) {
403
+            self::$cacheFactory = \OC::$server->get(ICacheFactory::class);
404
+        }
405
+        $memcache = self::$cacheFactory->createLocal('storage_info');
406
+
407
+        // return storage info without adding mount points
408
+        if (self::$quotaIncludeExternalStorage === null) {
409
+            self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
410
+        }
411
+
412
+        $view = Filesystem::getView();
413
+        if (!$view) {
414
+            throw new \OCP\Files\NotFoundException();
415
+        }
416
+        $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
417
+
418
+        $cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude');
419
+        if ($useCache) {
420
+            $cached = $memcache->get($cacheKey);
421
+            if ($cached) {
422
+                return $cached;
423
+            }
424
+        }
425
+
426
+        if (!$rootInfo) {
427
+            $rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false);
428
+        }
429
+        if (!$rootInfo instanceof \OCP\Files\FileInfo) {
430
+            throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing');
431
+        }
432
+        $used = $rootInfo->getSize($includeMountPoints);
433
+        if ($used < 0) {
434
+            $used = 0.0;
435
+        }
436
+        /** @var int|float $quota */
437
+        $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
438
+        $mount = $rootInfo->getMountPoint();
439
+        $storage = $mount->getStorage();
440
+        $sourceStorage = $storage;
441
+        if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
442
+            self::$quotaIncludeExternalStorage = false;
443
+        }
444
+        if (self::$quotaIncludeExternalStorage) {
445
+            if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
446
+                || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
447
+            ) {
448
+                /** @var \OC\Files\Storage\Home $storage */
449
+                $user = $storage->getUser();
450
+            } else {
451
+                $user = \OC::$server->getUserSession()->getUser();
452
+            }
453
+            $quota = OC_Util::getUserQuota($user);
454
+            if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
455
+                // always get free space / total space from root + mount points
456
+                return self::getGlobalStorageInfo($quota, $user, $mount);
457
+            }
458
+        }
459
+
460
+        // TODO: need a better way to get total space from storage
461
+        if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
462
+            /** @var \OC\Files\Storage\Wrapper\Quota $storage */
463
+            $quota = $sourceStorage->getQuota();
464
+        }
465
+        try {
466
+            $free = $sourceStorage->free_space($rootInfo->getInternalPath());
467
+            if (is_bool($free)) {
468
+                $free = 0.0;
469
+            }
470
+        } catch (\Exception $e) {
471
+            if ($path === '') {
472
+                throw $e;
473
+            }
474
+            /** @var LoggerInterface $logger */
475
+            $logger = \OC::$server->get(LoggerInterface::class);
476
+            $logger->warning('Error while getting quota info, using root quota', ['exception' => $e]);
477
+            $rootInfo = self::getStorageInfo('');
478
+            $memcache->set($cacheKey, $rootInfo, 5 * 60);
479
+            return $rootInfo;
480
+        }
481
+        if ($free >= 0) {
482
+            $total = $free + $used;
483
+        } else {
484
+            $total = $free; //either unknown or unlimited
485
+        }
486
+        if ($total > 0) {
487
+            if ($quota > 0 && $total > $quota) {
488
+                $total = $quota;
489
+            }
490
+            // prevent division by zero or error codes (negative values)
491
+            $relative = round(($used / $total) * 10000) / 100;
492
+        } else {
493
+            $relative = 0;
494
+        }
495
+
496
+        /*
497 497
 		 * \OCA\Files_Sharing\External\Storage returns the cloud ID as the owner for the storage.
498 498
 		 * It is unnecessary to query the user manager for the display name, as it won't have this information.
499 499
 		 */
500
-		$isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class);
501
-
502
-		$ownerId = $storage->getOwner($path);
503
-		$ownerDisplayName = '';
504
-
505
-		if ($isRemoteShare === false && $ownerId !== false) {
506
-			$ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? '';
507
-		}
508
-
509
-		if (substr_count($mount->getMountPoint(), '/') < 3) {
510
-			$mountPoint = '';
511
-		} else {
512
-			[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
513
-		}
514
-
515
-		$info = [
516
-			'free' => $free,
517
-			'used' => $used,
518
-			'quota' => $quota,
519
-			'total' => $total,
520
-			'relative' => $relative,
521
-			'owner' => $ownerId,
522
-			'ownerDisplayName' => $ownerDisplayName,
523
-			'mountType' => $mount->getMountType(),
524
-			'mountPoint' => trim($mountPoint, '/'),
525
-		];
526
-
527
-		if ($isRemoteShare === false && $ownerId !== false && $path === '/') {
528
-			// If path is root, store this as last known quota usage for this user
529
-			\OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative);
530
-		}
531
-
532
-		$memcache->set($cacheKey, $info, 5 * 60);
533
-
534
-		return $info;
535
-	}
536
-
537
-	/**
538
-	 * Get storage info including all mount points and quota
539
-	 *
540
-	 * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
541
-	 * @return StorageInfo
542
-	 */
543
-	private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
544
-		$rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
545
-		/** @var int|float $used */
546
-		$used = $rootInfo['size'];
547
-		if ($used < 0) {
548
-			$used = 0.0;
549
-		}
550
-
551
-		$total = $quota;
552
-		/** @var int|float $free */
553
-		$free = $quota - $used;
554
-
555
-		if ($total > 0) {
556
-			if ($quota > 0 && $total > $quota) {
557
-				$total = $quota;
558
-			}
559
-			// prevent division by zero or error codes (negative values)
560
-			$relative = round(($used / $total) * 10000) / 100;
561
-		} else {
562
-			$relative = 0.0;
563
-		}
564
-
565
-		if (substr_count($mount->getMountPoint(), '/') < 3) {
566
-			$mountPoint = '';
567
-		} else {
568
-			[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
569
-		}
570
-
571
-		return [
572
-			'free' => $free,
573
-			'used' => $used,
574
-			'total' => $total,
575
-			'relative' => $relative,
576
-			'quota' => $quota,
577
-			'owner' => $user->getUID(),
578
-			'ownerDisplayName' => $user->getDisplayName(),
579
-			'mountType' => $mount->getMountType(),
580
-			'mountPoint' => trim($mountPoint, '/'),
581
-		];
582
-	}
583
-
584
-	public static function clearStorageInfo(string $absolutePath): void {
585
-		/** @var ICacheFactory $cacheFactory */
586
-		$cacheFactory = \OC::$server->get(ICacheFactory::class);
587
-		$memcache = $cacheFactory->createLocal('storage_info');
588
-		$cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
589
-		$memcache->remove($cacheKeyPrefix . 'include');
590
-		$memcache->remove($cacheKeyPrefix . 'exclude');
591
-	}
592
-
593
-	/**
594
-	 * Returns whether the config file is set manually to read-only
595
-	 * @return bool
596
-	 */
597
-	public static function isReadOnlyConfigEnabled() {
598
-		return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false);
599
-	}
500
+        $isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class);
501
+
502
+        $ownerId = $storage->getOwner($path);
503
+        $ownerDisplayName = '';
504
+
505
+        if ($isRemoteShare === false && $ownerId !== false) {
506
+            $ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? '';
507
+        }
508
+
509
+        if (substr_count($mount->getMountPoint(), '/') < 3) {
510
+            $mountPoint = '';
511
+        } else {
512
+            [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
513
+        }
514
+
515
+        $info = [
516
+            'free' => $free,
517
+            'used' => $used,
518
+            'quota' => $quota,
519
+            'total' => $total,
520
+            'relative' => $relative,
521
+            'owner' => $ownerId,
522
+            'ownerDisplayName' => $ownerDisplayName,
523
+            'mountType' => $mount->getMountType(),
524
+            'mountPoint' => trim($mountPoint, '/'),
525
+        ];
526
+
527
+        if ($isRemoteShare === false && $ownerId !== false && $path === '/') {
528
+            // If path is root, store this as last known quota usage for this user
529
+            \OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative);
530
+        }
531
+
532
+        $memcache->set($cacheKey, $info, 5 * 60);
533
+
534
+        return $info;
535
+    }
536
+
537
+    /**
538
+     * Get storage info including all mount points and quota
539
+     *
540
+     * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
541
+     * @return StorageInfo
542
+     */
543
+    private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
544
+        $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
545
+        /** @var int|float $used */
546
+        $used = $rootInfo['size'];
547
+        if ($used < 0) {
548
+            $used = 0.0;
549
+        }
550
+
551
+        $total = $quota;
552
+        /** @var int|float $free */
553
+        $free = $quota - $used;
554
+
555
+        if ($total > 0) {
556
+            if ($quota > 0 && $total > $quota) {
557
+                $total = $quota;
558
+            }
559
+            // prevent division by zero or error codes (negative values)
560
+            $relative = round(($used / $total) * 10000) / 100;
561
+        } else {
562
+            $relative = 0.0;
563
+        }
564
+
565
+        if (substr_count($mount->getMountPoint(), '/') < 3) {
566
+            $mountPoint = '';
567
+        } else {
568
+            [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
569
+        }
570
+
571
+        return [
572
+            'free' => $free,
573
+            'used' => $used,
574
+            'total' => $total,
575
+            'relative' => $relative,
576
+            'quota' => $quota,
577
+            'owner' => $user->getUID(),
578
+            'ownerDisplayName' => $user->getDisplayName(),
579
+            'mountType' => $mount->getMountType(),
580
+            'mountPoint' => trim($mountPoint, '/'),
581
+        ];
582
+    }
583
+
584
+    public static function clearStorageInfo(string $absolutePath): void {
585
+        /** @var ICacheFactory $cacheFactory */
586
+        $cacheFactory = \OC::$server->get(ICacheFactory::class);
587
+        $memcache = $cacheFactory->createLocal('storage_info');
588
+        $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
589
+        $memcache->remove($cacheKeyPrefix . 'include');
590
+        $memcache->remove($cacheKeyPrefix . 'exclude');
591
+    }
592
+
593
+    /**
594
+     * Returns whether the config file is set manually to read-only
595
+     * @return bool
596
+     */
597
+    public static function isReadOnlyConfigEnabled() {
598
+        return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false);
599
+    }
600 600
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -43,7 +43,7 @@  discard block
 block discarded – undo
43 43
 	 *
44 44
 	 * Makes 2048 to 2 kB.
45 45
 	 */
46
-	public static function humanFileSize(int|float $bytes): string {
46
+	public static function humanFileSize(int | float $bytes): string {
47 47
 		return \OCP\Util::humanFileSize($bytes);
48 48
 	}
49 49
 
@@ -57,7 +57,7 @@  discard block
 block discarded – undo
57 57
 	 *
58 58
 	 * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
59 59
 	 */
60
-	public static function computerFileSize(string $str): false|int|float {
60
+	public static function computerFileSize(string $str): false | int | float {
61 61
 		return \OCP\Util::computerFileSize($str);
62 62
 	}
63 63
 
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 		}
172 172
 		foreach ($dirs as $dir) {
173 173
 			foreach ($exts as $ext) {
174
-				if ($check_fn("$dir/$name" . $ext)) {
174
+				if ($check_fn("$dir/$name".$ext)) {
175 175
 					return true;
176 176
 				}
177 177
 			}
@@ -243,7 +243,7 @@  discard block
 block discarded – undo
243 243
 			$ext = '';
244 244
 		}
245 245
 
246
-		$newpath = $path . '/' . $filename;
246
+		$newpath = $path.'/'.$filename;
247 247
 		if ($view->file_exists($newpath)) {
248 248
 			if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
249 249
 				//Replace the last "(number)" with "(number+1)"
@@ -259,11 +259,11 @@  discard block
 block discarded – undo
259 259
 			do {
260 260
 				if ($offset) {
261 261
 					//Replace the last "(number)" with "(number+1)"
262
-					$newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
262
+					$newname = substr_replace($name, '('.$counter.')', $offset, $match_length);
263 263
 				} else {
264
-					$newname = $name . ' (' . $counter . ')';
264
+					$newname = $name.' ('.$counter.')';
265 265
 				}
266
-				$newpath = $path . '/' . $newname . $ext;
266
+				$newpath = $path.'/'.$newname.$ext;
267 267
 				$counter++;
268 268
 			} while ($view->file_exists($newpath));
269 269
 		}
@@ -344,7 +344,7 @@  discard block
 block discarded – undo
344 344
 			$freeSpace = max($freeSpace, 0);
345 345
 			return $freeSpace;
346 346
 		} else {
347
-			return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
347
+			return (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
348 348
 		}
349 349
 	}
350 350
 
@@ -415,7 +415,7 @@  discard block
 block discarded – undo
415 415
 		}
416 416
 		$fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
417 417
 
418
-		$cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude');
418
+		$cacheKey = $fullPath.'::'.($includeMountPoints ? 'include' : 'exclude');
419 419
 		if ($useCache) {
420 420
 			$cached = $memcache->get($cacheKey);
421 421
 			if ($cached) {
@@ -526,7 +526,7 @@  discard block
 block discarded – undo
526 526
 
527 527
 		if ($isRemoteShare === false && $ownerId !== false && $path === '/') {
528 528
 			// If path is root, store this as last known quota usage for this user
529
-			\OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative);
529
+			\OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string) $relative);
530 530
 		}
531 531
 
532 532
 		$memcache->set($cacheKey, $info, 5 * 60);
@@ -540,7 +540,7 @@  discard block
 block discarded – undo
540 540
 	 * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
541 541
 	 * @return StorageInfo
542 542
 	 */
543
-	private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
543
+	private static function getGlobalStorageInfo(int | float $quota, IUser $user, IMountPoint $mount): array {
544 544
 		$rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
545 545
 		/** @var int|float $used */
546 546
 		$used = $rootInfo['size'];
@@ -585,9 +585,9 @@  discard block
 block discarded – undo
585 585
 		/** @var ICacheFactory $cacheFactory */
586 586
 		$cacheFactory = \OC::$server->get(ICacheFactory::class);
587 587
 		$memcache = $cacheFactory->createLocal('storage_info');
588
-		$cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
589
-		$memcache->remove($cacheKeyPrefix . 'include');
590
-		$memcache->remove($cacheKeyPrefix . 'exclude');
588
+		$cacheKeyPrefix = Filesystem::normalizePath($absolutePath).'::';
589
+		$memcache->remove($cacheKeyPrefix.'include');
590
+		$memcache->remove($cacheKeyPrefix.'exclude');
591 591
 	}
592 592
 
593 593
 	/**
Please login to merge, or discard this patch.
lib/private/legacy/OC_Util.php 1 patch
Indentation   +835 added lines, -835 removed lines patch added patch discarded remove patch
@@ -22,839 +22,839 @@
 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
-	 */
102
-	public static function getUserQuota(?IUser $user) {
103
-		if (is_null($user)) {
104
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
105
-		}
106
-		$userQuota = $user->getQuota();
107
-		if ($userQuota === 'none') {
108
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
109
-		}
110
-		return \OCP\Util::computerFileSize($userQuota);
111
-	}
112
-
113
-	/**
114
-	 * copies the skeleton to the users /files
115
-	 *
116
-	 * @param string $userId
117
-	 * @param \OCP\Files\Folder $userDirectory
118
-	 * @throws \OCP\Files\NotFoundException
119
-	 * @throws \OCP\Files\NotPermittedException
120
-	 * @suppress PhanDeprecatedFunction
121
-	 */
122
-	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
123
-		/** @var LoggerInterface $logger */
124
-		$logger = \OC::$server->get(LoggerInterface::class);
125
-
126
-		$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
127
-		$userLang = \OC::$server->get(IFactory::class)->findLanguage();
128
-		$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
129
-
130
-		if (!file_exists($skeletonDirectory)) {
131
-			$dialectStart = strpos($userLang, '_');
132
-			if ($dialectStart !== false) {
133
-				$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
134
-			}
135
-			if ($dialectStart === false || !file_exists($skeletonDirectory)) {
136
-				$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
137
-			}
138
-			if (!file_exists($skeletonDirectory)) {
139
-				$skeletonDirectory = '';
140
-			}
141
-		}
142
-
143
-		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
144
-
145
-		if ($instanceId === null) {
146
-			throw new \RuntimeException('no instance id!');
147
-		}
148
-		$appdata = 'appdata_' . $instanceId;
149
-		if ($userId === $appdata) {
150
-			throw new \RuntimeException('username is reserved name: ' . $appdata);
151
-		}
152
-
153
-		if (!empty($skeletonDirectory)) {
154
-			$logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
155
-			self::copyr($skeletonDirectory, $userDirectory);
156
-			// update the file cache
157
-			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
158
-
159
-			/** @var ITemplateManager $templateManager */
160
-			$templateManager = \OC::$server->get(ITemplateManager::class);
161
-			$templateManager->initializeTemplateDirectory(null, $userId);
162
-		}
163
-	}
164
-
165
-	/**
166
-	 * copies a directory recursively by using streams
167
-	 *
168
-	 * @param string $source
169
-	 * @param \OCP\Files\Folder $target
170
-	 * @return void
171
-	 */
172
-	public static function copyr($source, \OCP\Files\Folder $target) {
173
-		$logger = \OCP\Server::get(LoggerInterface::class);
174
-
175
-		// Verify if folder exists
176
-		$dir = opendir($source);
177
-		if ($dir === false) {
178
-			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
179
-			return;
180
-		}
181
-
182
-		// Copy the files
183
-		while (false !== ($file = readdir($dir))) {
184
-			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
185
-				if (is_dir($source . '/' . $file)) {
186
-					$child = $target->newFolder($file);
187
-					self::copyr($source . '/' . $file, $child);
188
-				} else {
189
-					$child = $target->newFile($file);
190
-					$sourceStream = fopen($source . '/' . $file, 'r');
191
-					if ($sourceStream === false) {
192
-						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
193
-						closedir($dir);
194
-						return;
195
-					}
196
-					$child->putContent($sourceStream);
197
-				}
198
-			}
199
-		}
200
-		closedir($dir);
201
-	}
202
-
203
-	/**
204
-	 * @deprecated 32.0.0 Call tearDown directly on SetupManager
205
-	 */
206
-	public static function tearDownFS(): void {
207
-		$setupManager = \OCP\Server::get(SetupManager::class);
208
-		$setupManager->tearDown();
209
-	}
210
-
211
-	/**
212
-	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
213
-	 *
214
-	 * @param string $application application to get the files from
215
-	 * @param string $directory directory within this application (css, js, vendor, etc)
216
-	 * @param ?string $file the file inside of the above folder
217
-	 */
218
-	private static function generatePath($application, $directory, $file): string {
219
-		if (is_null($file)) {
220
-			$file = $application;
221
-			$application = '';
222
-		}
223
-		if (!empty($application)) {
224
-			return "$application/$directory/$file";
225
-		} else {
226
-			return "$directory/$file";
227
-		}
228
-	}
229
-
230
-	/**
231
-	 * add a css file
232
-	 *
233
-	 * @param string $application application id
234
-	 * @param string|null $file filename
235
-	 * @param bool $prepend prepend the Style to the beginning of the list
236
-	 * @deprecated 32.0.0 Use \OCP\Util::addStyle
237
-	 */
238
-	public static function addStyle($application, $file = null, $prepend = false): void {
239
-		$path = OC_Util::generatePath($application, 'css', $file);
240
-		self::addExternalResource($application, $prepend, $path, 'style');
241
-	}
242
-
243
-	/**
244
-	 * add a css file from the vendor sub folder
245
-	 *
246
-	 * @param string $application application id
247
-	 * @param string|null $file filename
248
-	 * @param bool $prepend prepend the Style to the beginning of the list
249
-	 * @deprecated 32.0.0
250
-	 */
251
-	public static function addVendorStyle($application, $file = null, $prepend = false): void {
252
-		$path = OC_Util::generatePath($application, 'vendor', $file);
253
-		self::addExternalResource($application, $prepend, $path, 'style');
254
-	}
255
-
256
-	/**
257
-	 * add an external resource css/js file
258
-	 *
259
-	 * @param string $application application id
260
-	 * @param bool $prepend prepend the file to the beginning of the list
261
-	 * @param string $path
262
-	 * @param string $type (script or style)
263
-	 */
264
-	private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
265
-		if ($type === 'style') {
266
-			if (!in_array($path, self::$styles)) {
267
-				if ($prepend === true) {
268
-					array_unshift(self::$styles, $path);
269
-				} else {
270
-					self::$styles[] = $path;
271
-				}
272
-			}
273
-		}
274
-	}
275
-
276
-	/**
277
-	 * Add a custom element to the header
278
-	 * If $text is null then the element will be written as empty element.
279
-	 * So use "" to get a closing tag.
280
-	 * @param string $tag tag name of the element
281
-	 * @param array $attributes array of attributes for the element
282
-	 * @param string $text the text content for the element
283
-	 * @param bool $prepend prepend the header to the beginning of the list
284
-	 * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
285
-	 */
286
-	public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
287
-		$header = [
288
-			'tag' => $tag,
289
-			'attributes' => $attributes,
290
-			'text' => $text
291
-		];
292
-		if ($prepend === true) {
293
-			array_unshift(self::$headers, $header);
294
-		} else {
295
-			self::$headers[] = $header;
296
-		}
297
-	}
298
-
299
-	/**
300
-	 * check if the current server configuration is suitable for ownCloud
301
-	 *
302
-	 * @return array arrays with error messages and hints
303
-	 */
304
-	public static function checkServer(\OC\SystemConfig $config) {
305
-		$l = \OC::$server->getL10N('lib');
306
-		$errors = [];
307
-		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
308
-
309
-		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
310
-			// this check needs to be done every time
311
-			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
312
-		}
313
-
314
-		// Assume that if checkServer() succeeded before in this session, then all is fine.
315
-		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
316
-			return $errors;
317
-		}
318
-
319
-		$webServerRestart = false;
320
-		$setup = \OCP\Server::get(\OC\Setup::class);
321
-
322
-		$urlGenerator = \OC::$server->getURLGenerator();
323
-
324
-		$availableDatabases = $setup->getSupportedDatabases();
325
-		if (empty($availableDatabases)) {
326
-			$errors[] = [
327
-				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
328
-				'hint' => '' //TODO: sane hint
329
-			];
330
-			$webServerRestart = true;
331
-		}
332
-
333
-		// Check if config folder is writable.
334
-		if (!OC_Helper::isReadOnlyConfigEnabled()) {
335
-			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
336
-				$errors[] = [
337
-					'error' => $l->t('Cannot write into "config" directory.'),
338
-					'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
339
-						[ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
340
-						. $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',
341
-							[ $urlGenerator->linkToDocs('admin-config') ])
342
-				];
343
-			}
344
-		}
345
-
346
-		// Check if there is a writable install folder.
347
-		if ($config->getValue('appstoreenabled', true)) {
348
-			if (OC_App::getInstallPath() === null
349
-				|| !is_writable(OC_App::getInstallPath())
350
-				|| !is_readable(OC_App::getInstallPath())
351
-			) {
352
-				$errors[] = [
353
-					'error' => $l->t('Cannot write into "apps" directory.'),
354
-					'hint' => $l->t('This can usually be fixed by giving the web server write access to the apps directory'
355
-						. ' or disabling the App Store in the config file.')
356
-				];
357
-			}
358
-		}
359
-		// Create root dir.
360
-		if ($config->getValue('installed', false)) {
361
-			if (!is_dir($CONFIG_DATADIRECTORY)) {
362
-				$success = @mkdir($CONFIG_DATADIRECTORY);
363
-				if ($success) {
364
-					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
365
-				} else {
366
-					$errors[] = [
367
-						'error' => $l->t('Cannot create "data" directory.'),
368
-						'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
369
-							[$urlGenerator->linkToDocs('admin-dir_permissions')])
370
-					];
371
-				}
372
-			} elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
373
-				// is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
374
-				$testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
375
-				$handle = fopen($testFile, 'w');
376
-				if (!$handle || fwrite($handle, 'Test write operation') === false) {
377
-					$permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
378
-						[$urlGenerator->linkToDocs('admin-dir_permissions')]);
379
-					$errors[] = [
380
-						'error' => $l->t('Your data directory is not writable.'),
381
-						'hint' => $permissionsHint
382
-					];
383
-				} else {
384
-					fclose($handle);
385
-					unlink($testFile);
386
-				}
387
-			} else {
388
-				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
389
-			}
390
-		}
391
-
392
-		if (!OC_Util::isSetLocaleWorking()) {
393
-			$errors[] = [
394
-				'error' => $l->t('Setting locale to %s failed.',
395
-					['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
396
-						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
397
-				'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
398
-			];
399
-		}
400
-
401
-		// Contains the dependencies that should be checked against
402
-		// classes = class_exists
403
-		// functions = function_exists
404
-		// defined = defined
405
-		// ini = ini_get
406
-		// If the dependency is not found the missing module name is shown to the EndUser
407
-		// When adding new checks always verify that they pass on CI as well
408
-		$dependencies = [
409
-			'classes' => [
410
-				'ZipArchive' => 'zip',
411
-				'DOMDocument' => 'dom',
412
-				'XMLWriter' => 'XMLWriter',
413
-				'XMLReader' => 'XMLReader',
414
-			],
415
-			'functions' => [
416
-				'xml_parser_create' => 'libxml',
417
-				'mb_strcut' => 'mbstring',
418
-				'ctype_digit' => 'ctype',
419
-				'json_encode' => 'JSON',
420
-				'gd_info' => 'GD',
421
-				'gzencode' => 'zlib',
422
-				'simplexml_load_string' => 'SimpleXML',
423
-				'hash' => 'HASH Message Digest Framework',
424
-				'curl_init' => 'cURL',
425
-				'openssl_verify' => 'OpenSSL',
426
-			],
427
-			'defined' => [
428
-				'PDO::ATTR_DRIVER_NAME' => 'PDO'
429
-			],
430
-			'ini' => [
431
-				'default_charset' => 'UTF-8',
432
-			],
433
-		];
434
-		$missingDependencies = [];
435
-		$invalidIniSettings = [];
436
-
437
-		$iniWrapper = \OC::$server->get(IniGetWrapper::class);
438
-		foreach ($dependencies['classes'] as $class => $module) {
439
-			if (!class_exists($class)) {
440
-				$missingDependencies[] = $module;
441
-			}
442
-		}
443
-		foreach ($dependencies['functions'] as $function => $module) {
444
-			if (!function_exists($function)) {
445
-				$missingDependencies[] = $module;
446
-			}
447
-		}
448
-		foreach ($dependencies['defined'] as $defined => $module) {
449
-			if (!defined($defined)) {
450
-				$missingDependencies[] = $module;
451
-			}
452
-		}
453
-		foreach ($dependencies['ini'] as $setting => $expected) {
454
-			if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
455
-				$invalidIniSettings[] = [$setting, $expected];
456
-			}
457
-		}
458
-
459
-		foreach ($missingDependencies as $missingDependency) {
460
-			$errors[] = [
461
-				'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
462
-				'hint' => $l->t('Please ask your server administrator to install the module.'),
463
-			];
464
-			$webServerRestart = true;
465
-		}
466
-		foreach ($invalidIniSettings as $setting) {
467
-			$errors[] = [
468
-				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
469
-				'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
470
-			];
471
-			$webServerRestart = true;
472
-		}
473
-
474
-		/**
475
-		 * The mbstring.func_overload check can only be performed if the mbstring
476
-		 * module is installed as it will return null if the checking setting is
477
-		 * not available and thus a check on the boolean value fails.
478
-		 *
479
-		 * TODO: Should probably be implemented in the above generic dependency
480
-		 *       check somehow in the long-term.
481
-		 */
482
-		if ($iniWrapper->getBool('mbstring.func_overload') !== null &&
483
-			$iniWrapper->getBool('mbstring.func_overload') === true) {
484
-			$errors[] = [
485
-				'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')]),
486
-				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.')
487
-			];
488
-		}
489
-
490
-		if (!self::isAnnotationsWorking()) {
491
-			$errors[] = [
492
-				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
493
-				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
494
-			];
495
-		}
496
-
497
-		if (!\OC::$CLI && $webServerRestart) {
498
-			$errors[] = [
499
-				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
500
-				'hint' => $l->t('Please ask your server administrator to restart the web server.')
501
-			];
502
-		}
503
-
504
-		foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
505
-			if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
506
-				$errors[] = [
507
-					'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
508
-					'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
509
-				];
510
-			}
511
-		}
512
-
513
-		// Cache the result of this function
514
-		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
515
-
516
-		return $errors;
517
-	}
518
-
519
-	/**
520
-	 * Check for correct file permissions of data directory
521
-	 *
522
-	 * @param string $dataDirectory
523
-	 * @return array arrays with error messages and hints
524
-	 * @internal
525
-	 */
526
-	public static function checkDataDirectoryPermissions($dataDirectory) {
527
-		if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
528
-			return  [];
529
-		}
530
-
531
-		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
532
-		if (substr($perms, -1) !== '0') {
533
-			chmod($dataDirectory, 0770);
534
-			clearstatcache();
535
-			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
536
-			if ($perms[2] !== '0') {
537
-				$l = \OC::$server->getL10N('lib');
538
-				return [[
539
-					'error' => $l->t('Your data directory is readable by other people.'),
540
-					'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
541
-				]];
542
-			}
543
-		}
544
-		return [];
545
-	}
546
-
547
-	/**
548
-	 * Check that the data directory exists and is valid by
549
-	 * checking the existence of the ".ncdata" file.
550
-	 *
551
-	 * @param string $dataDirectory data directory path
552
-	 * @return array errors found
553
-	 * @internal
554
-	 */
555
-	public static function checkDataDirectoryValidity($dataDirectory) {
556
-		$l = \OC::$server->getL10N('lib');
557
-		$errors = [];
558
-		if ($dataDirectory[0] !== '/') {
559
-			$errors[] = [
560
-				'error' => $l->t('Your data directory must be an absolute path.'),
561
-				'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
562
-			];
563
-		}
564
-
565
-		if (!file_exists($dataDirectory . '/.ncdata')) {
566
-			$errors[] = [
567
-				'error' => $l->t('Your data directory is invalid.'),
568
-				'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']),
569
-			];
570
-		}
571
-		return $errors;
572
-	}
573
-
574
-	/**
575
-	 * Check if the user is logged in, redirects to home if not. With
576
-	 * redirect URL parameter to the request URI.
577
-	 *
578
-	 * @deprecated 32.0.0
579
-	 */
580
-	public static function checkLoggedIn(): void {
581
-		// Check if we are a user
582
-		if (!\OC::$server->getUserSession()->isLoggedIn()) {
583
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
584
-				'core.login.showLoginForm',
585
-				[
586
-					'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
587
-				]
588
-			)
589
-			);
590
-			exit();
591
-		}
592
-		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
593
-		if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
594
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
595
-			exit();
596
-		}
597
-	}
598
-
599
-	/**
600
-	 * Check if the user is a admin, redirects to home if not
601
-	 *
602
-	 * @deprecated 32.0.0
603
-	 */
604
-	public static function checkAdminUser(): void {
605
-		self::checkLoggedIn();
606
-		if (!OC_User::isAdminUser(OC_User::getUser())) {
607
-			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
608
-			exit();
609
-		}
610
-	}
611
-
612
-	/**
613
-	 * Returns the URL of the default page
614
-	 * based on the system configuration and
615
-	 * the apps visible for the current user
616
-	 *
617
-	 * @return string URL
618
-	 * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
619
-	 */
620
-	public static function getDefaultPageUrl() {
621
-		/** @var IURLGenerator $urlGenerator */
622
-		$urlGenerator = \OC::$server->get(IURLGenerator::class);
623
-		return $urlGenerator->linkToDefaultPageUrl();
624
-	}
625
-
626
-	/**
627
-	 * Redirect to the user default page
628
-	 *
629
-	 * @deprecated 32.0.0
630
-	 */
631
-	public static function redirectToDefaultPage(): void {
632
-		$location = self::getDefaultPageUrl();
633
-		header('Location: ' . $location);
634
-		exit();
635
-	}
636
-
637
-	/**
638
-	 * get an id unique for this instance
639
-	 *
640
-	 * @return string
641
-	 */
642
-	public static function getInstanceId(): string {
643
-		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
644
-		if (is_null($id)) {
645
-			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
646
-			$id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
647
-			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
648
-		}
649
-		return $id;
650
-	}
651
-
652
-	/**
653
-	 * Public function to sanitize HTML
654
-	 *
655
-	 * This function is used to sanitize HTML and should be applied on any
656
-	 * string or array of strings before displaying it on a web page.
657
-	 *
658
-	 * @param string|string[] $value
659
-	 * @return ($value is array ? string[] : string)
660
-	 * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
661
-	 */
662
-	public static function sanitizeHTML($value) {
663
-		if (is_array($value)) {
664
-			$value = array_map(function ($value) {
665
-				return self::sanitizeHTML($value);
666
-			}, $value);
667
-		} else {
668
-			// Specify encoding for PHP<5.4
669
-			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
670
-		}
671
-		return $value;
672
-	}
673
-
674
-	/**
675
-	 * Public function to encode url parameters
676
-	 *
677
-	 * This function is used to encode path to file before output.
678
-	 * Encoding is done according to RFC 3986 with one exception:
679
-	 * Character '/' is preserved as is.
680
-	 *
681
-	 * @param string $component part of URI to encode
682
-	 * @return string
683
-	 * @deprecated 32.0.0 use \OCP\Util::encodePath instead
684
-	 */
685
-	public static function encodePath($component) {
686
-		$encoded = rawurlencode($component);
687
-		$encoded = str_replace('%2F', '/', $encoded);
688
-		return $encoded;
689
-	}
690
-
691
-	/**
692
-	 * Check if current locale is non-UTF8
693
-	 *
694
-	 * @return bool
695
-	 */
696
-	private static function isNonUTF8Locale() {
697
-		if (function_exists('escapeshellcmd')) {
698
-			return escapeshellcmd('§') === '';
699
-		} elseif (function_exists('escapeshellarg')) {
700
-			return escapeshellarg('§') === '\'\'';
701
-		} else {
702
-			return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
703
-		}
704
-	}
705
-
706
-	/**
707
-	 * Check if the setlocale call does not work. This can happen if the right
708
-	 * local packages are not available on the server.
709
-	 *
710
-	 * @internal
711
-	 */
712
-	public static function isSetLocaleWorking(): bool {
713
-		if (self::isNonUTF8Locale()) {
714
-			// Borrowed from \Patchwork\Utf8\Bootup::initLocale
715
-			setlocale(LC_ALL, 'C.UTF-8', 'C');
716
-			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');
717
-
718
-			// Check again
719
-			if (self::isNonUTF8Locale()) {
720
-				return false;
721
-			}
722
-		}
723
-
724
-		return true;
725
-	}
726
-
727
-	/**
728
-	 * Check if it's possible to get the inline annotations
729
-	 *
730
-	 * @internal
731
-	 */
732
-	public static function isAnnotationsWorking(): bool {
733
-		if (PHP_VERSION_ID >= 80300) {
734
-			/** @psalm-suppress UndefinedMethod */
735
-			$reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
736
-		} else {
737
-			$reflection = new \ReflectionMethod(__METHOD__);
738
-		}
739
-		$docs = $reflection->getDocComment();
740
-
741
-		return (is_string($docs) && strlen($docs) > 50);
742
-	}
743
-
744
-	/**
745
-	 * Check if the PHP module fileinfo is loaded.
746
-	 *
747
-	 * @internal
748
-	 */
749
-	public static function fileInfoLoaded(): bool {
750
-		return function_exists('finfo_open');
751
-	}
752
-
753
-	/**
754
-	 * clear all levels of output buffering
755
-	 *
756
-	 * @return void
757
-	 */
758
-	public static function obEnd() {
759
-		while (ob_get_level()) {
760
-			ob_end_clean();
761
-		}
762
-	}
763
-
764
-	/**
765
-	 * Checks whether the server is running on Mac OS X
766
-	 *
767
-	 * @return bool true if running on Mac OS X, false otherwise
768
-	 */
769
-	public static function runningOnMac() {
770
-		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
771
-	}
772
-
773
-	/**
774
-	 * Handles the case that there may not be a theme, then check if a "default"
775
-	 * theme exists and take that one
776
-	 *
777
-	 * @return string the theme
778
-	 */
779
-	public static function getTheme() {
780
-		$theme = \OC::$server->getSystemConfig()->getValue('theme', '');
781
-
782
-		if ($theme === '') {
783
-			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
784
-				$theme = 'default';
785
-			}
786
-		}
787
-
788
-		return $theme;
789
-	}
790
-
791
-	/**
792
-	 * Normalize a unicode string
793
-	 *
794
-	 * @param string $value a not normalized string
795
-	 * @return string The normalized string or the input if the normalization failed
796
-	 */
797
-	public static function normalizeUnicode(string $value): string {
798
-		if (Normalizer::isNormalized($value)) {
799
-			return $value;
800
-		}
801
-
802
-		$normalizedValue = Normalizer::normalize($value);
803
-		if ($normalizedValue === false) {
804
-			\OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
805
-			return $value;
806
-		}
807
-
808
-		return $normalizedValue;
809
-	}
810
-
811
-	/**
812
-	 * Check whether the instance needs to perform an upgrade,
813
-	 * either when the core version is higher or any app requires
814
-	 * an upgrade.
815
-	 *
816
-	 * @param \OC\SystemConfig $config
817
-	 * @return bool whether the core or any app needs an upgrade
818
-	 * @throws \OCP\HintException When the upgrade from the given version is not allowed
819
-	 * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
820
-	 */
821
-	public static function needUpgrade(\OC\SystemConfig $config) {
822
-		if ($config->getValue('installed', false)) {
823
-			$installedVersion = $config->getValue('version', '0.0.0');
824
-			$currentVersion = implode('.', \OCP\Util::getVersion());
825
-			$versionDiff = version_compare($currentVersion, $installedVersion);
826
-			if ($versionDiff > 0) {
827
-				return true;
828
-			} elseif ($config->getValue('debug', false) && $versionDiff < 0) {
829
-				// downgrade with debug
830
-				$installedMajor = explode('.', $installedVersion);
831
-				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
832
-				$currentMajor = explode('.', $currentVersion);
833
-				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
834
-				if ($installedMajor === $currentMajor) {
835
-					// Same major, allow downgrade for developers
836
-					return true;
837
-				} else {
838
-					// downgrade attempt, throw exception
839
-					throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
840
-				}
841
-			} elseif ($versionDiff < 0) {
842
-				// downgrade attempt, throw exception
843
-				throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
844
-			}
845
-
846
-			// also check for upgrades for apps (independently from the user)
847
-			$apps = \OC_App::getEnabledApps(false, true);
848
-			$shouldUpgrade = false;
849
-			foreach ($apps as $app) {
850
-				if (\OC_App::shouldUpgrade($app)) {
851
-					$shouldUpgrade = true;
852
-					break;
853
-				}
854
-			}
855
-			return $shouldUpgrade;
856
-		} else {
857
-			return false;
858
-		}
859
-	}
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
+     */
102
+    public static function getUserQuota(?IUser $user) {
103
+        if (is_null($user)) {
104
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
105
+        }
106
+        $userQuota = $user->getQuota();
107
+        if ($userQuota === 'none') {
108
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
109
+        }
110
+        return \OCP\Util::computerFileSize($userQuota);
111
+    }
112
+
113
+    /**
114
+     * copies the skeleton to the users /files
115
+     *
116
+     * @param string $userId
117
+     * @param \OCP\Files\Folder $userDirectory
118
+     * @throws \OCP\Files\NotFoundException
119
+     * @throws \OCP\Files\NotPermittedException
120
+     * @suppress PhanDeprecatedFunction
121
+     */
122
+    public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
123
+        /** @var LoggerInterface $logger */
124
+        $logger = \OC::$server->get(LoggerInterface::class);
125
+
126
+        $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
127
+        $userLang = \OC::$server->get(IFactory::class)->findLanguage();
128
+        $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
129
+
130
+        if (!file_exists($skeletonDirectory)) {
131
+            $dialectStart = strpos($userLang, '_');
132
+            if ($dialectStart !== false) {
133
+                $skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
134
+            }
135
+            if ($dialectStart === false || !file_exists($skeletonDirectory)) {
136
+                $skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
137
+            }
138
+            if (!file_exists($skeletonDirectory)) {
139
+                $skeletonDirectory = '';
140
+            }
141
+        }
142
+
143
+        $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
144
+
145
+        if ($instanceId === null) {
146
+            throw new \RuntimeException('no instance id!');
147
+        }
148
+        $appdata = 'appdata_' . $instanceId;
149
+        if ($userId === $appdata) {
150
+            throw new \RuntimeException('username is reserved name: ' . $appdata);
151
+        }
152
+
153
+        if (!empty($skeletonDirectory)) {
154
+            $logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
155
+            self::copyr($skeletonDirectory, $userDirectory);
156
+            // update the file cache
157
+            $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
158
+
159
+            /** @var ITemplateManager $templateManager */
160
+            $templateManager = \OC::$server->get(ITemplateManager::class);
161
+            $templateManager->initializeTemplateDirectory(null, $userId);
162
+        }
163
+    }
164
+
165
+    /**
166
+     * copies a directory recursively by using streams
167
+     *
168
+     * @param string $source
169
+     * @param \OCP\Files\Folder $target
170
+     * @return void
171
+     */
172
+    public static function copyr($source, \OCP\Files\Folder $target) {
173
+        $logger = \OCP\Server::get(LoggerInterface::class);
174
+
175
+        // Verify if folder exists
176
+        $dir = opendir($source);
177
+        if ($dir === false) {
178
+            $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
179
+            return;
180
+        }
181
+
182
+        // Copy the files
183
+        while (false !== ($file = readdir($dir))) {
184
+            if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
185
+                if (is_dir($source . '/' . $file)) {
186
+                    $child = $target->newFolder($file);
187
+                    self::copyr($source . '/' . $file, $child);
188
+                } else {
189
+                    $child = $target->newFile($file);
190
+                    $sourceStream = fopen($source . '/' . $file, 'r');
191
+                    if ($sourceStream === false) {
192
+                        $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
193
+                        closedir($dir);
194
+                        return;
195
+                    }
196
+                    $child->putContent($sourceStream);
197
+                }
198
+            }
199
+        }
200
+        closedir($dir);
201
+    }
202
+
203
+    /**
204
+     * @deprecated 32.0.0 Call tearDown directly on SetupManager
205
+     */
206
+    public static function tearDownFS(): void {
207
+        $setupManager = \OCP\Server::get(SetupManager::class);
208
+        $setupManager->tearDown();
209
+    }
210
+
211
+    /**
212
+     * generates a path for JS/CSS files. If no application is provided it will create the path for core.
213
+     *
214
+     * @param string $application application to get the files from
215
+     * @param string $directory directory within this application (css, js, vendor, etc)
216
+     * @param ?string $file the file inside of the above folder
217
+     */
218
+    private static function generatePath($application, $directory, $file): string {
219
+        if (is_null($file)) {
220
+            $file = $application;
221
+            $application = '';
222
+        }
223
+        if (!empty($application)) {
224
+            return "$application/$directory/$file";
225
+        } else {
226
+            return "$directory/$file";
227
+        }
228
+    }
229
+
230
+    /**
231
+     * add a css file
232
+     *
233
+     * @param string $application application id
234
+     * @param string|null $file filename
235
+     * @param bool $prepend prepend the Style to the beginning of the list
236
+     * @deprecated 32.0.0 Use \OCP\Util::addStyle
237
+     */
238
+    public static function addStyle($application, $file = null, $prepend = false): void {
239
+        $path = OC_Util::generatePath($application, 'css', $file);
240
+        self::addExternalResource($application, $prepend, $path, 'style');
241
+    }
242
+
243
+    /**
244
+     * add a css file from the vendor sub folder
245
+     *
246
+     * @param string $application application id
247
+     * @param string|null $file filename
248
+     * @param bool $prepend prepend the Style to the beginning of the list
249
+     * @deprecated 32.0.0
250
+     */
251
+    public static function addVendorStyle($application, $file = null, $prepend = false): void {
252
+        $path = OC_Util::generatePath($application, 'vendor', $file);
253
+        self::addExternalResource($application, $prepend, $path, 'style');
254
+    }
255
+
256
+    /**
257
+     * add an external resource css/js file
258
+     *
259
+     * @param string $application application id
260
+     * @param bool $prepend prepend the file to the beginning of the list
261
+     * @param string $path
262
+     * @param string $type (script or style)
263
+     */
264
+    private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
265
+        if ($type === 'style') {
266
+            if (!in_array($path, self::$styles)) {
267
+                if ($prepend === true) {
268
+                    array_unshift(self::$styles, $path);
269
+                } else {
270
+                    self::$styles[] = $path;
271
+                }
272
+            }
273
+        }
274
+    }
275
+
276
+    /**
277
+     * Add a custom element to the header
278
+     * If $text is null then the element will be written as empty element.
279
+     * So use "" to get a closing tag.
280
+     * @param string $tag tag name of the element
281
+     * @param array $attributes array of attributes for the element
282
+     * @param string $text the text content for the element
283
+     * @param bool $prepend prepend the header to the beginning of the list
284
+     * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
285
+     */
286
+    public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
287
+        $header = [
288
+            'tag' => $tag,
289
+            'attributes' => $attributes,
290
+            'text' => $text
291
+        ];
292
+        if ($prepend === true) {
293
+            array_unshift(self::$headers, $header);
294
+        } else {
295
+            self::$headers[] = $header;
296
+        }
297
+    }
298
+
299
+    /**
300
+     * check if the current server configuration is suitable for ownCloud
301
+     *
302
+     * @return array arrays with error messages and hints
303
+     */
304
+    public static function checkServer(\OC\SystemConfig $config) {
305
+        $l = \OC::$server->getL10N('lib');
306
+        $errors = [];
307
+        $CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
308
+
309
+        if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
310
+            // this check needs to be done every time
311
+            $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
312
+        }
313
+
314
+        // Assume that if checkServer() succeeded before in this session, then all is fine.
315
+        if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
316
+            return $errors;
317
+        }
318
+
319
+        $webServerRestart = false;
320
+        $setup = \OCP\Server::get(\OC\Setup::class);
321
+
322
+        $urlGenerator = \OC::$server->getURLGenerator();
323
+
324
+        $availableDatabases = $setup->getSupportedDatabases();
325
+        if (empty($availableDatabases)) {
326
+            $errors[] = [
327
+                'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
328
+                'hint' => '' //TODO: sane hint
329
+            ];
330
+            $webServerRestart = true;
331
+        }
332
+
333
+        // Check if config folder is writable.
334
+        if (!OC_Helper::isReadOnlyConfigEnabled()) {
335
+            if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
336
+                $errors[] = [
337
+                    'error' => $l->t('Cannot write into "config" directory.'),
338
+                    'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
339
+                        [ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
340
+                        . $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',
341
+                            [ $urlGenerator->linkToDocs('admin-config') ])
342
+                ];
343
+            }
344
+        }
345
+
346
+        // Check if there is a writable install folder.
347
+        if ($config->getValue('appstoreenabled', true)) {
348
+            if (OC_App::getInstallPath() === null
349
+                || !is_writable(OC_App::getInstallPath())
350
+                || !is_readable(OC_App::getInstallPath())
351
+            ) {
352
+                $errors[] = [
353
+                    'error' => $l->t('Cannot write into "apps" directory.'),
354
+                    'hint' => $l->t('This can usually be fixed by giving the web server write access to the apps directory'
355
+                        . ' or disabling the App Store in the config file.')
356
+                ];
357
+            }
358
+        }
359
+        // Create root dir.
360
+        if ($config->getValue('installed', false)) {
361
+            if (!is_dir($CONFIG_DATADIRECTORY)) {
362
+                $success = @mkdir($CONFIG_DATADIRECTORY);
363
+                if ($success) {
364
+                    $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
365
+                } else {
366
+                    $errors[] = [
367
+                        'error' => $l->t('Cannot create "data" directory.'),
368
+                        'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
369
+                            [$urlGenerator->linkToDocs('admin-dir_permissions')])
370
+                    ];
371
+                }
372
+            } elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
373
+                // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
374
+                $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
375
+                $handle = fopen($testFile, 'w');
376
+                if (!$handle || fwrite($handle, 'Test write operation') === false) {
377
+                    $permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
378
+                        [$urlGenerator->linkToDocs('admin-dir_permissions')]);
379
+                    $errors[] = [
380
+                        'error' => $l->t('Your data directory is not writable.'),
381
+                        'hint' => $permissionsHint
382
+                    ];
383
+                } else {
384
+                    fclose($handle);
385
+                    unlink($testFile);
386
+                }
387
+            } else {
388
+                $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
389
+            }
390
+        }
391
+
392
+        if (!OC_Util::isSetLocaleWorking()) {
393
+            $errors[] = [
394
+                'error' => $l->t('Setting locale to %s failed.',
395
+                    ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
396
+                        . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
397
+                'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
398
+            ];
399
+        }
400
+
401
+        // Contains the dependencies that should be checked against
402
+        // classes = class_exists
403
+        // functions = function_exists
404
+        // defined = defined
405
+        // ini = ini_get
406
+        // If the dependency is not found the missing module name is shown to the EndUser
407
+        // When adding new checks always verify that they pass on CI as well
408
+        $dependencies = [
409
+            'classes' => [
410
+                'ZipArchive' => 'zip',
411
+                'DOMDocument' => 'dom',
412
+                'XMLWriter' => 'XMLWriter',
413
+                'XMLReader' => 'XMLReader',
414
+            ],
415
+            'functions' => [
416
+                'xml_parser_create' => 'libxml',
417
+                'mb_strcut' => 'mbstring',
418
+                'ctype_digit' => 'ctype',
419
+                'json_encode' => 'JSON',
420
+                'gd_info' => 'GD',
421
+                'gzencode' => 'zlib',
422
+                'simplexml_load_string' => 'SimpleXML',
423
+                'hash' => 'HASH Message Digest Framework',
424
+                'curl_init' => 'cURL',
425
+                'openssl_verify' => 'OpenSSL',
426
+            ],
427
+            'defined' => [
428
+                'PDO::ATTR_DRIVER_NAME' => 'PDO'
429
+            ],
430
+            'ini' => [
431
+                'default_charset' => 'UTF-8',
432
+            ],
433
+        ];
434
+        $missingDependencies = [];
435
+        $invalidIniSettings = [];
436
+
437
+        $iniWrapper = \OC::$server->get(IniGetWrapper::class);
438
+        foreach ($dependencies['classes'] as $class => $module) {
439
+            if (!class_exists($class)) {
440
+                $missingDependencies[] = $module;
441
+            }
442
+        }
443
+        foreach ($dependencies['functions'] as $function => $module) {
444
+            if (!function_exists($function)) {
445
+                $missingDependencies[] = $module;
446
+            }
447
+        }
448
+        foreach ($dependencies['defined'] as $defined => $module) {
449
+            if (!defined($defined)) {
450
+                $missingDependencies[] = $module;
451
+            }
452
+        }
453
+        foreach ($dependencies['ini'] as $setting => $expected) {
454
+            if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
455
+                $invalidIniSettings[] = [$setting, $expected];
456
+            }
457
+        }
458
+
459
+        foreach ($missingDependencies as $missingDependency) {
460
+            $errors[] = [
461
+                'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
462
+                'hint' => $l->t('Please ask your server administrator to install the module.'),
463
+            ];
464
+            $webServerRestart = true;
465
+        }
466
+        foreach ($invalidIniSettings as $setting) {
467
+            $errors[] = [
468
+                'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
469
+                'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
470
+            ];
471
+            $webServerRestart = true;
472
+        }
473
+
474
+        /**
475
+         * The mbstring.func_overload check can only be performed if the mbstring
476
+         * module is installed as it will return null if the checking setting is
477
+         * not available and thus a check on the boolean value fails.
478
+         *
479
+         * TODO: Should probably be implemented in the above generic dependency
480
+         *       check somehow in the long-term.
481
+         */
482
+        if ($iniWrapper->getBool('mbstring.func_overload') !== null &&
483
+            $iniWrapper->getBool('mbstring.func_overload') === true) {
484
+            $errors[] = [
485
+                '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')]),
486
+                'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.')
487
+            ];
488
+        }
489
+
490
+        if (!self::isAnnotationsWorking()) {
491
+            $errors[] = [
492
+                'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
493
+                'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
494
+            ];
495
+        }
496
+
497
+        if (!\OC::$CLI && $webServerRestart) {
498
+            $errors[] = [
499
+                'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
500
+                'hint' => $l->t('Please ask your server administrator to restart the web server.')
501
+            ];
502
+        }
503
+
504
+        foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
505
+            if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
506
+                $errors[] = [
507
+                    'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
508
+                    'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
509
+                ];
510
+            }
511
+        }
512
+
513
+        // Cache the result of this function
514
+        \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
515
+
516
+        return $errors;
517
+    }
518
+
519
+    /**
520
+     * Check for correct file permissions of data directory
521
+     *
522
+     * @param string $dataDirectory
523
+     * @return array arrays with error messages and hints
524
+     * @internal
525
+     */
526
+    public static function checkDataDirectoryPermissions($dataDirectory) {
527
+        if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
528
+            return  [];
529
+        }
530
+
531
+        $perms = substr(decoct(@fileperms($dataDirectory)), -3);
532
+        if (substr($perms, -1) !== '0') {
533
+            chmod($dataDirectory, 0770);
534
+            clearstatcache();
535
+            $perms = substr(decoct(@fileperms($dataDirectory)), -3);
536
+            if ($perms[2] !== '0') {
537
+                $l = \OC::$server->getL10N('lib');
538
+                return [[
539
+                    'error' => $l->t('Your data directory is readable by other people.'),
540
+                    'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
541
+                ]];
542
+            }
543
+        }
544
+        return [];
545
+    }
546
+
547
+    /**
548
+     * Check that the data directory exists and is valid by
549
+     * checking the existence of the ".ncdata" file.
550
+     *
551
+     * @param string $dataDirectory data directory path
552
+     * @return array errors found
553
+     * @internal
554
+     */
555
+    public static function checkDataDirectoryValidity($dataDirectory) {
556
+        $l = \OC::$server->getL10N('lib');
557
+        $errors = [];
558
+        if ($dataDirectory[0] !== '/') {
559
+            $errors[] = [
560
+                'error' => $l->t('Your data directory must be an absolute path.'),
561
+                'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
562
+            ];
563
+        }
564
+
565
+        if (!file_exists($dataDirectory . '/.ncdata')) {
566
+            $errors[] = [
567
+                'error' => $l->t('Your data directory is invalid.'),
568
+                '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']),
569
+            ];
570
+        }
571
+        return $errors;
572
+    }
573
+
574
+    /**
575
+     * Check if the user is logged in, redirects to home if not. With
576
+     * redirect URL parameter to the request URI.
577
+     *
578
+     * @deprecated 32.0.0
579
+     */
580
+    public static function checkLoggedIn(): void {
581
+        // Check if we are a user
582
+        if (!\OC::$server->getUserSession()->isLoggedIn()) {
583
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
584
+                'core.login.showLoginForm',
585
+                [
586
+                    'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
587
+                ]
588
+            )
589
+            );
590
+            exit();
591
+        }
592
+        // Redirect to 2FA challenge selection if 2FA challenge was not solved yet
593
+        if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
594
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
595
+            exit();
596
+        }
597
+    }
598
+
599
+    /**
600
+     * Check if the user is a admin, redirects to home if not
601
+     *
602
+     * @deprecated 32.0.0
603
+     */
604
+    public static function checkAdminUser(): void {
605
+        self::checkLoggedIn();
606
+        if (!OC_User::isAdminUser(OC_User::getUser())) {
607
+            header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
608
+            exit();
609
+        }
610
+    }
611
+
612
+    /**
613
+     * Returns the URL of the default page
614
+     * based on the system configuration and
615
+     * the apps visible for the current user
616
+     *
617
+     * @return string URL
618
+     * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
619
+     */
620
+    public static function getDefaultPageUrl() {
621
+        /** @var IURLGenerator $urlGenerator */
622
+        $urlGenerator = \OC::$server->get(IURLGenerator::class);
623
+        return $urlGenerator->linkToDefaultPageUrl();
624
+    }
625
+
626
+    /**
627
+     * Redirect to the user default page
628
+     *
629
+     * @deprecated 32.0.0
630
+     */
631
+    public static function redirectToDefaultPage(): void {
632
+        $location = self::getDefaultPageUrl();
633
+        header('Location: ' . $location);
634
+        exit();
635
+    }
636
+
637
+    /**
638
+     * get an id unique for this instance
639
+     *
640
+     * @return string
641
+     */
642
+    public static function getInstanceId(): string {
643
+        $id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
644
+        if (is_null($id)) {
645
+            // We need to guarantee at least one letter in instanceid so it can be used as the session_name
646
+            $id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
647
+            \OC::$server->getSystemConfig()->setValue('instanceid', $id);
648
+        }
649
+        return $id;
650
+    }
651
+
652
+    /**
653
+     * Public function to sanitize HTML
654
+     *
655
+     * This function is used to sanitize HTML and should be applied on any
656
+     * string or array of strings before displaying it on a web page.
657
+     *
658
+     * @param string|string[] $value
659
+     * @return ($value is array ? string[] : string)
660
+     * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
661
+     */
662
+    public static function sanitizeHTML($value) {
663
+        if (is_array($value)) {
664
+            $value = array_map(function ($value) {
665
+                return self::sanitizeHTML($value);
666
+            }, $value);
667
+        } else {
668
+            // Specify encoding for PHP<5.4
669
+            $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
670
+        }
671
+        return $value;
672
+    }
673
+
674
+    /**
675
+     * Public function to encode url parameters
676
+     *
677
+     * This function is used to encode path to file before output.
678
+     * Encoding is done according to RFC 3986 with one exception:
679
+     * Character '/' is preserved as is.
680
+     *
681
+     * @param string $component part of URI to encode
682
+     * @return string
683
+     * @deprecated 32.0.0 use \OCP\Util::encodePath instead
684
+     */
685
+    public static function encodePath($component) {
686
+        $encoded = rawurlencode($component);
687
+        $encoded = str_replace('%2F', '/', $encoded);
688
+        return $encoded;
689
+    }
690
+
691
+    /**
692
+     * Check if current locale is non-UTF8
693
+     *
694
+     * @return bool
695
+     */
696
+    private static function isNonUTF8Locale() {
697
+        if (function_exists('escapeshellcmd')) {
698
+            return escapeshellcmd('§') === '';
699
+        } elseif (function_exists('escapeshellarg')) {
700
+            return escapeshellarg('§') === '\'\'';
701
+        } else {
702
+            return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
703
+        }
704
+    }
705
+
706
+    /**
707
+     * Check if the setlocale call does not work. This can happen if the right
708
+     * local packages are not available on the server.
709
+     *
710
+     * @internal
711
+     */
712
+    public static function isSetLocaleWorking(): bool {
713
+        if (self::isNonUTF8Locale()) {
714
+            // Borrowed from \Patchwork\Utf8\Bootup::initLocale
715
+            setlocale(LC_ALL, 'C.UTF-8', 'C');
716
+            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');
717
+
718
+            // Check again
719
+            if (self::isNonUTF8Locale()) {
720
+                return false;
721
+            }
722
+        }
723
+
724
+        return true;
725
+    }
726
+
727
+    /**
728
+     * Check if it's possible to get the inline annotations
729
+     *
730
+     * @internal
731
+     */
732
+    public static function isAnnotationsWorking(): bool {
733
+        if (PHP_VERSION_ID >= 80300) {
734
+            /** @psalm-suppress UndefinedMethod */
735
+            $reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
736
+        } else {
737
+            $reflection = new \ReflectionMethod(__METHOD__);
738
+        }
739
+        $docs = $reflection->getDocComment();
740
+
741
+        return (is_string($docs) && strlen($docs) > 50);
742
+    }
743
+
744
+    /**
745
+     * Check if the PHP module fileinfo is loaded.
746
+     *
747
+     * @internal
748
+     */
749
+    public static function fileInfoLoaded(): bool {
750
+        return function_exists('finfo_open');
751
+    }
752
+
753
+    /**
754
+     * clear all levels of output buffering
755
+     *
756
+     * @return void
757
+     */
758
+    public static function obEnd() {
759
+        while (ob_get_level()) {
760
+            ob_end_clean();
761
+        }
762
+    }
763
+
764
+    /**
765
+     * Checks whether the server is running on Mac OS X
766
+     *
767
+     * @return bool true if running on Mac OS X, false otherwise
768
+     */
769
+    public static function runningOnMac() {
770
+        return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
771
+    }
772
+
773
+    /**
774
+     * Handles the case that there may not be a theme, then check if a "default"
775
+     * theme exists and take that one
776
+     *
777
+     * @return string the theme
778
+     */
779
+    public static function getTheme() {
780
+        $theme = \OC::$server->getSystemConfig()->getValue('theme', '');
781
+
782
+        if ($theme === '') {
783
+            if (is_dir(OC::$SERVERROOT . '/themes/default')) {
784
+                $theme = 'default';
785
+            }
786
+        }
787
+
788
+        return $theme;
789
+    }
790
+
791
+    /**
792
+     * Normalize a unicode string
793
+     *
794
+     * @param string $value a not normalized string
795
+     * @return string The normalized string or the input if the normalization failed
796
+     */
797
+    public static function normalizeUnicode(string $value): string {
798
+        if (Normalizer::isNormalized($value)) {
799
+            return $value;
800
+        }
801
+
802
+        $normalizedValue = Normalizer::normalize($value);
803
+        if ($normalizedValue === false) {
804
+            \OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
805
+            return $value;
806
+        }
807
+
808
+        return $normalizedValue;
809
+    }
810
+
811
+    /**
812
+     * Check whether the instance needs to perform an upgrade,
813
+     * either when the core version is higher or any app requires
814
+     * an upgrade.
815
+     *
816
+     * @param \OC\SystemConfig $config
817
+     * @return bool whether the core or any app needs an upgrade
818
+     * @throws \OCP\HintException When the upgrade from the given version is not allowed
819
+     * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
820
+     */
821
+    public static function needUpgrade(\OC\SystemConfig $config) {
822
+        if ($config->getValue('installed', false)) {
823
+            $installedVersion = $config->getValue('version', '0.0.0');
824
+            $currentVersion = implode('.', \OCP\Util::getVersion());
825
+            $versionDiff = version_compare($currentVersion, $installedVersion);
826
+            if ($versionDiff > 0) {
827
+                return true;
828
+            } elseif ($config->getValue('debug', false) && $versionDiff < 0) {
829
+                // downgrade with debug
830
+                $installedMajor = explode('.', $installedVersion);
831
+                $installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
832
+                $currentMajor = explode('.', $currentVersion);
833
+                $currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
834
+                if ($installedMajor === $currentMajor) {
835
+                    // Same major, allow downgrade for developers
836
+                    return true;
837
+                } else {
838
+                    // downgrade attempt, throw exception
839
+                    throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
840
+                }
841
+            } elseif ($versionDiff < 0) {
842
+                // downgrade attempt, throw exception
843
+                throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
844
+            }
845
+
846
+            // also check for upgrades for apps (independently from the user)
847
+            $apps = \OC_App::getEnabledApps(false, true);
848
+            $shouldUpgrade = false;
849
+            foreach ($apps as $app) {
850
+                if (\OC_App::shouldUpgrade($app)) {
851
+                    $shouldUpgrade = true;
852
+                    break;
853
+                }
854
+            }
855
+            return $shouldUpgrade;
856
+        } else {
857
+            return false;
858
+        }
859
+    }
860 860
 }
Please login to merge, or discard this patch.
lib/private/User/User.php 2 patches
Indentation   +611 added lines, -611 removed lines patch added patch discarded remove patch
@@ -44,615 +44,615 @@
 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
-	/**
562
-	 * set the users' quota
563
-	 *
564
-	 * @param string $quota
565
-	 * @return void
566
-	 * @throws InvalidArgumentException
567
-	 * @since 9.0.0
568
-	 */
569
-	public function setQuota($quota) {
570
-		$oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
571
-		if ($quota !== 'none' and $quota !== 'default') {
572
-			$bytesQuota = \OCP\Util::computerFileSize($quota);
573
-			if ($bytesQuota === false) {
574
-				throw new InvalidArgumentException('Failed to set quota to invalid value ' . $quota);
575
-			}
576
-			$quota = \OCP\Util::humanFileSize($bytesQuota);
577
-		}
578
-		if ($quota !== $oldQuota) {
579
-			$this->config->setUserValue($this->uid, 'files', 'quota', $quota);
580
-			$this->triggerChange('quota', $quota, $oldQuota);
581
-		}
582
-		\OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
583
-	}
584
-
585
-	public function getManagerUids(): array {
586
-		$encodedUids = $this->config->getUserValue(
587
-			$this->uid,
588
-			'settings',
589
-			self::CONFIG_KEY_MANAGERS,
590
-			'[]'
591
-		);
592
-		return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
593
-	}
594
-
595
-	public function setManagerUids(array $uids): void {
596
-		$oldUids = $this->getManagerUids();
597
-		$this->config->setUserValue(
598
-			$this->uid,
599
-			'settings',
600
-			self::CONFIG_KEY_MANAGERS,
601
-			json_encode($uids, JSON_THROW_ON_ERROR)
602
-		);
603
-		$this->triggerChange('managers', $uids, $oldUids);
604
-	}
605
-
606
-	/**
607
-	 * get the avatar image if it exists
608
-	 *
609
-	 * @param int $size
610
-	 * @return IImage|null
611
-	 * @since 9.0.0
612
-	 */
613
-	public function getAvatarImage($size) {
614
-		// delay the initialization
615
-		if (is_null($this->avatarManager)) {
616
-			$this->avatarManager = \OC::$server->get(IAvatarManager::class);
617
-		}
618
-
619
-		$avatar = $this->avatarManager->getAvatar($this->uid);
620
-		$image = $avatar->get($size);
621
-		if ($image) {
622
-			return $image;
623
-		}
624
-
625
-		return null;
626
-	}
627
-
628
-	/**
629
-	 * get the federation cloud id
630
-	 *
631
-	 * @return string
632
-	 * @since 9.0.0
633
-	 */
634
-	public function getCloudId() {
635
-		$uid = $this->getUID();
636
-		$server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
637
-		if (str_ends_with($server, '/index.php')) {
638
-			$server = substr($server, 0, -10);
639
-		}
640
-		$server = $this->removeProtocolFromUrl($server);
641
-		return $uid . '@' . $server;
642
-	}
643
-
644
-	private function removeProtocolFromUrl(string $url): string {
645
-		if (str_starts_with($url, 'https://')) {
646
-			return substr($url, strlen('https://'));
647
-		}
648
-
649
-		return $url;
650
-	}
651
-
652
-	public function triggerChange($feature, $value = null, $oldValue = null) {
653
-		$this->dispatcher->dispatchTyped(new UserChangedEvent($this, $feature, $value, $oldValue));
654
-		if ($this->emitter) {
655
-			$this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
656
-		}
657
-	}
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
+    /**
562
+     * set the users' quota
563
+     *
564
+     * @param string $quota
565
+     * @return void
566
+     * @throws InvalidArgumentException
567
+     * @since 9.0.0
568
+     */
569
+    public function setQuota($quota) {
570
+        $oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
571
+        if ($quota !== 'none' and $quota !== 'default') {
572
+            $bytesQuota = \OCP\Util::computerFileSize($quota);
573
+            if ($bytesQuota === false) {
574
+                throw new InvalidArgumentException('Failed to set quota to invalid value ' . $quota);
575
+            }
576
+            $quota = \OCP\Util::humanFileSize($bytesQuota);
577
+        }
578
+        if ($quota !== $oldQuota) {
579
+            $this->config->setUserValue($this->uid, 'files', 'quota', $quota);
580
+            $this->triggerChange('quota', $quota, $oldQuota);
581
+        }
582
+        \OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
583
+    }
584
+
585
+    public function getManagerUids(): array {
586
+        $encodedUids = $this->config->getUserValue(
587
+            $this->uid,
588
+            'settings',
589
+            self::CONFIG_KEY_MANAGERS,
590
+            '[]'
591
+        );
592
+        return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
593
+    }
594
+
595
+    public function setManagerUids(array $uids): void {
596
+        $oldUids = $this->getManagerUids();
597
+        $this->config->setUserValue(
598
+            $this->uid,
599
+            'settings',
600
+            self::CONFIG_KEY_MANAGERS,
601
+            json_encode($uids, JSON_THROW_ON_ERROR)
602
+        );
603
+        $this->triggerChange('managers', $uids, $oldUids);
604
+    }
605
+
606
+    /**
607
+     * get the avatar image if it exists
608
+     *
609
+     * @param int $size
610
+     * @return IImage|null
611
+     * @since 9.0.0
612
+     */
613
+    public function getAvatarImage($size) {
614
+        // delay the initialization
615
+        if (is_null($this->avatarManager)) {
616
+            $this->avatarManager = \OC::$server->get(IAvatarManager::class);
617
+        }
618
+
619
+        $avatar = $this->avatarManager->getAvatar($this->uid);
620
+        $image = $avatar->get($size);
621
+        if ($image) {
622
+            return $image;
623
+        }
624
+
625
+        return null;
626
+    }
627
+
628
+    /**
629
+     * get the federation cloud id
630
+     *
631
+     * @return string
632
+     * @since 9.0.0
633
+     */
634
+    public function getCloudId() {
635
+        $uid = $this->getUID();
636
+        $server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
637
+        if (str_ends_with($server, '/index.php')) {
638
+            $server = substr($server, 0, -10);
639
+        }
640
+        $server = $this->removeProtocolFromUrl($server);
641
+        return $uid . '@' . $server;
642
+    }
643
+
644
+    private function removeProtocolFromUrl(string $url): string {
645
+        if (str_starts_with($url, 'https://')) {
646
+            return substr($url, strlen('https://'));
647
+        }
648
+
649
+        return $url;
650
+    }
651
+
652
+    public function triggerChange($feature, $value = null, $oldValue = null) {
653
+        $this->dispatcher->dispatchTyped(new UserChangedEvent($this, $feature, $value, $oldValue));
654
+        if ($this->emitter) {
655
+            $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
656
+        }
657
+    }
658 658
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 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';
@@ -571,7 +571,7 @@  discard block
 block discarded – undo
571 571
 		if ($quota !== 'none' and $quota !== 'default') {
572 572
 			$bytesQuota = \OCP\Util::computerFileSize($quota);
573 573
 			if ($bytesQuota === false) {
574
-				throw new InvalidArgumentException('Failed to set quota to invalid value ' . $quota);
574
+				throw new InvalidArgumentException('Failed to set quota to invalid value '.$quota);
575 575
 			}
576 576
 			$quota = \OCP\Util::humanFileSize($bytesQuota);
577 577
 		}
@@ -579,7 +579,7 @@  discard block
 block discarded – undo
579 579
 			$this->config->setUserValue($this->uid, 'files', 'quota', $quota);
580 580
 			$this->triggerChange('quota', $quota, $oldQuota);
581 581
 		}
582
-		\OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
582
+		\OC_Helper::clearStorageInfo('/'.$this->uid.'/files');
583 583
 	}
584 584
 
585 585
 	public function getManagerUids(): array {
@@ -638,7 +638,7 @@  discard block
 block discarded – undo
638 638
 			$server = substr($server, 0, -10);
639 639
 		}
640 640
 		$server = $this->removeProtocolFromUrl($server);
641
-		return $uid . '@' . $server;
641
+		return $uid.'@'.$server;
642 642
 	}
643 643
 
644 644
 	private function removeProtocolFromUrl(string $url): string {
Please login to merge, or discard this patch.
apps/settings/lib/Settings/Personal/PersonalInfo.php 1 patch
Indentation   +283 added lines, -283 removed lines patch added patch discarded remove patch
@@ -32,287 +32,287 @@
 block discarded – undo
32 32
 
33 33
 class PersonalInfo implements ISettings {
34 34
 
35
-	/** @var ProfileManager */
36
-	private $profileManager;
37
-
38
-	public function __construct(
39
-		private IConfig $config,
40
-		private IUserManager $userManager,
41
-		private IGroupManager $groupManager,
42
-		private IAccountManager $accountManager,
43
-		ProfileManager $profileManager,
44
-		private IAppManager $appManager,
45
-		private IFactory $l10nFactory,
46
-		private IL10N $l,
47
-		private IInitialState $initialStateService,
48
-		private IManager $manager,
49
-	) {
50
-		$this->profileManager = $profileManager;
51
-	}
52
-
53
-	public function getForm(): TemplateResponse {
54
-		$federationEnabled = $this->appManager->isEnabledForUser('federation');
55
-		$federatedFileSharingEnabled = $this->appManager->isEnabledForUser('federatedfilesharing');
56
-		$lookupServerUploadEnabled = false;
57
-		if ($federatedFileSharingEnabled) {
58
-			/** @var FederatedShareProvider $shareProvider */
59
-			$shareProvider = Server::get(FederatedShareProvider::class);
60
-			$lookupServerUploadEnabled = $shareProvider->isLookupServerUploadEnabled();
61
-		}
62
-
63
-		$uid = \OC_User::getUser();
64
-		$user = $this->userManager->get($uid);
65
-		$account = $this->accountManager->getAccount($user);
66
-
67
-		// make sure FS is setup before querying storage related stuff...
68
-		\OC_Util::setupFS($user->getUID());
69
-
70
-		$storageInfo = \OC_Helper::getStorageInfo('/');
71
-		if ($storageInfo['quota'] === FileInfo::SPACE_UNLIMITED) {
72
-			$totalSpace = $this->l->t('Unlimited');
73
-		} else {
74
-			$totalSpace = \OCP\Util::humanFileSize($storageInfo['total']);
75
-		}
76
-
77
-		$messageParameters = $this->getMessageParameters($account);
78
-
79
-		$parameters = [
80
-			'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
81
-			'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
82
-			'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
83
-		] + $messageParameters;
84
-
85
-		$personalInfoParameters = [
86
-			'userId' => $uid,
87
-			'avatar' => $this->getProperty($account, IAccountManager::PROPERTY_AVATAR),
88
-			'groups' => $this->getGroups($user),
89
-			'quota' => $storageInfo['quota'],
90
-			'totalSpace' => $totalSpace,
91
-			'usage' => \OCP\Util::humanFileSize($storageInfo['used']),
92
-			'usageRelative' => round($storageInfo['relative']),
93
-			'displayName' => $this->getProperty($account, IAccountManager::PROPERTY_DISPLAYNAME),
94
-			'emailMap' => $this->getEmailMap($account),
95
-			'phone' => $this->getProperty($account, IAccountManager::PROPERTY_PHONE),
96
-			'defaultPhoneRegion' => $this->config->getSystemValueString('default_phone_region'),
97
-			'location' => $this->getProperty($account, IAccountManager::PROPERTY_ADDRESS),
98
-			'website' => $this->getProperty($account, IAccountManager::PROPERTY_WEBSITE),
99
-			'twitter' => $this->getProperty($account, IAccountManager::PROPERTY_TWITTER),
100
-			'fediverse' => $this->getProperty($account, IAccountManager::PROPERTY_FEDIVERSE),
101
-			'languageMap' => $this->getLanguageMap($user),
102
-			'localeMap' => $this->getLocaleMap($user),
103
-			'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
104
-			'profileEnabled' => $this->profileManager->isProfileEnabled($user),
105
-			'organisation' => $this->getProperty($account, IAccountManager::PROPERTY_ORGANISATION),
106
-			'role' => $this->getProperty($account, IAccountManager::PROPERTY_ROLE),
107
-			'headline' => $this->getProperty($account, IAccountManager::PROPERTY_HEADLINE),
108
-			'biography' => $this->getProperty($account, IAccountManager::PROPERTY_BIOGRAPHY),
109
-			'birthdate' => $this->getProperty($account, IAccountManager::PROPERTY_BIRTHDATE),
110
-			'firstDayOfWeek' => $this->config->getUserValue($uid, 'core', AUserDataOCSController::USER_FIELD_FIRST_DAY_OF_WEEK),
111
-			'pronouns' => $this->getProperty($account, IAccountManager::PROPERTY_PRONOUNS),
112
-		];
113
-
114
-		$accountParameters = [
115
-			'avatarChangeSupported' => $user->canChangeAvatar(),
116
-			'displayNameChangeSupported' => $user->canChangeDisplayName(),
117
-			'emailChangeSupported' => $user->canChangeEmail(),
118
-			'federationEnabled' => $federationEnabled,
119
-			'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
120
-		];
121
-
122
-		$profileParameters = [
123
-			'profileConfig' => $this->profileManager->getProfileConfigWithMetadata($user, $user),
124
-		];
125
-
126
-		$this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled());
127
-		$this->initialStateService->provideInitialState('personalInfoParameters', $personalInfoParameters);
128
-		$this->initialStateService->provideInitialState('accountParameters', $accountParameters);
129
-		$this->initialStateService->provideInitialState('profileParameters', $profileParameters);
130
-
131
-		return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, '');
132
-	}
133
-
134
-	/**
135
-	 * Check if is fair use of free push service
136
-	 * @return boolean
137
-	 */
138
-	private function isFairUseOfFreePushService(): bool {
139
-		return $this->manager->isFairUseOfFreePushService();
140
-	}
141
-
142
-	/**
143
-	 * returns the property data in an
144
-	 * associative array
145
-	 */
146
-	private function getProperty(IAccount $account, string $property): array {
147
-		$property = [
148
-			'name' => $account->getProperty($property)->getName(),
149
-			'value' => $account->getProperty($property)->getValue(),
150
-			'scope' => $account->getProperty($property)->getScope(),
151
-			'verified' => $account->getProperty($property)->getVerified(),
152
-		];
153
-
154
-		return $property;
155
-	}
156
-
157
-	/**
158
-	 * returns the section ID string, e.g. 'sharing'
159
-	 * @since 9.1
160
-	 */
161
-	public function getSection(): string {
162
-		return 'personal-info';
163
-	}
164
-
165
-	/**
166
-	 * @return int whether the form should be rather on the top or bottom of
167
-	 *             the admin section. The forms are arranged in ascending order of the
168
-	 *             priority values. It is required to return a value between 0 and 100.
169
-	 *
170
-	 * E.g.: 70
171
-	 * @since 9.1
172
-	 */
173
-	public function getPriority(): int {
174
-		return 10;
175
-	}
176
-
177
-	/**
178
-	 * returns a sorted list of the user's group GIDs
179
-	 */
180
-	private function getGroups(IUser $user): array {
181
-		$groups = array_map(
182
-			static function (IGroup $group) {
183
-				return $group->getDisplayName();
184
-			},
185
-			$this->groupManager->getUserGroups($user)
186
-		);
187
-		sort($groups);
188
-
189
-		return $groups;
190
-	}
191
-
192
-	/**
193
-	 * returns the primary email and additional emails in an
194
-	 * associative array
195
-	 */
196
-	private function getEmailMap(IAccount $account): array {
197
-		$systemEmail = [
198
-			'name' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getName(),
199
-			'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
200
-			'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
201
-			'verified' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(),
202
-		];
203
-
204
-		$additionalEmails = array_map(
205
-			function (IAccountProperty $property) {
206
-				return [
207
-					'name' => $property->getName(),
208
-					'value' => $property->getValue(),
209
-					'scope' => $property->getScope(),
210
-					'verified' => $property->getVerified(),
211
-					'locallyVerified' => $property->getLocallyVerified(),
212
-				];
213
-			},
214
-			$account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties(),
215
-		);
216
-
217
-		$emailMap = [
218
-			'primaryEmail' => $systemEmail,
219
-			'additionalEmails' => $additionalEmails,
220
-			'notificationEmail' => (string)$account->getUser()->getPrimaryEMailAddress(),
221
-		];
222
-
223
-		return $emailMap;
224
-	}
225
-
226
-	/**
227
-	 * returns the user's active language, common languages, and other languages in an
228
-	 * associative array
229
-	 */
230
-	private function getLanguageMap(IUser $user): array {
231
-		$forceLanguage = $this->config->getSystemValue('force_language', false);
232
-		if ($forceLanguage !== false) {
233
-			return [];
234
-		}
235
-
236
-		$uid = $user->getUID();
237
-
238
-		$userConfLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
239
-		$languages = $this->l10nFactory->getLanguages();
240
-
241
-		// associate the user language with the proper array
242
-		$userLangIndex = array_search($userConfLang, array_column($languages['commonLanguages'], 'code'));
243
-		$userLang = $languages['commonLanguages'][$userLangIndex];
244
-		// search in the other languages
245
-		if ($userLangIndex === false) {
246
-			$userLangIndex = array_search($userConfLang, array_column($languages['otherLanguages'], 'code'));
247
-			$userLang = $languages['otherLanguages'][$userLangIndex];
248
-		}
249
-		// if user language is not available but set somehow: show the actual code as name
250
-		if (!is_array($userLang)) {
251
-			$userLang = [
252
-				'code' => $userConfLang,
253
-				'name' => $userConfLang,
254
-			];
255
-		}
256
-
257
-		return array_merge(
258
-			['activeLanguage' => $userLang],
259
-			$languages
260
-		);
261
-	}
262
-
263
-	private function getLocaleMap(IUser $user): array {
264
-		$forceLanguage = $this->config->getSystemValue('force_locale', false);
265
-		if ($forceLanguage !== false) {
266
-			return [];
267
-		}
268
-
269
-		$uid = $user->getUID();
270
-		$userLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
271
-		$userLocaleString = $this->config->getUserValue($uid, 'core', 'locale', $this->l10nFactory->findLocale($userLang));
272
-		$localeCodes = $this->l10nFactory->findAvailableLocales();
273
-		$userLocale = array_filter($localeCodes, fn ($value) => $userLocaleString === $value['code']);
274
-
275
-		if (!empty($userLocale)) {
276
-			$userLocale = reset($userLocale);
277
-		}
278
-
279
-		$localesForLanguage = array_values(array_filter($localeCodes, fn ($localeCode) => str_starts_with($localeCode['code'], $userLang)));
280
-		$otherLocales = array_values(array_filter($localeCodes, fn ($localeCode) => !str_starts_with($localeCode['code'], $userLang)));
281
-
282
-		if (!$userLocale) {
283
-			$userLocale = [
284
-				'code' => 'en',
285
-				'name' => 'English'
286
-			];
287
-		}
288
-
289
-		return [
290
-			'activeLocaleLang' => $userLocaleString,
291
-			'activeLocale' => $userLocale,
292
-			'localesForLanguage' => $localesForLanguage,
293
-			'otherLocales' => $otherLocales,
294
-		];
295
-	}
296
-
297
-	/**
298
-	 * returns the message parameters
299
-	 */
300
-	private function getMessageParameters(IAccount $account): array {
301
-		$needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER];
302
-		$messageParameters = [];
303
-		foreach ($needVerifyMessage as $property) {
304
-			switch ($account->getProperty($property)->getVerified()) {
305
-				case IAccountManager::VERIFIED:
306
-					$message = $this->l->t('Verifying');
307
-					break;
308
-				case IAccountManager::VERIFICATION_IN_PROGRESS:
309
-					$message = $this->l->t('Verifying …');
310
-					break;
311
-				default:
312
-					$message = $this->l->t('Verify');
313
-			}
314
-			$messageParameters[$property . 'Message'] = $message;
315
-		}
316
-		return $messageParameters;
317
-	}
35
+    /** @var ProfileManager */
36
+    private $profileManager;
37
+
38
+    public function __construct(
39
+        private IConfig $config,
40
+        private IUserManager $userManager,
41
+        private IGroupManager $groupManager,
42
+        private IAccountManager $accountManager,
43
+        ProfileManager $profileManager,
44
+        private IAppManager $appManager,
45
+        private IFactory $l10nFactory,
46
+        private IL10N $l,
47
+        private IInitialState $initialStateService,
48
+        private IManager $manager,
49
+    ) {
50
+        $this->profileManager = $profileManager;
51
+    }
52
+
53
+    public function getForm(): TemplateResponse {
54
+        $federationEnabled = $this->appManager->isEnabledForUser('federation');
55
+        $federatedFileSharingEnabled = $this->appManager->isEnabledForUser('federatedfilesharing');
56
+        $lookupServerUploadEnabled = false;
57
+        if ($federatedFileSharingEnabled) {
58
+            /** @var FederatedShareProvider $shareProvider */
59
+            $shareProvider = Server::get(FederatedShareProvider::class);
60
+            $lookupServerUploadEnabled = $shareProvider->isLookupServerUploadEnabled();
61
+        }
62
+
63
+        $uid = \OC_User::getUser();
64
+        $user = $this->userManager->get($uid);
65
+        $account = $this->accountManager->getAccount($user);
66
+
67
+        // make sure FS is setup before querying storage related stuff...
68
+        \OC_Util::setupFS($user->getUID());
69
+
70
+        $storageInfo = \OC_Helper::getStorageInfo('/');
71
+        if ($storageInfo['quota'] === FileInfo::SPACE_UNLIMITED) {
72
+            $totalSpace = $this->l->t('Unlimited');
73
+        } else {
74
+            $totalSpace = \OCP\Util::humanFileSize($storageInfo['total']);
75
+        }
76
+
77
+        $messageParameters = $this->getMessageParameters($account);
78
+
79
+        $parameters = [
80
+            'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
81
+            'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
82
+            'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
83
+        ] + $messageParameters;
84
+
85
+        $personalInfoParameters = [
86
+            'userId' => $uid,
87
+            'avatar' => $this->getProperty($account, IAccountManager::PROPERTY_AVATAR),
88
+            'groups' => $this->getGroups($user),
89
+            'quota' => $storageInfo['quota'],
90
+            'totalSpace' => $totalSpace,
91
+            'usage' => \OCP\Util::humanFileSize($storageInfo['used']),
92
+            'usageRelative' => round($storageInfo['relative']),
93
+            'displayName' => $this->getProperty($account, IAccountManager::PROPERTY_DISPLAYNAME),
94
+            'emailMap' => $this->getEmailMap($account),
95
+            'phone' => $this->getProperty($account, IAccountManager::PROPERTY_PHONE),
96
+            'defaultPhoneRegion' => $this->config->getSystemValueString('default_phone_region'),
97
+            'location' => $this->getProperty($account, IAccountManager::PROPERTY_ADDRESS),
98
+            'website' => $this->getProperty($account, IAccountManager::PROPERTY_WEBSITE),
99
+            'twitter' => $this->getProperty($account, IAccountManager::PROPERTY_TWITTER),
100
+            'fediverse' => $this->getProperty($account, IAccountManager::PROPERTY_FEDIVERSE),
101
+            'languageMap' => $this->getLanguageMap($user),
102
+            'localeMap' => $this->getLocaleMap($user),
103
+            'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
104
+            'profileEnabled' => $this->profileManager->isProfileEnabled($user),
105
+            'organisation' => $this->getProperty($account, IAccountManager::PROPERTY_ORGANISATION),
106
+            'role' => $this->getProperty($account, IAccountManager::PROPERTY_ROLE),
107
+            'headline' => $this->getProperty($account, IAccountManager::PROPERTY_HEADLINE),
108
+            'biography' => $this->getProperty($account, IAccountManager::PROPERTY_BIOGRAPHY),
109
+            'birthdate' => $this->getProperty($account, IAccountManager::PROPERTY_BIRTHDATE),
110
+            'firstDayOfWeek' => $this->config->getUserValue($uid, 'core', AUserDataOCSController::USER_FIELD_FIRST_DAY_OF_WEEK),
111
+            'pronouns' => $this->getProperty($account, IAccountManager::PROPERTY_PRONOUNS),
112
+        ];
113
+
114
+        $accountParameters = [
115
+            'avatarChangeSupported' => $user->canChangeAvatar(),
116
+            'displayNameChangeSupported' => $user->canChangeDisplayName(),
117
+            'emailChangeSupported' => $user->canChangeEmail(),
118
+            'federationEnabled' => $federationEnabled,
119
+            'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
120
+        ];
121
+
122
+        $profileParameters = [
123
+            'profileConfig' => $this->profileManager->getProfileConfigWithMetadata($user, $user),
124
+        ];
125
+
126
+        $this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled());
127
+        $this->initialStateService->provideInitialState('personalInfoParameters', $personalInfoParameters);
128
+        $this->initialStateService->provideInitialState('accountParameters', $accountParameters);
129
+        $this->initialStateService->provideInitialState('profileParameters', $profileParameters);
130
+
131
+        return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, '');
132
+    }
133
+
134
+    /**
135
+     * Check if is fair use of free push service
136
+     * @return boolean
137
+     */
138
+    private function isFairUseOfFreePushService(): bool {
139
+        return $this->manager->isFairUseOfFreePushService();
140
+    }
141
+
142
+    /**
143
+     * returns the property data in an
144
+     * associative array
145
+     */
146
+    private function getProperty(IAccount $account, string $property): array {
147
+        $property = [
148
+            'name' => $account->getProperty($property)->getName(),
149
+            'value' => $account->getProperty($property)->getValue(),
150
+            'scope' => $account->getProperty($property)->getScope(),
151
+            'verified' => $account->getProperty($property)->getVerified(),
152
+        ];
153
+
154
+        return $property;
155
+    }
156
+
157
+    /**
158
+     * returns the section ID string, e.g. 'sharing'
159
+     * @since 9.1
160
+     */
161
+    public function getSection(): string {
162
+        return 'personal-info';
163
+    }
164
+
165
+    /**
166
+     * @return int whether the form should be rather on the top or bottom of
167
+     *             the admin section. The forms are arranged in ascending order of the
168
+     *             priority values. It is required to return a value between 0 and 100.
169
+     *
170
+     * E.g.: 70
171
+     * @since 9.1
172
+     */
173
+    public function getPriority(): int {
174
+        return 10;
175
+    }
176
+
177
+    /**
178
+     * returns a sorted list of the user's group GIDs
179
+     */
180
+    private function getGroups(IUser $user): array {
181
+        $groups = array_map(
182
+            static function (IGroup $group) {
183
+                return $group->getDisplayName();
184
+            },
185
+            $this->groupManager->getUserGroups($user)
186
+        );
187
+        sort($groups);
188
+
189
+        return $groups;
190
+    }
191
+
192
+    /**
193
+     * returns the primary email and additional emails in an
194
+     * associative array
195
+     */
196
+    private function getEmailMap(IAccount $account): array {
197
+        $systemEmail = [
198
+            'name' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getName(),
199
+            'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
200
+            'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
201
+            'verified' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(),
202
+        ];
203
+
204
+        $additionalEmails = array_map(
205
+            function (IAccountProperty $property) {
206
+                return [
207
+                    'name' => $property->getName(),
208
+                    'value' => $property->getValue(),
209
+                    'scope' => $property->getScope(),
210
+                    'verified' => $property->getVerified(),
211
+                    'locallyVerified' => $property->getLocallyVerified(),
212
+                ];
213
+            },
214
+            $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties(),
215
+        );
216
+
217
+        $emailMap = [
218
+            'primaryEmail' => $systemEmail,
219
+            'additionalEmails' => $additionalEmails,
220
+            'notificationEmail' => (string)$account->getUser()->getPrimaryEMailAddress(),
221
+        ];
222
+
223
+        return $emailMap;
224
+    }
225
+
226
+    /**
227
+     * returns the user's active language, common languages, and other languages in an
228
+     * associative array
229
+     */
230
+    private function getLanguageMap(IUser $user): array {
231
+        $forceLanguage = $this->config->getSystemValue('force_language', false);
232
+        if ($forceLanguage !== false) {
233
+            return [];
234
+        }
235
+
236
+        $uid = $user->getUID();
237
+
238
+        $userConfLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
239
+        $languages = $this->l10nFactory->getLanguages();
240
+
241
+        // associate the user language with the proper array
242
+        $userLangIndex = array_search($userConfLang, array_column($languages['commonLanguages'], 'code'));
243
+        $userLang = $languages['commonLanguages'][$userLangIndex];
244
+        // search in the other languages
245
+        if ($userLangIndex === false) {
246
+            $userLangIndex = array_search($userConfLang, array_column($languages['otherLanguages'], 'code'));
247
+            $userLang = $languages['otherLanguages'][$userLangIndex];
248
+        }
249
+        // if user language is not available but set somehow: show the actual code as name
250
+        if (!is_array($userLang)) {
251
+            $userLang = [
252
+                'code' => $userConfLang,
253
+                'name' => $userConfLang,
254
+            ];
255
+        }
256
+
257
+        return array_merge(
258
+            ['activeLanguage' => $userLang],
259
+            $languages
260
+        );
261
+    }
262
+
263
+    private function getLocaleMap(IUser $user): array {
264
+        $forceLanguage = $this->config->getSystemValue('force_locale', false);
265
+        if ($forceLanguage !== false) {
266
+            return [];
267
+        }
268
+
269
+        $uid = $user->getUID();
270
+        $userLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
271
+        $userLocaleString = $this->config->getUserValue($uid, 'core', 'locale', $this->l10nFactory->findLocale($userLang));
272
+        $localeCodes = $this->l10nFactory->findAvailableLocales();
273
+        $userLocale = array_filter($localeCodes, fn ($value) => $userLocaleString === $value['code']);
274
+
275
+        if (!empty($userLocale)) {
276
+            $userLocale = reset($userLocale);
277
+        }
278
+
279
+        $localesForLanguage = array_values(array_filter($localeCodes, fn ($localeCode) => str_starts_with($localeCode['code'], $userLang)));
280
+        $otherLocales = array_values(array_filter($localeCodes, fn ($localeCode) => !str_starts_with($localeCode['code'], $userLang)));
281
+
282
+        if (!$userLocale) {
283
+            $userLocale = [
284
+                'code' => 'en',
285
+                'name' => 'English'
286
+            ];
287
+        }
288
+
289
+        return [
290
+            'activeLocaleLang' => $userLocaleString,
291
+            'activeLocale' => $userLocale,
292
+            'localesForLanguage' => $localesForLanguage,
293
+            'otherLocales' => $otherLocales,
294
+        ];
295
+    }
296
+
297
+    /**
298
+     * returns the message parameters
299
+     */
300
+    private function getMessageParameters(IAccount $account): array {
301
+        $needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER];
302
+        $messageParameters = [];
303
+        foreach ($needVerifyMessage as $property) {
304
+            switch ($account->getProperty($property)->getVerified()) {
305
+                case IAccountManager::VERIFIED:
306
+                    $message = $this->l->t('Verifying');
307
+                    break;
308
+                case IAccountManager::VERIFICATION_IN_PROGRESS:
309
+                    $message = $this->l->t('Verifying …');
310
+                    break;
311
+                default:
312
+                    $message = $this->l->t('Verify');
313
+            }
314
+            $messageParameters[$property . 'Message'] = $message;
315
+        }
316
+        return $messageParameters;
317
+    }
318 318
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Command/Size.php 2 patches
Indentation   +92 added lines, -92 removed lines patch added patch discarded remove patch
@@ -19,105 +19,105 @@
 block discarded – undo
19 19
 use Symfony\Component\Console\Output\OutputInterface;
20 20
 
21 21
 class Size extends Base {
22
-	public function __construct(
23
-		private IConfig $config,
24
-		private IUserManager $userManager,
25
-		private IBus $commandBus,
26
-	) {
27
-		parent::__construct();
28
-	}
22
+    public function __construct(
23
+        private IConfig $config,
24
+        private IUserManager $userManager,
25
+        private IBus $commandBus,
26
+    ) {
27
+        parent::__construct();
28
+    }
29 29
 
30
-	protected function configure() {
31
-		parent::configure();
32
-		$this
33
-			->setName('trashbin:size')
34
-			->setDescription('Configure the target trashbin size')
35
-			->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'configure the target size for the provided user, if no user is given the default size is configured')
36
-			->addArgument(
37
-				'size',
38
-				InputArgument::OPTIONAL,
39
-				'the target size for the trashbin, if not provided the current trashbin size will be returned'
40
-			);
41
-	}
30
+    protected function configure() {
31
+        parent::configure();
32
+        $this
33
+            ->setName('trashbin:size')
34
+            ->setDescription('Configure the target trashbin size')
35
+            ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'configure the target size for the provided user, if no user is given the default size is configured')
36
+            ->addArgument(
37
+                'size',
38
+                InputArgument::OPTIONAL,
39
+                'the target size for the trashbin, if not provided the current trashbin size will be returned'
40
+            );
41
+    }
42 42
 
43
-	protected function execute(InputInterface $input, OutputInterface $output): int {
44
-		$user = $input->getOption('user');
45
-		$size = $input->getArgument('size');
43
+    protected function execute(InputInterface $input, OutputInterface $output): int {
44
+        $user = $input->getOption('user');
45
+        $size = $input->getArgument('size');
46 46
 
47
-		if ($size) {
48
-			$parsedSize = \OCP\Util::computerFileSize($size);
49
-			if ($parsedSize === false) {
50
-				$output->writeln('<error>Failed to parse input size</error>');
51
-				return -1;
52
-			}
53
-			if ($user) {
54
-				$this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize);
55
-				$this->commandBus->push(new Expire($user));
56
-			} else {
57
-				$this->config->setAppValue('files_trashbin', 'trashbin_size', (string)$parsedSize);
58
-				$output->writeln('<info>Warning: changing the default trashbin size will automatically trigger cleanup of existing trashbins,</info>');
59
-				$output->writeln('<info>a users trashbin can exceed the configured size until they move a new file to the trashbin.</info>');
60
-			}
61
-		} else {
62
-			$this->printTrashbinSize($input, $output, $user);
63
-		}
47
+        if ($size) {
48
+            $parsedSize = \OCP\Util::computerFileSize($size);
49
+            if ($parsedSize === false) {
50
+                $output->writeln('<error>Failed to parse input size</error>');
51
+                return -1;
52
+            }
53
+            if ($user) {
54
+                $this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize);
55
+                $this->commandBus->push(new Expire($user));
56
+            } else {
57
+                $this->config->setAppValue('files_trashbin', 'trashbin_size', (string)$parsedSize);
58
+                $output->writeln('<info>Warning: changing the default trashbin size will automatically trigger cleanup of existing trashbins,</info>');
59
+                $output->writeln('<info>a users trashbin can exceed the configured size until they move a new file to the trashbin.</info>');
60
+            }
61
+        } else {
62
+            $this->printTrashbinSize($input, $output, $user);
63
+        }
64 64
 
65
-		return 0;
66
-	}
65
+        return 0;
66
+    }
67 67
 
68
-	private function printTrashbinSize(InputInterface $input, OutputInterface $output, ?string $user) {
69
-		$globalSize = (int)$this->config->getAppValue('files_trashbin', 'trashbin_size', '-1');
70
-		if ($globalSize < 0) {
71
-			$globalHumanSize = 'default (50% of available space)';
72
-		} else {
73
-			$globalHumanSize = \OCP\Util::humanFileSize($globalSize);
74
-		}
68
+    private function printTrashbinSize(InputInterface $input, OutputInterface $output, ?string $user) {
69
+        $globalSize = (int)$this->config->getAppValue('files_trashbin', 'trashbin_size', '-1');
70
+        if ($globalSize < 0) {
71
+            $globalHumanSize = 'default (50% of available space)';
72
+        } else {
73
+            $globalHumanSize = \OCP\Util::humanFileSize($globalSize);
74
+        }
75 75
 
76
-		if ($user) {
77
-			$userSize = (int)$this->config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
76
+        if ($user) {
77
+            $userSize = (int)$this->config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
78 78
 
79
-			if ($userSize < 0) {
80
-				$userHumanSize = ($globalSize < 0) ? $globalHumanSize : "default($globalHumanSize)";
81
-			} else {
82
-				$userHumanSize = \OCP\Util::humanFileSize($userSize);
83
-			}
79
+            if ($userSize < 0) {
80
+                $userHumanSize = ($globalSize < 0) ? $globalHumanSize : "default($globalHumanSize)";
81
+            } else {
82
+                $userHumanSize = \OCP\Util::humanFileSize($userSize);
83
+            }
84 84
 
85
-			if ($input->getOption('output') == self::OUTPUT_FORMAT_PLAIN) {
86
-				$output->writeln($userHumanSize);
87
-			} else {
88
-				$userValue = ($userSize < 0) ? 'default' : $userSize;
89
-				$globalValue = ($globalSize < 0) ? 'default' : $globalSize;
90
-				$this->writeArrayInOutputFormat($input, $output, [
91
-					'user_size' => $userValue,
92
-					'global_size' => $globalValue,
93
-					'effective_size' => ($userSize < 0) ? $globalValue : $userValue,
94
-				]);
95
-			}
96
-		} else {
97
-			$users = [];
98
-			$this->userManager->callForSeenUsers(function (IUser $user) use (&$users): void {
99
-				$users[] = $user->getUID();
100
-			});
101
-			$userValues = $this->config->getUserValueForUsers('files_trashbin', 'trashbin_size', $users);
85
+            if ($input->getOption('output') == self::OUTPUT_FORMAT_PLAIN) {
86
+                $output->writeln($userHumanSize);
87
+            } else {
88
+                $userValue = ($userSize < 0) ? 'default' : $userSize;
89
+                $globalValue = ($globalSize < 0) ? 'default' : $globalSize;
90
+                $this->writeArrayInOutputFormat($input, $output, [
91
+                    'user_size' => $userValue,
92
+                    'global_size' => $globalValue,
93
+                    'effective_size' => ($userSize < 0) ? $globalValue : $userValue,
94
+                ]);
95
+            }
96
+        } else {
97
+            $users = [];
98
+            $this->userManager->callForSeenUsers(function (IUser $user) use (&$users): void {
99
+                $users[] = $user->getUID();
100
+            });
101
+            $userValues = $this->config->getUserValueForUsers('files_trashbin', 'trashbin_size', $users);
102 102
 
103
-			if ($input->getOption('output') == self::OUTPUT_FORMAT_PLAIN) {
104
-				$output->writeln("Default size: $globalHumanSize");
105
-				$output->writeln('');
106
-				if (count($userValues)) {
107
-					$output->writeln('Per-user sizes:');
108
-					$this->writeArrayInOutputFormat($input, $output, array_map(function ($size) {
109
-						return \OCP\Util::humanFileSize($size);
110
-					}, $userValues));
111
-				} else {
112
-					$output->writeln('No per-user sizes configured');
113
-				}
114
-			} else {
115
-				$globalValue = ($globalSize < 0) ? 'default' : $globalSize;
116
-				$this->writeArrayInOutputFormat($input, $output, [
117
-					'global_size' => $globalValue,
118
-					'user_sizes' => $userValues,
119
-				]);
120
-			}
121
-		}
122
-	}
103
+            if ($input->getOption('output') == self::OUTPUT_FORMAT_PLAIN) {
104
+                $output->writeln("Default size: $globalHumanSize");
105
+                $output->writeln('');
106
+                if (count($userValues)) {
107
+                    $output->writeln('Per-user sizes:');
108
+                    $this->writeArrayInOutputFormat($input, $output, array_map(function ($size) {
109
+                        return \OCP\Util::humanFileSize($size);
110
+                    }, $userValues));
111
+                } else {
112
+                    $output->writeln('No per-user sizes configured');
113
+                }
114
+            } else {
115
+                $globalValue = ($globalSize < 0) ? 'default' : $globalSize;
116
+                $this->writeArrayInOutputFormat($input, $output, [
117
+                    'global_size' => $globalValue,
118
+                    'user_sizes' => $userValues,
119
+                ]);
120
+            }
121
+        }
122
+    }
123 123
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -51,10 +51,10 @@  discard block
 block discarded – undo
51 51
 				return -1;
52 52
 			}
53 53
 			if ($user) {
54
-				$this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string)$parsedSize);
54
+				$this->config->setUserValue($user, 'files_trashbin', 'trashbin_size', (string) $parsedSize);
55 55
 				$this->commandBus->push(new Expire($user));
56 56
 			} else {
57
-				$this->config->setAppValue('files_trashbin', 'trashbin_size', (string)$parsedSize);
57
+				$this->config->setAppValue('files_trashbin', 'trashbin_size', (string) $parsedSize);
58 58
 				$output->writeln('<info>Warning: changing the default trashbin size will automatically trigger cleanup of existing trashbins,</info>');
59 59
 				$output->writeln('<info>a users trashbin can exceed the configured size until they move a new file to the trashbin.</info>');
60 60
 			}
@@ -66,7 +66,7 @@  discard block
 block discarded – undo
66 66
 	}
67 67
 
68 68
 	private function printTrashbinSize(InputInterface $input, OutputInterface $output, ?string $user) {
69
-		$globalSize = (int)$this->config->getAppValue('files_trashbin', 'trashbin_size', '-1');
69
+		$globalSize = (int) $this->config->getAppValue('files_trashbin', 'trashbin_size', '-1');
70 70
 		if ($globalSize < 0) {
71 71
 			$globalHumanSize = 'default (50% of available space)';
72 72
 		} else {
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 		}
75 75
 
76 76
 		if ($user) {
77
-			$userSize = (int)$this->config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
77
+			$userSize = (int) $this->config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
78 78
 
79 79
 			if ($userSize < 0) {
80 80
 				$userHumanSize = ($globalSize < 0) ? $globalHumanSize : "default($globalHumanSize)";
@@ -95,7 +95,7 @@  discard block
 block discarded – undo
95 95
 			}
96 96
 		} else {
97 97
 			$users = [];
98
-			$this->userManager->callForSeenUsers(function (IUser $user) use (&$users): void {
98
+			$this->userManager->callForSeenUsers(function(IUser $user) use (&$users): void {
99 99
 				$users[] = $user->getUID();
100 100
 			});
101 101
 			$userValues = $this->config->getUserValueForUsers('files_trashbin', 'trashbin_size', $users);
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
 				$output->writeln('');
106 106
 				if (count($userValues)) {
107 107
 					$output->writeln('Per-user sizes:');
108
-					$this->writeArrayInOutputFormat($input, $output, array_map(function ($size) {
108
+					$this->writeArrayInOutputFormat($input, $output, array_map(function($size) {
109 109
 						return \OCP\Util::humanFileSize($size);
110 110
 					}, $userValues));
111 111
 				} else {
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Command/CleanUp.php 2 patches
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -20,98 +20,98 @@
 block discarded – undo
20 20
 
21 21
 class CleanUp extends Command {
22 22
 
23
-	public function __construct(
24
-		protected IRootFolder $rootFolder,
25
-		protected IUserManager $userManager,
26
-		protected IDBConnection $dbConnection,
27
-	) {
28
-		parent::__construct();
29
-	}
23
+    public function __construct(
24
+        protected IRootFolder $rootFolder,
25
+        protected IUserManager $userManager,
26
+        protected IDBConnection $dbConnection,
27
+    ) {
28
+        parent::__construct();
29
+    }
30 30
 
31
-	protected function configure() {
32
-		$this
33
-			->setName('trashbin:cleanup')
34
-			->setDescription('Remove deleted files')
35
-			->addArgument(
36
-				'user_id',
37
-				InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
38
-				'remove deleted files of the given user(s)'
39
-			)
40
-			->addOption(
41
-				'all-users',
42
-				null,
43
-				InputOption::VALUE_NONE,
44
-				'run action on all users'
45
-			);
46
-	}
31
+    protected function configure() {
32
+        $this
33
+            ->setName('trashbin:cleanup')
34
+            ->setDescription('Remove deleted files')
35
+            ->addArgument(
36
+                'user_id',
37
+                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
38
+                'remove deleted files of the given user(s)'
39
+            )
40
+            ->addOption(
41
+                'all-users',
42
+                null,
43
+                InputOption::VALUE_NONE,
44
+                'run action on all users'
45
+            );
46
+    }
47 47
 
48
-	protected function execute(InputInterface $input, OutputInterface $output): int {
49
-		$users = $input->getArgument('user_id');
50
-		$verbose = $input->getOption('verbose');
51
-		if ((!empty($users)) and ($input->getOption('all-users'))) {
52
-			throw new InvalidOptionException('Either specify a user_id or --all-users');
53
-		} elseif (!empty($users)) {
54
-			foreach ($users as $user) {
55
-				if ($this->userManager->userExists($user)) {
56
-					$output->writeln("Remove deleted files of   <info>$user</info>");
57
-					$this->removeDeletedFiles($user, $output, $verbose);
58
-				} else {
59
-					$output->writeln("<error>Unknown user $user</error>");
60
-					return 1;
61
-				}
62
-			}
63
-		} elseif ($input->getOption('all-users')) {
64
-			$output->writeln('Remove deleted files for all users');
65
-			foreach ($this->userManager->getBackends() as $backend) {
66
-				$name = get_class($backend);
67
-				if ($backend instanceof IUserBackend) {
68
-					$name = $backend->getBackendName();
69
-				}
70
-				$output->writeln("Remove deleted files for users on backend <info>$name</info>");
71
-				$limit = 500;
72
-				$offset = 0;
73
-				do {
74
-					$users = $backend->getUsers('', $limit, $offset);
75
-					foreach ($users as $user) {
76
-						$output->writeln("   <info>$user</info>");
77
-						$this->removeDeletedFiles($user, $output, $verbose);
78
-					}
79
-					$offset += $limit;
80
-				} while (count($users) >= $limit);
81
-			}
82
-		} else {
83
-			throw new InvalidOptionException('Either specify a user_id or --all-users');
84
-		}
85
-		return 0;
86
-	}
48
+    protected function execute(InputInterface $input, OutputInterface $output): int {
49
+        $users = $input->getArgument('user_id');
50
+        $verbose = $input->getOption('verbose');
51
+        if ((!empty($users)) and ($input->getOption('all-users'))) {
52
+            throw new InvalidOptionException('Either specify a user_id or --all-users');
53
+        } elseif (!empty($users)) {
54
+            foreach ($users as $user) {
55
+                if ($this->userManager->userExists($user)) {
56
+                    $output->writeln("Remove deleted files of   <info>$user</info>");
57
+                    $this->removeDeletedFiles($user, $output, $verbose);
58
+                } else {
59
+                    $output->writeln("<error>Unknown user $user</error>");
60
+                    return 1;
61
+                }
62
+            }
63
+        } elseif ($input->getOption('all-users')) {
64
+            $output->writeln('Remove deleted files for all users');
65
+            foreach ($this->userManager->getBackends() as $backend) {
66
+                $name = get_class($backend);
67
+                if ($backend instanceof IUserBackend) {
68
+                    $name = $backend->getBackendName();
69
+                }
70
+                $output->writeln("Remove deleted files for users on backend <info>$name</info>");
71
+                $limit = 500;
72
+                $offset = 0;
73
+                do {
74
+                    $users = $backend->getUsers('', $limit, $offset);
75
+                    foreach ($users as $user) {
76
+                        $output->writeln("   <info>$user</info>");
77
+                        $this->removeDeletedFiles($user, $output, $verbose);
78
+                    }
79
+                    $offset += $limit;
80
+                } while (count($users) >= $limit);
81
+            }
82
+        } else {
83
+            throw new InvalidOptionException('Either specify a user_id or --all-users');
84
+        }
85
+        return 0;
86
+    }
87 87
 
88
-	/**
89
-	 * remove deleted files for the given user
90
-	 */
91
-	protected function removeDeletedFiles(string $uid, OutputInterface $output, bool $verbose): void {
92
-		\OC_Util::tearDownFS();
93
-		\OC_Util::setupFS($uid);
94
-		$path = '/' . $uid . '/files_trashbin';
95
-		if ($this->rootFolder->nodeExists($path)) {
96
-			$node = $this->rootFolder->get($path);
88
+    /**
89
+     * remove deleted files for the given user
90
+     */
91
+    protected function removeDeletedFiles(string $uid, OutputInterface $output, bool $verbose): void {
92
+        \OC_Util::tearDownFS();
93
+        \OC_Util::setupFS($uid);
94
+        $path = '/' . $uid . '/files_trashbin';
95
+        if ($this->rootFolder->nodeExists($path)) {
96
+            $node = $this->rootFolder->get($path);
97 97
 
98
-			if ($verbose) {
99
-				$output->writeln('Deleting <info>' . \OCP\Util::humanFileSize($node->getSize()) . "</info> in trash for <info>$uid</info>.");
100
-			}
101
-			$node->delete();
102
-			if ($this->rootFolder->nodeExists($path)) {
103
-				$output->writeln('<error>Trash folder sill exists after attempting to delete it</error>');
104
-				return;
105
-			}
106
-			$query = $this->dbConnection->getQueryBuilder();
107
-			$query->delete('files_trash')
108
-				->where($query->expr()->eq('user', $query->createParameter('uid')))
109
-				->setParameter('uid', $uid);
110
-			$query->executeStatement();
111
-		} else {
112
-			if ($verbose) {
113
-				$output->writeln("No trash found for <info>$uid</info>");
114
-			}
115
-		}
116
-	}
98
+            if ($verbose) {
99
+                $output->writeln('Deleting <info>' . \OCP\Util::humanFileSize($node->getSize()) . "</info> in trash for <info>$uid</info>.");
100
+            }
101
+            $node->delete();
102
+            if ($this->rootFolder->nodeExists($path)) {
103
+                $output->writeln('<error>Trash folder sill exists after attempting to delete it</error>');
104
+                return;
105
+            }
106
+            $query = $this->dbConnection->getQueryBuilder();
107
+            $query->delete('files_trash')
108
+                ->where($query->expr()->eq('user', $query->createParameter('uid')))
109
+                ->setParameter('uid', $uid);
110
+            $query->executeStatement();
111
+        } else {
112
+            if ($verbose) {
113
+                $output->writeln("No trash found for <info>$uid</info>");
114
+            }
115
+        }
116
+    }
117 117
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -91,12 +91,12 @@
 block discarded – undo
91 91
 	protected function removeDeletedFiles(string $uid, OutputInterface $output, bool $verbose): void {
92 92
 		\OC_Util::tearDownFS();
93 93
 		\OC_Util::setupFS($uid);
94
-		$path = '/' . $uid . '/files_trashbin';
94
+		$path = '/'.$uid.'/files_trashbin';
95 95
 		if ($this->rootFolder->nodeExists($path)) {
96 96
 			$node = $this->rootFolder->get($path);
97 97
 
98 98
 			if ($verbose) {
99
-				$output->writeln('Deleting <info>' . \OCP\Util::humanFileSize($node->getSize()) . "</info> in trash for <info>$uid</info>.");
99
+				$output->writeln('Deleting <info>'.\OCP\Util::humanFileSize($node->getSize())."</info> in trash for <info>$uid</info>.");
100 100
 			}
101 101
 			$node->delete();
102 102
 			if ($this->rootFolder->nodeExists($path)) {
Please login to merge, or discard this patch.
apps/user_ldap/lib/User/User.php 1 patch
Indentation   +786 added lines, -786 removed lines patch added patch discarded remove patch
@@ -33,790 +33,790 @@
 block discarded – undo
33 33
  * represents an LDAP user, gets and holds user-specific information from LDAP
34 34
  */
35 35
 class User {
36
-	protected Connection $connection;
37
-	/**
38
-	 * @var array<string,1>
39
-	 */
40
-	protected array $refreshedFeatures = [];
41
-	protected string|false|null $avatarImage = null;
42
-
43
-	protected BirthdateParserService $birthdateParser;
44
-
45
-	/**
46
-	 * DB config keys for user preferences
47
-	 * @var string
48
-	 */
49
-	public const USER_PREFKEY_FIRSTLOGIN = 'firstLoginAccomplished';
50
-
51
-	/**
52
-	 * @brief constructor, make sure the subclasses call this one!
53
-	 */
54
-	public function __construct(
55
-		protected string $uid,
56
-		protected string $dn,
57
-		protected Access $access,
58
-		protected IConfig $config,
59
-		protected Image $image,
60
-		protected LoggerInterface $logger,
61
-		protected IAvatarManager $avatarManager,
62
-		protected IUserManager $userManager,
63
-		protected INotificationManager $notificationManager,
64
-	) {
65
-		if ($uid === '') {
66
-			$logger->error("uid for '$dn' must not be an empty string", ['app' => 'user_ldap']);
67
-			throw new \InvalidArgumentException('uid must not be an empty string!');
68
-		}
69
-		$this->connection = $this->access->getConnection();
70
-		$this->birthdateParser = new BirthdateParserService();
71
-
72
-		Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
73
-	}
74
-
75
-	/**
76
-	 * marks a user as deleted
77
-	 *
78
-	 * @throws PreConditionNotMetException
79
-	 */
80
-	public function markUser(): void {
81
-		$curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0');
82
-		if ($curValue === '1') {
83
-			// the user is already marked, do not write to DB again
84
-			return;
85
-		}
86
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1');
87
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time());
88
-	}
89
-
90
-	/**
91
-	 * processes results from LDAP for attributes as returned by getAttributesToRead()
92
-	 * @param array $ldapEntry the user entry as retrieved from LDAP
93
-	 */
94
-	public function processAttributes(array $ldapEntry): void {
95
-		//Quota
96
-		$attr = strtolower($this->connection->ldapQuotaAttribute);
97
-		if (isset($ldapEntry[$attr])) {
98
-			$this->updateQuota($ldapEntry[$attr][0]);
99
-		} else {
100
-			if ($this->connection->ldapQuotaDefault !== '') {
101
-				$this->updateQuota();
102
-			}
103
-		}
104
-		unset($attr);
105
-
106
-		//displayName
107
-		$displayName = $displayName2 = '';
108
-		$attr = strtolower($this->connection->ldapUserDisplayName);
109
-		if (isset($ldapEntry[$attr])) {
110
-			$displayName = (string)$ldapEntry[$attr][0];
111
-		}
112
-		$attr = strtolower($this->connection->ldapUserDisplayName2);
113
-		if (isset($ldapEntry[$attr])) {
114
-			$displayName2 = (string)$ldapEntry[$attr][0];
115
-		}
116
-		if ($displayName !== '') {
117
-			$this->composeAndStoreDisplayName($displayName, $displayName2);
118
-			$this->access->cacheUserDisplayName(
119
-				$this->getUsername(),
120
-				$displayName,
121
-				$displayName2
122
-			);
123
-		}
124
-		unset($attr);
125
-
126
-		//Email
127
-		//email must be stored after displayname, because it would cause a user
128
-		//change event that will trigger fetching the display name again
129
-		$attr = strtolower($this->connection->ldapEmailAttribute);
130
-		if (isset($ldapEntry[$attr])) {
131
-			$mailValue = 0;
132
-			for ($x = 0; $x < count($ldapEntry[$attr]); $x++) {
133
-				if (filter_var($ldapEntry[$attr][$x], FILTER_VALIDATE_EMAIL)) {
134
-					$mailValue = $x;
135
-					break;
136
-				}
137
-			}
138
-			$this->updateEmail($ldapEntry[$attr][$mailValue]);
139
-		}
140
-		unset($attr);
141
-
142
-		// LDAP Username, needed for s2s sharing
143
-		if (isset($ldapEntry['uid'])) {
144
-			$this->storeLDAPUserName($ldapEntry['uid'][0]);
145
-		} elseif (isset($ldapEntry['samaccountname'])) {
146
-			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
147
-		}
148
-
149
-		//homePath
150
-		if (str_starts_with($this->connection->homeFolderNamingRule, 'attr:')) {
151
-			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
152
-			if (isset($ldapEntry[$attr])) {
153
-				$this->access->cacheUserHome(
154
-					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
155
-			}
156
-		}
157
-
158
-		//memberOf groups
159
-		$cacheKey = 'getMemberOf' . $this->getUsername();
160
-		$groups = false;
161
-		if (isset($ldapEntry['memberof'])) {
162
-			$groups = $ldapEntry['memberof'];
163
-		}
164
-		$this->connection->writeToCache($cacheKey, $groups);
165
-
166
-		//external storage var
167
-		$attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
168
-		if (isset($ldapEntry[$attr])) {
169
-			$this->updateExtStorageHome($ldapEntry[$attr][0]);
170
-		}
171
-		unset($attr);
172
-
173
-		// check for cached profile data
174
-		$username = $this->getUsername(); // buffer variable, to save resource
175
-		$cacheKey = 'getUserProfile-' . $username;
176
-		$profileCached = $this->connection->getFromCache($cacheKey);
177
-		// honoring profile disabled in config.php and check if user profile was refreshed
178
-		if ($this->config->getSystemValueBool('profile.enabled', true) &&
179
-			($profileCached === null) && // no cache or TTL not expired
180
-			!$this->wasRefreshed('profile')) {
181
-			// check current data
182
-			$profileValues = [];
183
-			//User Profile Field - Phone number
184
-			$attr = strtolower($this->connection->ldapAttributePhone);
185
-			if (!empty($attr)) { // attribute configured
186
-				$profileValues[IAccountManager::PROPERTY_PHONE]
187
-					= $ldapEntry[$attr][0] ?? '';
188
-			}
189
-			//User Profile Field - website
190
-			$attr = strtolower($this->connection->ldapAttributeWebsite);
191
-			if (isset($ldapEntry[$attr])) {
192
-				$cutPosition = strpos($ldapEntry[$attr][0], ' ');
193
-				if ($cutPosition) {
194
-					// drop appended label
195
-					$profileValues[IAccountManager::PROPERTY_WEBSITE]
196
-						= substr($ldapEntry[$attr][0], 0, $cutPosition);
197
-				} else {
198
-					$profileValues[IAccountManager::PROPERTY_WEBSITE]
199
-						= $ldapEntry[$attr][0];
200
-				}
201
-			} elseif (!empty($attr)) {	// configured, but not defined
202
-				$profileValues[IAccountManager::PROPERTY_WEBSITE] = '';
203
-			}
204
-			//User Profile Field - Address
205
-			$attr = strtolower($this->connection->ldapAttributeAddress);
206
-			if (isset($ldapEntry[$attr])) {
207
-				if (str_contains($ldapEntry[$attr][0], '$')) {
208
-					// basic format conversion from postalAddress syntax to commata delimited
209
-					$profileValues[IAccountManager::PROPERTY_ADDRESS]
210
-						= str_replace('$', ', ', $ldapEntry[$attr][0]);
211
-				} else {
212
-					$profileValues[IAccountManager::PROPERTY_ADDRESS]
213
-						= $ldapEntry[$attr][0];
214
-				}
215
-			} elseif (!empty($attr)) {	// configured, but not defined
216
-				$profileValues[IAccountManager::PROPERTY_ADDRESS] = '';
217
-			}
218
-			//User Profile Field - Twitter
219
-			$attr = strtolower($this->connection->ldapAttributeTwitter);
220
-			if (!empty($attr)) {
221
-				$profileValues[IAccountManager::PROPERTY_TWITTER]
222
-					= $ldapEntry[$attr][0] ?? '';
223
-			}
224
-			//User Profile Field - fediverse
225
-			$attr = strtolower($this->connection->ldapAttributeFediverse);
226
-			if (!empty($attr)) {
227
-				$profileValues[IAccountManager::PROPERTY_FEDIVERSE]
228
-					= $ldapEntry[$attr][0] ?? '';
229
-			}
230
-			//User Profile Field - organisation
231
-			$attr = strtolower($this->connection->ldapAttributeOrganisation);
232
-			if (!empty($attr)) {
233
-				$profileValues[IAccountManager::PROPERTY_ORGANISATION]
234
-					= $ldapEntry[$attr][0] ?? '';
235
-			}
236
-			//User Profile Field - role
237
-			$attr = strtolower($this->connection->ldapAttributeRole);
238
-			if (!empty($attr)) {
239
-				$profileValues[IAccountManager::PROPERTY_ROLE]
240
-					= $ldapEntry[$attr][0] ?? '';
241
-			}
242
-			//User Profile Field - headline
243
-			$attr = strtolower($this->connection->ldapAttributeHeadline);
244
-			if (!empty($attr)) {
245
-				$profileValues[IAccountManager::PROPERTY_HEADLINE]
246
-					= $ldapEntry[$attr][0] ?? '';
247
-			}
248
-			//User Profile Field - biography
249
-			$attr = strtolower($this->connection->ldapAttributeBiography);
250
-			if (isset($ldapEntry[$attr])) {
251
-				if (str_contains($ldapEntry[$attr][0], '\r')) {
252
-					// convert line endings
253
-					$profileValues[IAccountManager::PROPERTY_BIOGRAPHY]
254
-						= str_replace(["\r\n","\r"], "\n", $ldapEntry[$attr][0]);
255
-				} else {
256
-					$profileValues[IAccountManager::PROPERTY_BIOGRAPHY]
257
-						= $ldapEntry[$attr][0];
258
-				}
259
-			} elseif (!empty($attr)) {	// configured, but not defined
260
-				$profileValues[IAccountManager::PROPERTY_BIOGRAPHY] = '';
261
-			}
262
-			//User Profile Field - birthday
263
-			$attr = strtolower($this->connection->ldapAttributeBirthDate);
264
-			if (!empty($attr) && !empty($ldapEntry[$attr][0])) {
265
-				$value = $ldapEntry[$attr][0];
266
-				try {
267
-					$birthdate = $this->birthdateParser->parseBirthdate($value);
268
-					$profileValues[IAccountManager::PROPERTY_BIRTHDATE]
269
-						= $birthdate->format('Y-m-d');
270
-				} catch (InvalidArgumentException $e) {
271
-					// Invalid date -> just skip the property
272
-					$this->logger->info("Failed to parse user's birthdate from LDAP: $value", [
273
-						'exception' => $e,
274
-						'userId' => $username,
275
-					]);
276
-				}
277
-			}
278
-			//User Profile Field - pronouns
279
-			$attr = strtolower($this->connection->ldapAttributePronouns);
280
-			if (!empty($attr)) {
281
-				$profileValues[IAccountManager::PROPERTY_PRONOUNS]
282
-					= $ldapEntry[$attr][0] ?? '';
283
-			}
284
-			// check for changed data and cache just for TTL checking
285
-			$checksum = hash('sha256', json_encode($profileValues));
286
-			$this->connection->writeToCache($cacheKey, $checksum // write array to cache. is waste of cache space
287
-				, null); // use ldapCacheTTL from configuration
288
-			// Update user profile
289
-			if ($this->config->getUserValue($username, 'user_ldap', 'lastProfileChecksum', null) !== $checksum) {
290
-				$this->config->setUserValue($username, 'user_ldap', 'lastProfileChecksum', $checksum);
291
-				$this->updateProfile($profileValues);
292
-				$this->logger->info("updated profile uid=$username", ['app' => 'user_ldap']);
293
-			} else {
294
-				$this->logger->debug('profile data from LDAP unchanged', ['app' => 'user_ldap', 'uid' => $username]);
295
-			}
296
-			unset($attr);
297
-		} elseif ($profileCached !== null) { // message delayed, to declutter log
298
-			$this->logger->debug('skipping profile check, while cached data exist', ['app' => 'user_ldap', 'uid' => $username]);
299
-		}
300
-
301
-		//Avatar
302
-		/** @var Connection $connection */
303
-		$connection = $this->access->getConnection();
304
-		$attributes = $connection->resolveRule('avatar');
305
-		foreach ($attributes as $attribute) {
306
-			if (isset($ldapEntry[$attribute])) {
307
-				$this->avatarImage = $ldapEntry[$attribute][0];
308
-				$this->updateAvatar();
309
-				break;
310
-			}
311
-		}
312
-	}
313
-
314
-	/**
315
-	 * @brief returns the LDAP DN of the user
316
-	 * @return string
317
-	 */
318
-	public function getDN() {
319
-		return $this->dn;
320
-	}
321
-
322
-	/**
323
-	 * @brief returns the Nextcloud internal username of the user
324
-	 * @return string
325
-	 */
326
-	public function getUsername() {
327
-		return $this->uid;
328
-	}
329
-
330
-	/**
331
-	 * returns the home directory of the user if specified by LDAP settings
332
-	 * @throws \Exception
333
-	 */
334
-	public function getHomePath(?string $valueFromLDAP = null): string|false {
335
-		$path = (string)$valueFromLDAP;
336
-		$attr = null;
337
-
338
-		if (is_null($valueFromLDAP)
339
-		   && str_starts_with($this->access->connection->homeFolderNamingRule, 'attr:')
340
-		   && $this->access->connection->homeFolderNamingRule !== 'attr:') {
341
-			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
342
-			$dn = $this->access->username2dn($this->getUsername());
343
-			if ($dn === false) {
344
-				return false;
345
-			}
346
-			$homedir = $this->access->readAttribute($dn, $attr);
347
-			if ($homedir !== false && isset($homedir[0])) {
348
-				$path = $homedir[0];
349
-			}
350
-		}
351
-
352
-		if ($path !== '') {
353
-			//if attribute's value is an absolute path take this, otherwise append it to data dir
354
-			//check for / at the beginning or pattern c:\ resp. c:/
355
-			if ($path[0] !== '/'
356
-			   && !(strlen($path) > 3 && ctype_alpha($path[0])
357
-				   && $path[1] === ':' && ($path[2] === '\\' || $path[2] === '/'))
358
-			) {
359
-				$path = $this->config->getSystemValue('datadirectory',
360
-					\OC::$SERVERROOT . '/data') . '/' . $path;
361
-			}
362
-			//we need it to store it in the DB as well in case a user gets
363
-			//deleted so we can clean up afterwards
364
-			$this->config->setUserValue(
365
-				$this->getUsername(), 'user_ldap', 'homePath', $path
366
-			);
367
-			return $path;
368
-		}
369
-
370
-		if (!is_null($attr)
371
-			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', 'true')
372
-		) {
373
-			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
374
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
375
-		}
376
-
377
-		//false will apply default behaviour as defined and done by OC_User
378
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
379
-		return false;
380
-	}
381
-
382
-	public function getMemberOfGroups(): array|false {
383
-		$cacheKey = 'getMemberOf' . $this->getUsername();
384
-		$memberOfGroups = $this->connection->getFromCache($cacheKey);
385
-		if (!is_null($memberOfGroups)) {
386
-			return $memberOfGroups;
387
-		}
388
-		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
389
-		$this->connection->writeToCache($cacheKey, $groupDNs);
390
-		return $groupDNs;
391
-	}
392
-
393
-	/**
394
-	 * @brief reads the image from LDAP that shall be used as Avatar
395
-	 * @return string|false data (provided by LDAP)
396
-	 */
397
-	public function getAvatarImage(): string|false {
398
-		if (!is_null($this->avatarImage)) {
399
-			return $this->avatarImage;
400
-		}
401
-
402
-		$this->avatarImage = false;
403
-		/** @var Connection $connection */
404
-		$connection = $this->access->getConnection();
405
-		$attributes = $connection->resolveRule('avatar');
406
-		foreach ($attributes as $attribute) {
407
-			$result = $this->access->readAttribute($this->dn, $attribute);
408
-			if ($result !== false && isset($result[0])) {
409
-				$this->avatarImage = $result[0];
410
-				break;
411
-			}
412
-		}
413
-
414
-		return $this->avatarImage;
415
-	}
416
-
417
-	/**
418
-	 * @brief marks the user as having logged in at least once
419
-	 */
420
-	public function markLogin(): void {
421
-		$this->config->setUserValue(
422
-			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, '1');
423
-	}
424
-
425
-	/**
426
-	 * Stores a key-value pair in relation to this user
427
-	 */
428
-	private function store(string $key, string $value): void {
429
-		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
430
-	}
431
-
432
-	/**
433
-	 * Composes the display name and stores it in the database. The final
434
-	 * display name is returned.
435
-	 *
436
-	 * @return string the effective display name
437
-	 */
438
-	public function composeAndStoreDisplayName(string $displayName, string $displayName2 = ''): string {
439
-		if ($displayName2 !== '') {
440
-			$displayName .= ' (' . $displayName2 . ')';
441
-		}
442
-		$oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
443
-		if ($oldName !== $displayName) {
444
-			$this->store('displayName', $displayName);
445
-			$user = $this->userManager->get($this->getUsername());
446
-			if (!empty($oldName) && $user instanceof \OC\User\User) {
447
-				// if it was empty, it would be a new record, not a change emitting the trigger could
448
-				// potentially cause a UniqueConstraintViolationException, depending on some factors.
449
-				$user->triggerChange('displayName', $displayName, $oldName);
450
-			}
451
-		}
452
-		return $displayName;
453
-	}
454
-
455
-	/**
456
-	 * Stores the LDAP Username in the Database
457
-	 */
458
-	public function storeLDAPUserName(string $userName): void {
459
-		$this->store('uid', $userName);
460
-	}
461
-
462
-	/**
463
-	 * @brief checks whether an update method specified by feature was run
464
-	 * already. If not, it will marked like this, because it is expected that
465
-	 * the method will be run, when false is returned.
466
-	 * @param string $feature email | quota | avatar | profile (can be extended)
467
-	 */
468
-	private function wasRefreshed(string $feature): bool {
469
-		if (isset($this->refreshedFeatures[$feature])) {
470
-			return true;
471
-		}
472
-		$this->refreshedFeatures[$feature] = 1;
473
-		return false;
474
-	}
475
-
476
-	/**
477
-	 * fetches the email from LDAP and stores it as Nextcloud user value
478
-	 * @param ?string $valueFromLDAP if known, to save an LDAP read request
479
-	 */
480
-	public function updateEmail(?string $valueFromLDAP = null): void {
481
-		if ($this->wasRefreshed('email')) {
482
-			return;
483
-		}
484
-		$email = (string)$valueFromLDAP;
485
-		if (is_null($valueFromLDAP)) {
486
-			$emailAttribute = $this->connection->ldapEmailAttribute;
487
-			if ($emailAttribute !== '') {
488
-				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
489
-				if (is_array($aEmail) && (count($aEmail) > 0)) {
490
-					$email = (string)$aEmail[0];
491
-				}
492
-			}
493
-		}
494
-		if ($email !== '') {
495
-			$user = $this->userManager->get($this->uid);
496
-			if (!is_null($user)) {
497
-				$currentEmail = (string)$user->getSystemEMailAddress();
498
-				if ($currentEmail !== $email) {
499
-					$user->setSystemEMailAddress($email);
500
-				}
501
-			}
502
-		}
503
-	}
504
-
505
-	/**
506
-	 * Overall process goes as follow:
507
-	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
508
-	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
509
-	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
510
-	 * 4. check if the target user exists and set the quota for the user.
511
-	 *
512
-	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
513
-	 * parameter can be passed with the value of the attribute. This value will be considered as the
514
-	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
515
-	 * fetch all the user's attributes in one call and use the fetched values in this function.
516
-	 * The expected value for that parameter is a string describing the quota for the user. Valid
517
-	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
518
-	 * bytes), '1234 MB' (quota in MB - check the \OCP\Util::computerFileSize method for more info)
519
-	 *
520
-	 * fetches the quota from LDAP and stores it as Nextcloud user value
521
-	 * @param ?string $valueFromLDAP the quota attribute's value can be passed,
522
-	 *                               to save the readAttribute request
523
-	 */
524
-	public function updateQuota(?string $valueFromLDAP = null): void {
525
-		if ($this->wasRefreshed('quota')) {
526
-			return;
527
-		}
528
-
529
-		$quotaAttribute = $this->connection->ldapQuotaAttribute;
530
-		$defaultQuota = $this->connection->ldapQuotaDefault;
531
-		if ($quotaAttribute === '' && $defaultQuota === '') {
532
-			return;
533
-		}
534
-
535
-		$quota = false;
536
-		if (is_null($valueFromLDAP) && $quotaAttribute !== '') {
537
-			$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
538
-			if ($aQuota !== false && isset($aQuota[0]) && $this->verifyQuotaValue($aQuota[0])) {
539
-				$quota = $aQuota[0];
540
-			} elseif (is_array($aQuota) && isset($aQuota[0])) {
541
-				$this->logger->debug('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ['app' => 'user_ldap']);
542
-			}
543
-		} elseif (!is_null($valueFromLDAP) && $this->verifyQuotaValue($valueFromLDAP)) {
544
-			$quota = $valueFromLDAP;
545
-		} else {
546
-			$this->logger->debug('no suitable LDAP quota found for user ' . $this->uid . ': [' . ($valueFromLDAP ?? '') . ']', ['app' => 'user_ldap']);
547
-		}
548
-
549
-		if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
550
-			// quota not found using the LDAP attribute (or not parseable). Try the default quota
551
-			$quota = $defaultQuota;
552
-		} elseif ($quota === false) {
553
-			$this->logger->debug('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ['app' => 'user_ldap']);
554
-			return;
555
-		}
556
-
557
-		$targetUser = $this->userManager->get($this->uid);
558
-		if ($targetUser instanceof IUser) {
559
-			$targetUser->setQuota($quota);
560
-		} else {
561
-			$this->logger->info('trying to set a quota for user ' . $this->uid . ' but the user is missing', ['app' => 'user_ldap']);
562
-		}
563
-	}
564
-
565
-	private function verifyQuotaValue(string $quotaValue): bool {
566
-		return $quotaValue === 'none' || $quotaValue === 'default' || \OCP\Util::computerFileSize($quotaValue) !== false;
567
-	}
568
-
569
-	/**
570
-	 * takes values from LDAP and stores it as Nextcloud user profile value
571
-	 *
572
-	 * @param array $profileValues associative array of property keys and values from LDAP
573
-	 */
574
-	private function updateProfile(array $profileValues): void {
575
-		// check if given array is empty
576
-		if (empty($profileValues)) {
577
-			return; // okay, nothing to do
578
-		}
579
-		// fetch/prepare user
580
-		$user = $this->userManager->get($this->uid);
581
-		if (is_null($user)) {
582
-			$this->logger->error('could not get user for uid=' . $this->uid . '', ['app' => 'user_ldap']);
583
-			return;
584
-		}
585
-		// prepare AccountManager and Account
586
-		$accountManager = Server::get(IAccountManager::class);
587
-		$account = $accountManager->getAccount($user);	// get Account
588
-		$defaultScopes = array_merge(AccountManager::DEFAULT_SCOPES,
589
-			$this->config->getSystemValue('account_manager.default_property_scope', []));
590
-		// loop through the properties and handle them
591
-		foreach ($profileValues as $property => $valueFromLDAP) {
592
-			// check and update profile properties
593
-			$value = (is_array($valueFromLDAP) ? $valueFromLDAP[0] : $valueFromLDAP); // take ONLY the first value, if multiple values specified
594
-			try {
595
-				$accountProperty = $account->getProperty($property);
596
-				$currentValue = $accountProperty->getValue();
597
-				$scope = ($accountProperty->getScope() ?: $defaultScopes[$property]);
598
-			} catch (PropertyDoesNotExistException $e) { // thrown at getProperty
599
-				$this->logger->error('property does not exist: ' . $property
600
-					. ' for uid=' . $this->uid . '', ['app' => 'user_ldap', 'exception' => $e]);
601
-				$currentValue = '';
602
-				$scope = $defaultScopes[$property];
603
-			}
604
-			$verified = IAccountManager::VERIFIED; // trust the LDAP admin knew what they put there
605
-			if ($currentValue !== $value) {
606
-				$account->setProperty($property, $value, $scope, $verified);
607
-				$this->logger->debug('update user profile: ' . $property . '=' . $value
608
-					. ' for uid=' . $this->uid . '', ['app' => 'user_ldap']);
609
-			}
610
-		}
611
-		try {
612
-			$accountManager->updateAccount($account); // may throw InvalidArgumentException
613
-		} catch (\InvalidArgumentException $e) {
614
-			$this->logger->error('invalid data from LDAP: for uid=' . $this->uid . '', ['app' => 'user_ldap', 'func' => 'updateProfile'
615
-				, 'exception' => $e]);
616
-		}
617
-	}
618
-
619
-	/**
620
-	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
621
-	 * @return bool true when the avatar was set successfully or is up to date
622
-	 */
623
-	public function updateAvatar(bool $force = false): bool {
624
-		if (!$force && $this->wasRefreshed('avatar')) {
625
-			return false;
626
-		}
627
-		$avatarImage = $this->getAvatarImage();
628
-		if ($avatarImage === false) {
629
-			//not set, nothing left to do;
630
-			return false;
631
-		}
632
-
633
-		if (!$this->image->loadFromBase64(base64_encode($avatarImage))) {
634
-			return false;
635
-		}
636
-
637
-		// use the checksum before modifications
638
-		$checksum = md5($this->image->data());
639
-
640
-		if ($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '') && $this->avatarExists()) {
641
-			return true;
642
-		}
643
-
644
-		$isSet = $this->setNextcloudAvatar();
645
-
646
-		if ($isSet) {
647
-			// save checksum only after successful setting
648
-			$this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum);
649
-		}
650
-
651
-		return $isSet;
652
-	}
653
-
654
-	private function avatarExists(): bool {
655
-		try {
656
-			$currentAvatar = $this->avatarManager->getAvatar($this->uid);
657
-			return $currentAvatar->exists() && $currentAvatar->isCustomAvatar();
658
-		} catch (\Exception $e) {
659
-			return false;
660
-		}
661
-	}
662
-
663
-	/**
664
-	 * @brief sets an image as Nextcloud avatar
665
-	 */
666
-	private function setNextcloudAvatar(): bool {
667
-		if (!$this->image->valid()) {
668
-			$this->logger->error('avatar image data from LDAP invalid for ' . $this->dn, ['app' => 'user_ldap']);
669
-			return false;
670
-		}
671
-
672
-
673
-		//make sure it is a square and not bigger than 512x512
674
-		$size = min([$this->image->width(), $this->image->height(), 512]);
675
-		if (!$this->image->centerCrop($size)) {
676
-			$this->logger->error('croping image for avatar failed for ' . $this->dn, ['app' => 'user_ldap']);
677
-			return false;
678
-		}
679
-
680
-		try {
681
-			$avatar = $this->avatarManager->getAvatar($this->uid);
682
-			$avatar->set($this->image);
683
-			return true;
684
-		} catch (\Exception $e) {
685
-			$this->logger->info('Could not set avatar for ' . $this->dn, ['exception' => $e]);
686
-		}
687
-		return false;
688
-	}
689
-
690
-	/**
691
-	 * @throws AttributeNotSet
692
-	 * @throws \OC\ServerNotAvailableException
693
-	 * @throws PreConditionNotMetException
694
-	 */
695
-	public function getExtStorageHome():string {
696
-		$value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
697
-		if ($value !== '') {
698
-			return $value;
699
-		}
700
-
701
-		$value = $this->updateExtStorageHome();
702
-		if ($value !== '') {
703
-			return $value;
704
-		}
705
-
706
-		throw new AttributeNotSet(sprintf(
707
-			'external home storage attribute yield no value for %s', $this->getUsername()
708
-		));
709
-	}
710
-
711
-	/**
712
-	 * @throws PreConditionNotMetException
713
-	 * @throws \OC\ServerNotAvailableException
714
-	 */
715
-	public function updateExtStorageHome(?string $valueFromLDAP = null):string {
716
-		if ($valueFromLDAP === null) {
717
-			$extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
718
-		} else {
719
-			$extHomeValues = [$valueFromLDAP];
720
-		}
721
-		if ($extHomeValues !== false && isset($extHomeValues[0])) {
722
-			$extHome = $extHomeValues[0];
723
-			$this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
724
-			return $extHome;
725
-		} else {
726
-			$this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
727
-			return '';
728
-		}
729
-	}
730
-
731
-	/**
732
-	 * called by a post_login hook to handle password expiry
733
-	 */
734
-	public function handlePasswordExpiry(array $params): void {
735
-		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
736
-		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
737
-			//password expiry handling disabled
738
-			return;
739
-		}
740
-		$uid = $params['uid'];
741
-		if (isset($uid) && $uid === $this->getUsername()) {
742
-			//retrieve relevant user attributes
743
-			$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
744
-
745
-			if (array_key_exists('pwdpolicysubentry', $result[0])) {
746
-				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
747
-				if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) {
748
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
749
-				}
750
-			}
751
-
752
-			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : [];
753
-			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : [];
754
-			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : [];
755
-
756
-			//retrieve relevant password policy attributes
757
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
758
-			$result = $this->connection->getFromCache($cacheKey);
759
-			if (is_null($result)) {
760
-				$result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
761
-				$this->connection->writeToCache($cacheKey, $result);
762
-			}
763
-
764
-			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : [];
765
-			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : [];
766
-			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : [];
767
-
768
-			//handle grace login
769
-			if (!empty($pwdGraceUseTime)) { //was this a grace login?
770
-				if (!empty($pwdGraceAuthNLimit)
771
-					&& count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available?
772
-					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
773
-					header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute(
774
-						'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
775
-				} else { //no more grace login available
776
-					header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute(
777
-						'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid]));
778
-				}
779
-				exit();
780
-			}
781
-			//handle pwdReset attribute
782
-			if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change their password
783
-				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
784
-				header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute(
785
-					'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
786
-				exit();
787
-			}
788
-			//handle password expiry warning
789
-			if (!empty($pwdChangedTime)) {
790
-				if (!empty($pwdMaxAge)
791
-					&& !empty($pwdExpireWarning)) {
792
-					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
793
-					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
794
-					if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) {
795
-						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
796
-						$pwdChangedTimeDt->add(new \DateInterval('PT' . $pwdMaxAgeInt . 'S'));
797
-						$currentDateTime = new \DateTime();
798
-						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
799
-						if ($secondsToExpiry <= $pwdExpireWarningInt) {
800
-							//remove last password expiry warning if any
801
-							$notification = $this->notificationManager->createNotification();
802
-							$notification->setApp('user_ldap')
803
-								->setUser($uid)
804
-								->setObject('pwd_exp_warn', $uid)
805
-							;
806
-							$this->notificationManager->markProcessed($notification);
807
-							//create new password expiry warning
808
-							$notification = $this->notificationManager->createNotification();
809
-							$notification->setApp('user_ldap')
810
-								->setUser($uid)
811
-								->setDateTime($currentDateTime)
812
-								->setObject('pwd_exp_warn', $uid)
813
-								->setSubject('pwd_exp_warn_days', [(int)ceil($secondsToExpiry / 60 / 60 / 24)])
814
-							;
815
-							$this->notificationManager->notify($notification);
816
-						}
817
-					}
818
-				}
819
-			}
820
-		}
821
-	}
36
+    protected Connection $connection;
37
+    /**
38
+     * @var array<string,1>
39
+     */
40
+    protected array $refreshedFeatures = [];
41
+    protected string|false|null $avatarImage = null;
42
+
43
+    protected BirthdateParserService $birthdateParser;
44
+
45
+    /**
46
+     * DB config keys for user preferences
47
+     * @var string
48
+     */
49
+    public const USER_PREFKEY_FIRSTLOGIN = 'firstLoginAccomplished';
50
+
51
+    /**
52
+     * @brief constructor, make sure the subclasses call this one!
53
+     */
54
+    public function __construct(
55
+        protected string $uid,
56
+        protected string $dn,
57
+        protected Access $access,
58
+        protected IConfig $config,
59
+        protected Image $image,
60
+        protected LoggerInterface $logger,
61
+        protected IAvatarManager $avatarManager,
62
+        protected IUserManager $userManager,
63
+        protected INotificationManager $notificationManager,
64
+    ) {
65
+        if ($uid === '') {
66
+            $logger->error("uid for '$dn' must not be an empty string", ['app' => 'user_ldap']);
67
+            throw new \InvalidArgumentException('uid must not be an empty string!');
68
+        }
69
+        $this->connection = $this->access->getConnection();
70
+        $this->birthdateParser = new BirthdateParserService();
71
+
72
+        Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
73
+    }
74
+
75
+    /**
76
+     * marks a user as deleted
77
+     *
78
+     * @throws PreConditionNotMetException
79
+     */
80
+    public function markUser(): void {
81
+        $curValue = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '0');
82
+        if ($curValue === '1') {
83
+            // the user is already marked, do not write to DB again
84
+            return;
85
+        }
86
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'isDeleted', '1');
87
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'foundDeleted', (string)time());
88
+    }
89
+
90
+    /**
91
+     * processes results from LDAP for attributes as returned by getAttributesToRead()
92
+     * @param array $ldapEntry the user entry as retrieved from LDAP
93
+     */
94
+    public function processAttributes(array $ldapEntry): void {
95
+        //Quota
96
+        $attr = strtolower($this->connection->ldapQuotaAttribute);
97
+        if (isset($ldapEntry[$attr])) {
98
+            $this->updateQuota($ldapEntry[$attr][0]);
99
+        } else {
100
+            if ($this->connection->ldapQuotaDefault !== '') {
101
+                $this->updateQuota();
102
+            }
103
+        }
104
+        unset($attr);
105
+
106
+        //displayName
107
+        $displayName = $displayName2 = '';
108
+        $attr = strtolower($this->connection->ldapUserDisplayName);
109
+        if (isset($ldapEntry[$attr])) {
110
+            $displayName = (string)$ldapEntry[$attr][0];
111
+        }
112
+        $attr = strtolower($this->connection->ldapUserDisplayName2);
113
+        if (isset($ldapEntry[$attr])) {
114
+            $displayName2 = (string)$ldapEntry[$attr][0];
115
+        }
116
+        if ($displayName !== '') {
117
+            $this->composeAndStoreDisplayName($displayName, $displayName2);
118
+            $this->access->cacheUserDisplayName(
119
+                $this->getUsername(),
120
+                $displayName,
121
+                $displayName2
122
+            );
123
+        }
124
+        unset($attr);
125
+
126
+        //Email
127
+        //email must be stored after displayname, because it would cause a user
128
+        //change event that will trigger fetching the display name again
129
+        $attr = strtolower($this->connection->ldapEmailAttribute);
130
+        if (isset($ldapEntry[$attr])) {
131
+            $mailValue = 0;
132
+            for ($x = 0; $x < count($ldapEntry[$attr]); $x++) {
133
+                if (filter_var($ldapEntry[$attr][$x], FILTER_VALIDATE_EMAIL)) {
134
+                    $mailValue = $x;
135
+                    break;
136
+                }
137
+            }
138
+            $this->updateEmail($ldapEntry[$attr][$mailValue]);
139
+        }
140
+        unset($attr);
141
+
142
+        // LDAP Username, needed for s2s sharing
143
+        if (isset($ldapEntry['uid'])) {
144
+            $this->storeLDAPUserName($ldapEntry['uid'][0]);
145
+        } elseif (isset($ldapEntry['samaccountname'])) {
146
+            $this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
147
+        }
148
+
149
+        //homePath
150
+        if (str_starts_with($this->connection->homeFolderNamingRule, 'attr:')) {
151
+            $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
152
+            if (isset($ldapEntry[$attr])) {
153
+                $this->access->cacheUserHome(
154
+                    $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
155
+            }
156
+        }
157
+
158
+        //memberOf groups
159
+        $cacheKey = 'getMemberOf' . $this->getUsername();
160
+        $groups = false;
161
+        if (isset($ldapEntry['memberof'])) {
162
+            $groups = $ldapEntry['memberof'];
163
+        }
164
+        $this->connection->writeToCache($cacheKey, $groups);
165
+
166
+        //external storage var
167
+        $attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
168
+        if (isset($ldapEntry[$attr])) {
169
+            $this->updateExtStorageHome($ldapEntry[$attr][0]);
170
+        }
171
+        unset($attr);
172
+
173
+        // check for cached profile data
174
+        $username = $this->getUsername(); // buffer variable, to save resource
175
+        $cacheKey = 'getUserProfile-' . $username;
176
+        $profileCached = $this->connection->getFromCache($cacheKey);
177
+        // honoring profile disabled in config.php and check if user profile was refreshed
178
+        if ($this->config->getSystemValueBool('profile.enabled', true) &&
179
+            ($profileCached === null) && // no cache or TTL not expired
180
+            !$this->wasRefreshed('profile')) {
181
+            // check current data
182
+            $profileValues = [];
183
+            //User Profile Field - Phone number
184
+            $attr = strtolower($this->connection->ldapAttributePhone);
185
+            if (!empty($attr)) { // attribute configured
186
+                $profileValues[IAccountManager::PROPERTY_PHONE]
187
+                    = $ldapEntry[$attr][0] ?? '';
188
+            }
189
+            //User Profile Field - website
190
+            $attr = strtolower($this->connection->ldapAttributeWebsite);
191
+            if (isset($ldapEntry[$attr])) {
192
+                $cutPosition = strpos($ldapEntry[$attr][0], ' ');
193
+                if ($cutPosition) {
194
+                    // drop appended label
195
+                    $profileValues[IAccountManager::PROPERTY_WEBSITE]
196
+                        = substr($ldapEntry[$attr][0], 0, $cutPosition);
197
+                } else {
198
+                    $profileValues[IAccountManager::PROPERTY_WEBSITE]
199
+                        = $ldapEntry[$attr][0];
200
+                }
201
+            } elseif (!empty($attr)) {	// configured, but not defined
202
+                $profileValues[IAccountManager::PROPERTY_WEBSITE] = '';
203
+            }
204
+            //User Profile Field - Address
205
+            $attr = strtolower($this->connection->ldapAttributeAddress);
206
+            if (isset($ldapEntry[$attr])) {
207
+                if (str_contains($ldapEntry[$attr][0], '$')) {
208
+                    // basic format conversion from postalAddress syntax to commata delimited
209
+                    $profileValues[IAccountManager::PROPERTY_ADDRESS]
210
+                        = str_replace('$', ', ', $ldapEntry[$attr][0]);
211
+                } else {
212
+                    $profileValues[IAccountManager::PROPERTY_ADDRESS]
213
+                        = $ldapEntry[$attr][0];
214
+                }
215
+            } elseif (!empty($attr)) {	// configured, but not defined
216
+                $profileValues[IAccountManager::PROPERTY_ADDRESS] = '';
217
+            }
218
+            //User Profile Field - Twitter
219
+            $attr = strtolower($this->connection->ldapAttributeTwitter);
220
+            if (!empty($attr)) {
221
+                $profileValues[IAccountManager::PROPERTY_TWITTER]
222
+                    = $ldapEntry[$attr][0] ?? '';
223
+            }
224
+            //User Profile Field - fediverse
225
+            $attr = strtolower($this->connection->ldapAttributeFediverse);
226
+            if (!empty($attr)) {
227
+                $profileValues[IAccountManager::PROPERTY_FEDIVERSE]
228
+                    = $ldapEntry[$attr][0] ?? '';
229
+            }
230
+            //User Profile Field - organisation
231
+            $attr = strtolower($this->connection->ldapAttributeOrganisation);
232
+            if (!empty($attr)) {
233
+                $profileValues[IAccountManager::PROPERTY_ORGANISATION]
234
+                    = $ldapEntry[$attr][0] ?? '';
235
+            }
236
+            //User Profile Field - role
237
+            $attr = strtolower($this->connection->ldapAttributeRole);
238
+            if (!empty($attr)) {
239
+                $profileValues[IAccountManager::PROPERTY_ROLE]
240
+                    = $ldapEntry[$attr][0] ?? '';
241
+            }
242
+            //User Profile Field - headline
243
+            $attr = strtolower($this->connection->ldapAttributeHeadline);
244
+            if (!empty($attr)) {
245
+                $profileValues[IAccountManager::PROPERTY_HEADLINE]
246
+                    = $ldapEntry[$attr][0] ?? '';
247
+            }
248
+            //User Profile Field - biography
249
+            $attr = strtolower($this->connection->ldapAttributeBiography);
250
+            if (isset($ldapEntry[$attr])) {
251
+                if (str_contains($ldapEntry[$attr][0], '\r')) {
252
+                    // convert line endings
253
+                    $profileValues[IAccountManager::PROPERTY_BIOGRAPHY]
254
+                        = str_replace(["\r\n","\r"], "\n", $ldapEntry[$attr][0]);
255
+                } else {
256
+                    $profileValues[IAccountManager::PROPERTY_BIOGRAPHY]
257
+                        = $ldapEntry[$attr][0];
258
+                }
259
+            } elseif (!empty($attr)) {	// configured, but not defined
260
+                $profileValues[IAccountManager::PROPERTY_BIOGRAPHY] = '';
261
+            }
262
+            //User Profile Field - birthday
263
+            $attr = strtolower($this->connection->ldapAttributeBirthDate);
264
+            if (!empty($attr) && !empty($ldapEntry[$attr][0])) {
265
+                $value = $ldapEntry[$attr][0];
266
+                try {
267
+                    $birthdate = $this->birthdateParser->parseBirthdate($value);
268
+                    $profileValues[IAccountManager::PROPERTY_BIRTHDATE]
269
+                        = $birthdate->format('Y-m-d');
270
+                } catch (InvalidArgumentException $e) {
271
+                    // Invalid date -> just skip the property
272
+                    $this->logger->info("Failed to parse user's birthdate from LDAP: $value", [
273
+                        'exception' => $e,
274
+                        'userId' => $username,
275
+                    ]);
276
+                }
277
+            }
278
+            //User Profile Field - pronouns
279
+            $attr = strtolower($this->connection->ldapAttributePronouns);
280
+            if (!empty($attr)) {
281
+                $profileValues[IAccountManager::PROPERTY_PRONOUNS]
282
+                    = $ldapEntry[$attr][0] ?? '';
283
+            }
284
+            // check for changed data and cache just for TTL checking
285
+            $checksum = hash('sha256', json_encode($profileValues));
286
+            $this->connection->writeToCache($cacheKey, $checksum // write array to cache. is waste of cache space
287
+                , null); // use ldapCacheTTL from configuration
288
+            // Update user profile
289
+            if ($this->config->getUserValue($username, 'user_ldap', 'lastProfileChecksum', null) !== $checksum) {
290
+                $this->config->setUserValue($username, 'user_ldap', 'lastProfileChecksum', $checksum);
291
+                $this->updateProfile($profileValues);
292
+                $this->logger->info("updated profile uid=$username", ['app' => 'user_ldap']);
293
+            } else {
294
+                $this->logger->debug('profile data from LDAP unchanged', ['app' => 'user_ldap', 'uid' => $username]);
295
+            }
296
+            unset($attr);
297
+        } elseif ($profileCached !== null) { // message delayed, to declutter log
298
+            $this->logger->debug('skipping profile check, while cached data exist', ['app' => 'user_ldap', 'uid' => $username]);
299
+        }
300
+
301
+        //Avatar
302
+        /** @var Connection $connection */
303
+        $connection = $this->access->getConnection();
304
+        $attributes = $connection->resolveRule('avatar');
305
+        foreach ($attributes as $attribute) {
306
+            if (isset($ldapEntry[$attribute])) {
307
+                $this->avatarImage = $ldapEntry[$attribute][0];
308
+                $this->updateAvatar();
309
+                break;
310
+            }
311
+        }
312
+    }
313
+
314
+    /**
315
+     * @brief returns the LDAP DN of the user
316
+     * @return string
317
+     */
318
+    public function getDN() {
319
+        return $this->dn;
320
+    }
321
+
322
+    /**
323
+     * @brief returns the Nextcloud internal username of the user
324
+     * @return string
325
+     */
326
+    public function getUsername() {
327
+        return $this->uid;
328
+    }
329
+
330
+    /**
331
+     * returns the home directory of the user if specified by LDAP settings
332
+     * @throws \Exception
333
+     */
334
+    public function getHomePath(?string $valueFromLDAP = null): string|false {
335
+        $path = (string)$valueFromLDAP;
336
+        $attr = null;
337
+
338
+        if (is_null($valueFromLDAP)
339
+           && str_starts_with($this->access->connection->homeFolderNamingRule, 'attr:')
340
+           && $this->access->connection->homeFolderNamingRule !== 'attr:') {
341
+            $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
342
+            $dn = $this->access->username2dn($this->getUsername());
343
+            if ($dn === false) {
344
+                return false;
345
+            }
346
+            $homedir = $this->access->readAttribute($dn, $attr);
347
+            if ($homedir !== false && isset($homedir[0])) {
348
+                $path = $homedir[0];
349
+            }
350
+        }
351
+
352
+        if ($path !== '') {
353
+            //if attribute's value is an absolute path take this, otherwise append it to data dir
354
+            //check for / at the beginning or pattern c:\ resp. c:/
355
+            if ($path[0] !== '/'
356
+               && !(strlen($path) > 3 && ctype_alpha($path[0])
357
+                   && $path[1] === ':' && ($path[2] === '\\' || $path[2] === '/'))
358
+            ) {
359
+                $path = $this->config->getSystemValue('datadirectory',
360
+                    \OC::$SERVERROOT . '/data') . '/' . $path;
361
+            }
362
+            //we need it to store it in the DB as well in case a user gets
363
+            //deleted so we can clean up afterwards
364
+            $this->config->setUserValue(
365
+                $this->getUsername(), 'user_ldap', 'homePath', $path
366
+            );
367
+            return $path;
368
+        }
369
+
370
+        if (!is_null($attr)
371
+            && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', 'true')
372
+        ) {
373
+            // a naming rule attribute is defined, but it doesn't exist for that LDAP user
374
+            throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
375
+        }
376
+
377
+        //false will apply default behaviour as defined and done by OC_User
378
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
379
+        return false;
380
+    }
381
+
382
+    public function getMemberOfGroups(): array|false {
383
+        $cacheKey = 'getMemberOf' . $this->getUsername();
384
+        $memberOfGroups = $this->connection->getFromCache($cacheKey);
385
+        if (!is_null($memberOfGroups)) {
386
+            return $memberOfGroups;
387
+        }
388
+        $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
389
+        $this->connection->writeToCache($cacheKey, $groupDNs);
390
+        return $groupDNs;
391
+    }
392
+
393
+    /**
394
+     * @brief reads the image from LDAP that shall be used as Avatar
395
+     * @return string|false data (provided by LDAP)
396
+     */
397
+    public function getAvatarImage(): string|false {
398
+        if (!is_null($this->avatarImage)) {
399
+            return $this->avatarImage;
400
+        }
401
+
402
+        $this->avatarImage = false;
403
+        /** @var Connection $connection */
404
+        $connection = $this->access->getConnection();
405
+        $attributes = $connection->resolveRule('avatar');
406
+        foreach ($attributes as $attribute) {
407
+            $result = $this->access->readAttribute($this->dn, $attribute);
408
+            if ($result !== false && isset($result[0])) {
409
+                $this->avatarImage = $result[0];
410
+                break;
411
+            }
412
+        }
413
+
414
+        return $this->avatarImage;
415
+    }
416
+
417
+    /**
418
+     * @brief marks the user as having logged in at least once
419
+     */
420
+    public function markLogin(): void {
421
+        $this->config->setUserValue(
422
+            $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, '1');
423
+    }
424
+
425
+    /**
426
+     * Stores a key-value pair in relation to this user
427
+     */
428
+    private function store(string $key, string $value): void {
429
+        $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
430
+    }
431
+
432
+    /**
433
+     * Composes the display name and stores it in the database. The final
434
+     * display name is returned.
435
+     *
436
+     * @return string the effective display name
437
+     */
438
+    public function composeAndStoreDisplayName(string $displayName, string $displayName2 = ''): string {
439
+        if ($displayName2 !== '') {
440
+            $displayName .= ' (' . $displayName2 . ')';
441
+        }
442
+        $oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null);
443
+        if ($oldName !== $displayName) {
444
+            $this->store('displayName', $displayName);
445
+            $user = $this->userManager->get($this->getUsername());
446
+            if (!empty($oldName) && $user instanceof \OC\User\User) {
447
+                // if it was empty, it would be a new record, not a change emitting the trigger could
448
+                // potentially cause a UniqueConstraintViolationException, depending on some factors.
449
+                $user->triggerChange('displayName', $displayName, $oldName);
450
+            }
451
+        }
452
+        return $displayName;
453
+    }
454
+
455
+    /**
456
+     * Stores the LDAP Username in the Database
457
+     */
458
+    public function storeLDAPUserName(string $userName): void {
459
+        $this->store('uid', $userName);
460
+    }
461
+
462
+    /**
463
+     * @brief checks whether an update method specified by feature was run
464
+     * already. If not, it will marked like this, because it is expected that
465
+     * the method will be run, when false is returned.
466
+     * @param string $feature email | quota | avatar | profile (can be extended)
467
+     */
468
+    private function wasRefreshed(string $feature): bool {
469
+        if (isset($this->refreshedFeatures[$feature])) {
470
+            return true;
471
+        }
472
+        $this->refreshedFeatures[$feature] = 1;
473
+        return false;
474
+    }
475
+
476
+    /**
477
+     * fetches the email from LDAP and stores it as Nextcloud user value
478
+     * @param ?string $valueFromLDAP if known, to save an LDAP read request
479
+     */
480
+    public function updateEmail(?string $valueFromLDAP = null): void {
481
+        if ($this->wasRefreshed('email')) {
482
+            return;
483
+        }
484
+        $email = (string)$valueFromLDAP;
485
+        if (is_null($valueFromLDAP)) {
486
+            $emailAttribute = $this->connection->ldapEmailAttribute;
487
+            if ($emailAttribute !== '') {
488
+                $aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
489
+                if (is_array($aEmail) && (count($aEmail) > 0)) {
490
+                    $email = (string)$aEmail[0];
491
+                }
492
+            }
493
+        }
494
+        if ($email !== '') {
495
+            $user = $this->userManager->get($this->uid);
496
+            if (!is_null($user)) {
497
+                $currentEmail = (string)$user->getSystemEMailAddress();
498
+                if ($currentEmail !== $email) {
499
+                    $user->setSystemEMailAddress($email);
500
+                }
501
+            }
502
+        }
503
+    }
504
+
505
+    /**
506
+     * Overall process goes as follow:
507
+     * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
508
+     * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
509
+     * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
510
+     * 4. check if the target user exists and set the quota for the user.
511
+     *
512
+     * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
513
+     * parameter can be passed with the value of the attribute. This value will be considered as the
514
+     * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
515
+     * fetch all the user's attributes in one call and use the fetched values in this function.
516
+     * The expected value for that parameter is a string describing the quota for the user. Valid
517
+     * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
518
+     * bytes), '1234 MB' (quota in MB - check the \OCP\Util::computerFileSize method for more info)
519
+     *
520
+     * fetches the quota from LDAP and stores it as Nextcloud user value
521
+     * @param ?string $valueFromLDAP the quota attribute's value can be passed,
522
+     *                               to save the readAttribute request
523
+     */
524
+    public function updateQuota(?string $valueFromLDAP = null): void {
525
+        if ($this->wasRefreshed('quota')) {
526
+            return;
527
+        }
528
+
529
+        $quotaAttribute = $this->connection->ldapQuotaAttribute;
530
+        $defaultQuota = $this->connection->ldapQuotaDefault;
531
+        if ($quotaAttribute === '' && $defaultQuota === '') {
532
+            return;
533
+        }
534
+
535
+        $quota = false;
536
+        if (is_null($valueFromLDAP) && $quotaAttribute !== '') {
537
+            $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
538
+            if ($aQuota !== false && isset($aQuota[0]) && $this->verifyQuotaValue($aQuota[0])) {
539
+                $quota = $aQuota[0];
540
+            } elseif (is_array($aQuota) && isset($aQuota[0])) {
541
+                $this->logger->debug('no suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ['app' => 'user_ldap']);
542
+            }
543
+        } elseif (!is_null($valueFromLDAP) && $this->verifyQuotaValue($valueFromLDAP)) {
544
+            $quota = $valueFromLDAP;
545
+        } else {
546
+            $this->logger->debug('no suitable LDAP quota found for user ' . $this->uid . ': [' . ($valueFromLDAP ?? '') . ']', ['app' => 'user_ldap']);
547
+        }
548
+
549
+        if ($quota === false && $this->verifyQuotaValue($defaultQuota)) {
550
+            // quota not found using the LDAP attribute (or not parseable). Try the default quota
551
+            $quota = $defaultQuota;
552
+        } elseif ($quota === false) {
553
+            $this->logger->debug('no suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ['app' => 'user_ldap']);
554
+            return;
555
+        }
556
+
557
+        $targetUser = $this->userManager->get($this->uid);
558
+        if ($targetUser instanceof IUser) {
559
+            $targetUser->setQuota($quota);
560
+        } else {
561
+            $this->logger->info('trying to set a quota for user ' . $this->uid . ' but the user is missing', ['app' => 'user_ldap']);
562
+        }
563
+    }
564
+
565
+    private function verifyQuotaValue(string $quotaValue): bool {
566
+        return $quotaValue === 'none' || $quotaValue === 'default' || \OCP\Util::computerFileSize($quotaValue) !== false;
567
+    }
568
+
569
+    /**
570
+     * takes values from LDAP and stores it as Nextcloud user profile value
571
+     *
572
+     * @param array $profileValues associative array of property keys and values from LDAP
573
+     */
574
+    private function updateProfile(array $profileValues): void {
575
+        // check if given array is empty
576
+        if (empty($profileValues)) {
577
+            return; // okay, nothing to do
578
+        }
579
+        // fetch/prepare user
580
+        $user = $this->userManager->get($this->uid);
581
+        if (is_null($user)) {
582
+            $this->logger->error('could not get user for uid=' . $this->uid . '', ['app' => 'user_ldap']);
583
+            return;
584
+        }
585
+        // prepare AccountManager and Account
586
+        $accountManager = Server::get(IAccountManager::class);
587
+        $account = $accountManager->getAccount($user);	// get Account
588
+        $defaultScopes = array_merge(AccountManager::DEFAULT_SCOPES,
589
+            $this->config->getSystemValue('account_manager.default_property_scope', []));
590
+        // loop through the properties and handle them
591
+        foreach ($profileValues as $property => $valueFromLDAP) {
592
+            // check and update profile properties
593
+            $value = (is_array($valueFromLDAP) ? $valueFromLDAP[0] : $valueFromLDAP); // take ONLY the first value, if multiple values specified
594
+            try {
595
+                $accountProperty = $account->getProperty($property);
596
+                $currentValue = $accountProperty->getValue();
597
+                $scope = ($accountProperty->getScope() ?: $defaultScopes[$property]);
598
+            } catch (PropertyDoesNotExistException $e) { // thrown at getProperty
599
+                $this->logger->error('property does not exist: ' . $property
600
+                    . ' for uid=' . $this->uid . '', ['app' => 'user_ldap', 'exception' => $e]);
601
+                $currentValue = '';
602
+                $scope = $defaultScopes[$property];
603
+            }
604
+            $verified = IAccountManager::VERIFIED; // trust the LDAP admin knew what they put there
605
+            if ($currentValue !== $value) {
606
+                $account->setProperty($property, $value, $scope, $verified);
607
+                $this->logger->debug('update user profile: ' . $property . '=' . $value
608
+                    . ' for uid=' . $this->uid . '', ['app' => 'user_ldap']);
609
+            }
610
+        }
611
+        try {
612
+            $accountManager->updateAccount($account); // may throw InvalidArgumentException
613
+        } catch (\InvalidArgumentException $e) {
614
+            $this->logger->error('invalid data from LDAP: for uid=' . $this->uid . '', ['app' => 'user_ldap', 'func' => 'updateProfile'
615
+                , 'exception' => $e]);
616
+        }
617
+    }
618
+
619
+    /**
620
+     * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
621
+     * @return bool true when the avatar was set successfully or is up to date
622
+     */
623
+    public function updateAvatar(bool $force = false): bool {
624
+        if (!$force && $this->wasRefreshed('avatar')) {
625
+            return false;
626
+        }
627
+        $avatarImage = $this->getAvatarImage();
628
+        if ($avatarImage === false) {
629
+            //not set, nothing left to do;
630
+            return false;
631
+        }
632
+
633
+        if (!$this->image->loadFromBase64(base64_encode($avatarImage))) {
634
+            return false;
635
+        }
636
+
637
+        // use the checksum before modifications
638
+        $checksum = md5($this->image->data());
639
+
640
+        if ($checksum === $this->config->getUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', '') && $this->avatarExists()) {
641
+            return true;
642
+        }
643
+
644
+        $isSet = $this->setNextcloudAvatar();
645
+
646
+        if ($isSet) {
647
+            // save checksum only after successful setting
648
+            $this->config->setUserValue($this->uid, 'user_ldap', 'lastAvatarChecksum', $checksum);
649
+        }
650
+
651
+        return $isSet;
652
+    }
653
+
654
+    private function avatarExists(): bool {
655
+        try {
656
+            $currentAvatar = $this->avatarManager->getAvatar($this->uid);
657
+            return $currentAvatar->exists() && $currentAvatar->isCustomAvatar();
658
+        } catch (\Exception $e) {
659
+            return false;
660
+        }
661
+    }
662
+
663
+    /**
664
+     * @brief sets an image as Nextcloud avatar
665
+     */
666
+    private function setNextcloudAvatar(): bool {
667
+        if (!$this->image->valid()) {
668
+            $this->logger->error('avatar image data from LDAP invalid for ' . $this->dn, ['app' => 'user_ldap']);
669
+            return false;
670
+        }
671
+
672
+
673
+        //make sure it is a square and not bigger than 512x512
674
+        $size = min([$this->image->width(), $this->image->height(), 512]);
675
+        if (!$this->image->centerCrop($size)) {
676
+            $this->logger->error('croping image for avatar failed for ' . $this->dn, ['app' => 'user_ldap']);
677
+            return false;
678
+        }
679
+
680
+        try {
681
+            $avatar = $this->avatarManager->getAvatar($this->uid);
682
+            $avatar->set($this->image);
683
+            return true;
684
+        } catch (\Exception $e) {
685
+            $this->logger->info('Could not set avatar for ' . $this->dn, ['exception' => $e]);
686
+        }
687
+        return false;
688
+    }
689
+
690
+    /**
691
+     * @throws AttributeNotSet
692
+     * @throws \OC\ServerNotAvailableException
693
+     * @throws PreConditionNotMetException
694
+     */
695
+    public function getExtStorageHome():string {
696
+        $value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
697
+        if ($value !== '') {
698
+            return $value;
699
+        }
700
+
701
+        $value = $this->updateExtStorageHome();
702
+        if ($value !== '') {
703
+            return $value;
704
+        }
705
+
706
+        throw new AttributeNotSet(sprintf(
707
+            'external home storage attribute yield no value for %s', $this->getUsername()
708
+        ));
709
+    }
710
+
711
+    /**
712
+     * @throws PreConditionNotMetException
713
+     * @throws \OC\ServerNotAvailableException
714
+     */
715
+    public function updateExtStorageHome(?string $valueFromLDAP = null):string {
716
+        if ($valueFromLDAP === null) {
717
+            $extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
718
+        } else {
719
+            $extHomeValues = [$valueFromLDAP];
720
+        }
721
+        if ($extHomeValues !== false && isset($extHomeValues[0])) {
722
+            $extHome = $extHomeValues[0];
723
+            $this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
724
+            return $extHome;
725
+        } else {
726
+            $this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
727
+            return '';
728
+        }
729
+    }
730
+
731
+    /**
732
+     * called by a post_login hook to handle password expiry
733
+     */
734
+    public function handlePasswordExpiry(array $params): void {
735
+        $ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
736
+        if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
737
+            //password expiry handling disabled
738
+            return;
739
+        }
740
+        $uid = $params['uid'];
741
+        if (isset($uid) && $uid === $this->getUsername()) {
742
+            //retrieve relevant user attributes
743
+            $result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
744
+
745
+            if (array_key_exists('pwdpolicysubentry', $result[0])) {
746
+                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
747
+                if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) {
748
+                    $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
749
+                }
750
+            }
751
+
752
+            $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : [];
753
+            $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : [];
754
+            $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : [];
755
+
756
+            //retrieve relevant password policy attributes
757
+            $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
758
+            $result = $this->connection->getFromCache($cacheKey);
759
+            if (is_null($result)) {
760
+                $result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
761
+                $this->connection->writeToCache($cacheKey, $result);
762
+            }
763
+
764
+            $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : [];
765
+            $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : [];
766
+            $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : [];
767
+
768
+            //handle grace login
769
+            if (!empty($pwdGraceUseTime)) { //was this a grace login?
770
+                if (!empty($pwdGraceAuthNLimit)
771
+                    && count($pwdGraceUseTime) < (int)$pwdGraceAuthNLimit[0]) { //at least one more grace login available?
772
+                    $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
773
+                    header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute(
774
+                        'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
775
+                } else { //no more grace login available
776
+                    header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute(
777
+                        'user_ldap.renewPassword.showLoginFormInvalidPassword', ['user' => $uid]));
778
+                }
779
+                exit();
780
+            }
781
+            //handle pwdReset attribute
782
+            if (!empty($pwdReset) && $pwdReset[0] === 'TRUE') { //user must change their password
783
+                $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
784
+                header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute(
785
+                    'user_ldap.renewPassword.showRenewPasswordForm', ['user' => $uid]));
786
+                exit();
787
+            }
788
+            //handle password expiry warning
789
+            if (!empty($pwdChangedTime)) {
790
+                if (!empty($pwdMaxAge)
791
+                    && !empty($pwdExpireWarning)) {
792
+                    $pwdMaxAgeInt = (int)$pwdMaxAge[0];
793
+                    $pwdExpireWarningInt = (int)$pwdExpireWarning[0];
794
+                    if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) {
795
+                        $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
796
+                        $pwdChangedTimeDt->add(new \DateInterval('PT' . $pwdMaxAgeInt . 'S'));
797
+                        $currentDateTime = new \DateTime();
798
+                        $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
799
+                        if ($secondsToExpiry <= $pwdExpireWarningInt) {
800
+                            //remove last password expiry warning if any
801
+                            $notification = $this->notificationManager->createNotification();
802
+                            $notification->setApp('user_ldap')
803
+                                ->setUser($uid)
804
+                                ->setObject('pwd_exp_warn', $uid)
805
+                            ;
806
+                            $this->notificationManager->markProcessed($notification);
807
+                            //create new password expiry warning
808
+                            $notification = $this->notificationManager->createNotification();
809
+                            $notification->setApp('user_ldap')
810
+                                ->setUser($uid)
811
+                                ->setDateTime($currentDateTime)
812
+                                ->setObject('pwd_exp_warn', $uid)
813
+                                ->setSubject('pwd_exp_warn_days', [(int)ceil($secondsToExpiry / 60 / 60 / 24)])
814
+                            ;
815
+                            $this->notificationManager->notify($notification);
816
+                        }
817
+                    }
818
+                }
819
+            }
820
+        }
821
+    }
822 822
 }
Please login to merge, or discard this patch.