Passed
Push — master ( 7e3d5d...244031 )
by Christoph
17:27 queued 12s
created

User::getManagerUids()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author John Molakvoæ <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Julius Härtl <[email protected]>
13
 * @author Leon Klingele <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Thomas Müller <[email protected]>
19
 *
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program. If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
namespace OC\User;
36
37
use InvalidArgumentException;
38
use OC\Accounts\AccountManager;
39
use OC\Avatar\AvatarManager;
40
use OC\Hooks\Emitter;
41
use OC_Helper;
42
use OCP\Accounts\IAccountManager;
43
use OCP\EventDispatcher\IEventDispatcher;
44
use OCP\Group\Events\BeforeUserRemovedEvent;
45
use OCP\Group\Events\UserRemovedEvent;
46
use OCP\IAvatarManager;
47
use OCP\IConfig;
48
use OCP\IImage;
49
use OCP\IURLGenerator;
50
use OCP\IUser;
51
use OCP\IUserBackend;
52
use OCP\User\Events\BeforeUserDeletedEvent;
53
use OCP\User\Events\UserDeletedEvent;
54
use OCP\User\GetQuotaEvent;
55
use OCP\User\Backend\ISetDisplayNameBackend;
56
use OCP\User\Backend\ISetPasswordBackend;
57
use OCP\User\Backend\IProvideAvatarBackend;
58
use OCP\User\Backend\IGetHomeBackend;
59
use OCP\UserInterface;
60
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
61
use Symfony\Component\EventDispatcher\GenericEvent;
62
use function json_decode;
63
use function json_encode;
64
65
class User implements IUser {
66
	private const CONFIG_KEY_MANAGERS = 'manager';
67
68
	/** @var IAccountManager */
69
	protected $accountManager;
70
	/** @var string */
71
	private $uid;
72
73
	/** @var string|null */
74
	private $displayName;
75
76
	/** @var UserInterface|null */
77
	private $backend;
78
	/** @var EventDispatcherInterface */
79
	private $legacyDispatcher;
80
81
	/** @var IEventDispatcher */
82
	private $dispatcher;
83
84
	/** @var bool|null */
85
	private $enabled;
86
87
	/** @var Emitter|Manager */
88
	private $emitter;
89
90
	/** @var string */
91
	private $home;
92
93
	/** @var int|null */
94
	private $lastLogin;
95
96
	/** @var \OCP\IConfig */
97
	private $config;
98
99
	/** @var IAvatarManager */
100
	private $avatarManager;
101
102
	/** @var IURLGenerator */
103
	private $urlGenerator;
104
105
	public function __construct(string $uid, ?UserInterface $backend, EventDispatcherInterface $dispatcher, $emitter = null, IConfig $config = null, $urlGenerator = null) {
106
		$this->uid = $uid;
107
		$this->backend = $backend;
108
		$this->legacyDispatcher = $dispatcher;
109
		$this->emitter = $emitter;
110
		if (is_null($config)) {
111
			$config = \OC::$server->getConfig();
112
		}
113
		$this->config = $config;
114
		$this->urlGenerator = $urlGenerator;
115
		if (is_null($this->urlGenerator)) {
116
			$this->urlGenerator = \OC::$server->getURLGenerator();
117
		}
118
		// TODO: inject
119
		$this->dispatcher = \OC::$server->query(IEventDispatcher::class);
120
	}
121
122
	/**
123
	 * get the user id
124
	 *
125
	 * @return string
126
	 */
127
	public function getUID() {
128
		return $this->uid;
129
	}
130
131
	/**
132
	 * get the display name for the user, if no specific display name is set it will fallback to the user id
133
	 *
134
	 * @return string
135
	 */
136
	public function getDisplayName() {
137
		if ($this->displayName === null) {
138
			$displayName = '';
139
			if ($this->backend && $this->backend->implementsActions(Backend::GET_DISPLAYNAME)) {
140
				// get display name and strip whitespace from the beginning and end of it
141
				$backendDisplayName = $this->backend->getDisplayName($this->uid);
142
				if (is_string($backendDisplayName)) {
0 ignored issues
show
introduced by
The condition is_string($backendDisplayName) is always true.
Loading history...
143
					$displayName = trim($backendDisplayName);
144
				}
145
			}
146
147
			if (!empty($displayName)) {
148
				$this->displayName = $displayName;
149
			} else {
150
				$this->displayName = $this->uid;
151
			}
152
		}
153
		return $this->displayName;
154
	}
155
156
	/**
157
	 * set the displayname for the user
158
	 *
159
	 * @param string $displayName
160
	 * @return bool
161
	 *
162
	 * @since 25.0.0 Throw InvalidArgumentException
163
	 * @throws \InvalidArgumentException
164
	 */
165
	public function setDisplayName($displayName) {
166
		$displayName = trim($displayName);
167
		$oldDisplayName = $this->getDisplayName();
168
		if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) {
0 ignored issues
show
Bug introduced by
The method implementsActions() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
		if ($this->backend->/** @scrutinizer ignore-call */ implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
169
			/** @var ISetDisplayNameBackend $backend */
170
			$backend = $this->backend;
171
			$result = $backend->setDisplayName($this->uid, $displayName);
172
			if ($result) {
173
				$this->displayName = $displayName;
174
				$this->triggerChange('displayName', $displayName, $oldDisplayName);
175
			}
176
			return $result !== false;
177
		}
178
		return false;
179
	}
180
181
	/**
182
	 * @inheritDoc
183
	 */
184
	public function setEMailAddress($mailAddress) {
185
		$this->setSystemEMailAddress($mailAddress);
186
	}
187
188
	/**
189
	 * @inheritDoc
190
	 */
191
	public function setSystemEMailAddress(string $mailAddress): void {
192
		$oldMailAddress = $this->getSystemEMailAddress();
193
194
		if ($mailAddress === '') {
195
			$this->config->deleteUserValue($this->uid, 'settings', 'email');
196
		} else {
197
			$this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress);
198
		}
199
200
		$primaryAddress = $this->getPrimaryEMailAddress();
201
		if ($primaryAddress === $mailAddress) {
202
			// on match no dedicated primary settings is necessary
203
			$this->setPrimaryEMailAddress('');
204
		}
205
206
		if ($oldMailAddress !== strtolower($mailAddress)) {
207
			$this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress);
208
		}
209
	}
210
211
	/**
212
	 * @inheritDoc
213
	 */
214
	public function setPrimaryEMailAddress(string $mailAddress): void {
215
		if ($mailAddress === '') {
216
			$this->config->deleteUserValue($this->uid, 'settings', 'primary_email');
217
			return;
218
		}
219
220
		$this->ensureAccountManager();
221
		$account = $this->accountManager->getAccount($this);
222
		$property = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)
223
			->getPropertyByValue($mailAddress);
224
225
		if ($property === null || $property->getLocallyVerified() !== IAccountManager::VERIFIED) {
226
			throw new InvalidArgumentException('Only verified emails can be set as primary');
227
		}
228
		$this->config->setUserValue($this->uid, 'settings', 'primary_email', $mailAddress);
229
	}
230
231
	private function ensureAccountManager() {
232
		if (!$this->accountManager instanceof IAccountManager) {
0 ignored issues
show
introduced by
$this->accountManager is always a sub-type of OCP\Accounts\IAccountManager.
Loading history...
233
			$this->accountManager = \OC::$server->get(IAccountManager::class);
234
		}
235
	}
236
237
	/**
238
	 * returns the timestamp of the user's last login or 0 if the user did never
239
	 * login
240
	 *
241
	 * @return int
242
	 */
243
	public function getLastLogin() {
244
		if ($this->lastLogin === null) {
245
			$this->lastLogin = (int) $this->config->getUserValue($this->uid, 'login', 'lastLogin', 0);
246
		}
247
		return (int) $this->lastLogin;
248
	}
249
250
	/**
251
	 * updates the timestamp of the most recent login of this user
252
	 */
253
	public function updateLastLoginTimestamp() {
254
		$previousLogin = $this->getLastLogin();
255
		$now = time();
256
		$firstTimeLogin = $previousLogin === 0;
257
258
		if ($now - $previousLogin > 60) {
259
			$this->lastLogin = time();
260
			$this->config->setUserValue(
261
				$this->uid, 'login', 'lastLogin', (string)$this->lastLogin);
262
		}
263
264
		return $firstTimeLogin;
265
	}
266
267
	/**
268
	 * Delete the user
269
	 *
270
	 * @return bool
271
	 */
272
	public function delete() {
273
		/** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
274
		$this->legacyDispatcher->dispatch(IUser::class . '::preDelete', new GenericEvent($this));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...her\GenericEvent($this). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

274
		$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
275
                           dispatch(IUser::class . '::preDelete', new GenericEvent($this));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\IUser::class . '::preDelete' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

274
		$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ IUser::class . '::preDelete', new GenericEvent($this));
Loading history...
275
		if ($this->emitter) {
276
			/** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
277
			$this->emitter->emit('\OC\User', 'preDelete', [$this]);
0 ignored issues
show
Bug introduced by
The method emit() does not exist on OC\Hooks\Emitter. It seems like you code against a sub-type of said class. However, the method does not exist in OCP\Files\IRootFolder or OC\User\Session. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

277
			$this->emitter->/** @scrutinizer ignore-call */ 
278
                   emit('\OC\User', 'preDelete', [$this]);
Loading history...
278
		}
279
		$this->dispatcher->dispatchTyped(new BeforeUserDeletedEvent($this));
280
		$result = $this->backend->deleteUser($this->uid);
281
		if ($result) {
282
			// FIXME: Feels like an hack - suggestions?
283
284
			$groupManager = \OC::$server->getGroupManager();
285
			// We have to delete the user from all groups
286
			foreach ($groupManager->getUserGroupIds($this) as $groupId) {
287
				$group = $groupManager->get($groupId);
288
				if ($group) {
289
					$this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this));
0 ignored issues
show
Deprecated Code introduced by
The class OCP\Group\Events\BeforeUserRemovedEvent has been deprecated: 20.0.0 - it can't be guaranteed that this event is triggered in all case (e.g. for LDAP users this isn't possible) - if there is a valid use case please reach out in the issue tracker at https://github.com/nextcloud/server/issues ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

289
					$this->dispatcher->dispatchTyped(/** @scrutinizer ignore-deprecated */ new BeforeUserRemovedEvent($group, $this));
Loading history...
290
					$group->removeUser($this);
291
					$this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this));
292
				}
293
			}
294
			// Delete the user's keys in preferences
295
			\OC::$server->getConfig()->deleteAllUserValues($this->uid);
296
297
			\OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid);
298
			\OC::$server->getCommentsManager()->deleteReadMarksFromUser($this);
299
300
			/** @var AvatarManager $avatarManager */
301
			$avatarManager = \OC::$server->query(AvatarManager::class);
302
			$avatarManager->deleteUserAvatar($this->uid);
303
304
			$notification = \OC::$server->getNotificationManager()->createNotification();
305
			$notification->setUser($this->uid);
306
			\OC::$server->getNotificationManager()->markProcessed($notification);
307
308
			/** @var AccountManager $accountManager */
309
			$accountManager = \OC::$server->query(AccountManager::class);
310
			$accountManager->deleteUser($this);
311
312
			/** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
313
			$this->legacyDispatcher->dispatch(IUser::class . '::postDelete', new GenericEvent($this));
314
			if ($this->emitter) {
315
				/** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
316
				$this->emitter->emit('\OC\User', 'postDelete', [$this]);
317
			}
318
			$this->dispatcher->dispatchTyped(new UserDeletedEvent($this));
319
		}
320
		return !($result === false);
321
	}
322
323
	/**
324
	 * Set the password of the user
325
	 *
326
	 * @param string $password
327
	 * @param string $recoveryPassword for the encryption app to reset encryption keys
328
	 * @return bool
329
	 */
330
	public function setPassword($password, $recoveryPassword = null) {
331
		$this->legacyDispatcher->dispatch(IUser::class . '::preSetPassword', new GenericEvent($this, [
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev... => $recoveryPassword)). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

331
		$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
332
                           dispatch(IUser::class . '::preSetPassword', new GenericEvent($this, [

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\IUser::class . '::preSetPassword' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

331
		$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ IUser::class . '::preSetPassword', new GenericEvent($this, [
Loading history...
332
			'password' => $password,
333
			'recoveryPassword' => $recoveryPassword,
334
		]));
335
		if ($this->emitter) {
336
			$this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]);
337
		}
338
		if ($this->backend->implementsActions(Backend::SET_PASSWORD)) {
339
			/** @var ISetPasswordBackend $backend */
340
			$backend = $this->backend;
341
			$result = $backend->setPassword($this->uid, $password);
342
343
			if ($result !== false) {
344
				$this->legacyDispatcher->dispatch(IUser::class . '::postSetPassword', new GenericEvent($this, [
345
					'password' => $password,
346
					'recoveryPassword' => $recoveryPassword,
347
				]));
348
				if ($this->emitter) {
349
					$this->emitter->emit('\OC\User', 'postSetPassword', [$this, $password, $recoveryPassword]);
350
				}
351
			}
352
353
			return !($result === false);
354
		} else {
355
			return false;
356
		}
357
	}
358
359
	/**
360
	 * get the users home folder to mount
361
	 *
362
	 * @return string
363
	 */
364
	public function getHome() {
365
		if (!$this->home) {
366
			/** @psalm-suppress UndefinedInterfaceMethod Once we get rid of the legacy implementsActions, psalm won't complain anymore */
367
			if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->getHome($this->uid)) {
0 ignored issues
show
Bug introduced by
The method getHome() does not exist on OCP\UserInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OCP\User\Backend\ABackend. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

367
			if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->/** @scrutinizer ignore-call */ getHome($this->uid)) {
Loading history...
368
				$this->home = $home;
0 ignored issues
show
Documentation Bug introduced by
It seems like $home can also be of type true. However, the property $home is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
369
			} elseif ($this->config) {
370
				$this->home = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid;
371
			} else {
372
				$this->home = \OC::$SERVERROOT . '/data/' . $this->uid;
373
			}
374
		}
375
		return $this->home;
376
	}
377
378
	/**
379
	 * Get the name of the backend class the user is connected with
380
	 *
381
	 * @return string
382
	 */
383
	public function getBackendClassName() {
384
		if ($this->backend instanceof IUserBackend) {
385
			return $this->backend->getBackendName();
386
		}
387
		return get_class($this->backend);
388
	}
389
390
	public function getBackend(): ?UserInterface {
391
		return $this->backend;
392
	}
393
394
	/**
395
	 * Check if the backend allows the user to change his avatar on Personal page
396
	 *
397
	 * @return bool
398
	 */
399
	public function canChangeAvatar() {
400
		if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
401
			/** @var IProvideAvatarBackend $backend */
402
			$backend = $this->backend;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->backend can also be of type OCP\User\Backend\IProvideAvatarBackend. However, the property $backend is declared as type OCP\UserInterface|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
403
			return $backend->canChangeAvatar($this->uid);
404
		}
405
		return true;
406
	}
407
408
	/**
409
	 * check if the backend supports changing passwords
410
	 *
411
	 * @return bool
412
	 */
413
	public function canChangePassword() {
414
		return $this->backend->implementsActions(Backend::SET_PASSWORD);
415
	}
416
417
	/**
418
	 * check if the backend supports changing display names
419
	 *
420
	 * @return bool
421
	 */
422
	public function canChangeDisplayName() {
423
		if (!$this->config->getSystemValueBool('allow_user_to_change_display_name', true)) {
424
			return false;
425
		}
426
		return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
427
	}
428
429
	/**
430
	 * check if the user is enabled
431
	 *
432
	 * @return bool
433
	 */
434
	public function isEnabled() {
435
		if ($this->enabled === null) {
436
			$enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
437
			$this->enabled = $enabled === 'true';
438
		}
439
		return (bool) $this->enabled;
440
	}
441
442
	/**
443
	 * set the enabled status for the user
444
	 *
445
	 * @param bool $enabled
446
	 */
447
	public function setEnabled(bool $enabled = true) {
448
		$oldStatus = $this->isEnabled();
449
		$this->enabled = $enabled;
450
		if ($oldStatus !== $this->enabled) {
451
			// TODO: First change the value, then trigger the event as done for all other properties.
452
			$this->triggerChange('enabled', $enabled, $oldStatus);
453
			$this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false');
454
		}
455
	}
456
457
	/**
458
	 * get the users email address
459
	 *
460
	 * @return string|null
461
	 * @since 9.0.0
462
	 */
463
	public function getEMailAddress() {
464
		return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress();
465
	}
466
467
	/**
468
	 * @inheritDoc
469
	 */
470
	public function getSystemEMailAddress(): ?string {
471
		return $this->config->getUserValue($this->uid, 'settings', 'email', null);
472
	}
473
474
	/**
475
	 * @inheritDoc
476
	 */
477
	public function getPrimaryEMailAddress(): ?string {
478
		return $this->config->getUserValue($this->uid, 'settings', 'primary_email', null);
479
	}
480
481
	/**
482
	 * get the users' quota
483
	 *
484
	 * @return string
485
	 * @since 9.0.0
486
	 */
487
	public function getQuota() {
488
		// allow apps to modify the user quota by hooking into the event
489
		$event = new GetQuotaEvent($this);
490
		$this->dispatcher->dispatchTyped($event);
491
		$overwriteQuota = $event->getQuota();
492
		if ($overwriteQuota) {
493
			$quota = $overwriteQuota;
494
		} else {
495
			$quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default');
496
		}
497
		if ($quota === 'default') {
498
			$quota = $this->config->getAppValue('files', 'default_quota', 'none');
499
500
			// if unlimited quota is not allowed => avoid getting 'unlimited' as default_quota fallback value
501
			// use the first preset instead
502
			$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
503
			if (!$allowUnlimitedQuota) {
504
				$presets = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
505
				$presets = array_filter(array_map('trim', explode(',', $presets)));
506
				$quotaPreset = array_values(array_diff($presets, ['default', 'none']));
507
				if (count($quotaPreset) > 0) {
508
					$quota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
509
				}
510
			}
511
		}
512
		return $quota;
513
	}
514
515
	/**
516
	 * set the users' quota
517
	 *
518
	 * @param string $quota
519
	 * @return void
520
	 * @throws InvalidArgumentException
521
	 * @since 9.0.0
522
	 */
523
	public function setQuota($quota) {
524
		$oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
525
		if ($quota !== 'none' and $quota !== 'default') {
526
			$bytesQuota = OC_Helper::computerFileSize($quota);
527
			if ($bytesQuota === false) {
528
				throw new InvalidArgumentException('Failed to set quota to invalid value '.$quota);
529
			}
530
			$quota = OC_Helper::humanFileSize($bytesQuota);
531
		}
532
		if ($quota !== $oldQuota) {
533
			$this->config->setUserValue($this->uid, 'files', 'quota', $quota);
534
			$this->triggerChange('quota', $quota, $oldQuota);
535
		}
536
		\OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
537
	}
538
539
	public function getManagerUids(): array {
540
		$encodedUids = $this->config->getUserValue(
541
			$this->uid,
542
			'settings',
543
			self::CONFIG_KEY_MANAGERS,
544
			'[]'
545
		);
546
		return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
547
	}
548
549
	public function setManagerUids(array $uids): void {
550
		$oldUids = $this->getManagerUids();
551
		$this->config->setUserValue(
552
			$this->uid,
553
			'settings',
554
			self::CONFIG_KEY_MANAGERS,
555
			json_encode($uids, JSON_THROW_ON_ERROR)
556
		);
557
		$this->triggerChange('managers', $uids, $oldUids);
558
	}
559
560
	/**
561
	 * get the avatar image if it exists
562
	 *
563
	 * @param int $size
564
	 * @return IImage|null
565
	 * @since 9.0.0
566
	 */
567
	public function getAvatarImage($size) {
568
		// delay the initialization
569
		if (is_null($this->avatarManager)) {
570
			$this->avatarManager = \OC::$server->getAvatarManager();
571
		}
572
573
		$avatar = $this->avatarManager->getAvatar($this->uid);
574
		$image = $avatar->get(-1);
575
		if ($image) {
576
			return $image;
577
		}
578
579
		return null;
580
	}
581
582
	/**
583
	 * get the federation cloud id
584
	 *
585
	 * @return string
586
	 * @since 9.0.0
587
	 */
588
	public function getCloudId() {
589
		$uid = $this->getUID();
590
		$server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
591
		if (substr($server, -10) === '/index.php') {
592
			$server = substr($server, 0, -10);
593
		}
594
		$server = $this->removeProtocolFromUrl($server);
595
		return $uid . '@' . $server;
596
	}
597
598
	private function removeProtocolFromUrl(string $url): string {
599
		if (strpos($url, 'https://') === 0) {
600
			return substr($url, strlen('https://'));
601
		}
602
603
		return $url;
604
	}
605
606
	public function triggerChange($feature, $value = null, $oldValue = null) {
607
		$this->legacyDispatcher->dispatch(IUser::class . '::changeUser', new GenericEvent($this, [
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...ldValue' => $oldValue)). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

607
		$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
608
                           dispatch(IUser::class . '::changeUser', new GenericEvent($this, [

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\IUser::class . '::changeUser' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

607
		$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ IUser::class . '::changeUser', new GenericEvent($this, [
Loading history...
608
			'feature' => $feature,
609
			'value' => $value,
610
			'oldValue' => $oldValue,
611
		]));
612
		if ($this->emitter) {
613
			$this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
614
		}
615
	}
616
}
617