Passed
Push — master ( b3cfa1...662ab9 )
by Blizzz
14:36 queued 11s
created

AccountManager::updateUser()   C

Complexity

Conditions 12
Paths 58

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 40
nc 58
nop 3
dl 0
loc 62
rs 6.9666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Björn Schießle
5
 *
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Calviño Sánchez <[email protected]>
10
 * @author Daniel Kesselberg <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Julius Härtl <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
namespace OC\Accounts;
34
35
use libphonenumber\NumberParseException;
36
use libphonenumber\PhoneNumber;
37
use libphonenumber\PhoneNumberFormat;
38
use libphonenumber\PhoneNumberUtil;
39
use OCA\Settings\BackgroundJobs\VerifyUserData;
40
use OCP\Accounts\IAccount;
41
use OCP\Accounts\IAccountManager;
42
use OCP\BackgroundJob\IJobList;
43
use OCP\DB\QueryBuilder\IQueryBuilder;
44
use OCP\IConfig;
45
use OCP\IDBConnection;
46
use OCP\IUser;
47
use Psr\Log\LoggerInterface;
48
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
49
use Symfony\Component\EventDispatcher\GenericEvent;
50
use function array_flip;
51
use function json_decode;
52
use function json_last_error;
53
54
/**
55
 * Class AccountManager
56
 *
57
 * Manage system accounts table
58
 *
59
 * @group DB
60
 * @package OC\Accounts
61
 */
62
class AccountManager implements IAccountManager {
63
	use TAccountsHelper;
64
65
	/** @var  IDBConnection database connection */
66
	private $connection;
67
68
	/** @var IConfig */
69
	private $config;
70
71
	/** @var string table name */
72
	private $table = 'accounts';
73
74
	/** @var string table name */
75
	private $dataTable = 'accounts_data';
76
77
	/** @var EventDispatcherInterface */
78
	private $eventDispatcher;
79
80
	/** @var IJobList */
81
	private $jobList;
82
83
	/** @var LoggerInterface */
84
	private $logger;
85
86
	public function __construct(IDBConnection $connection,
87
								IConfig $config,
88
								EventDispatcherInterface $eventDispatcher,
89
								IJobList $jobList,
90
								LoggerInterface $logger) {
91
		$this->connection = $connection;
92
		$this->config = $config;
93
		$this->eventDispatcher = $eventDispatcher;
94
		$this->jobList = $jobList;
95
		$this->logger = $logger;
96
	}
97
98
	/**
99
	 * @param string $input
100
	 * @return string Provided phone number in E.164 format when it was a valid number
101
	 * @throws \InvalidArgumentException When the phone number was invalid or no default region is set and the number doesn't start with a country code
102
	 */
103
	protected function parsePhoneNumber(string $input): string {
104
		$defaultRegion = $this->config->getSystemValueString('default_phone_region', '');
105
106
		if ($defaultRegion === '') {
107
			// When no default region is set, only +49… numbers are valid
108
			if (strpos($input, '+') !== 0) {
109
				throw new \InvalidArgumentException(self::PROPERTY_PHONE);
110
			}
111
112
			$defaultRegion = 'EN';
113
		}
114
115
		$phoneUtil = PhoneNumberUtil::getInstance();
116
		try {
117
			$phoneNumber = $phoneUtil->parse($input, $defaultRegion);
118
			if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
119
				return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
120
			}
121
		} catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
122
		}
123
124
		throw new \InvalidArgumentException(self::PROPERTY_PHONE);
125
	}
126
127
	/**
128
	 *
129
	 * @param string $input
130
	 * @return string
131
	 * @throws \InvalidArgumentException When the website did not have http(s) as protocol or the host name was empty
132
	 */
133
	protected function parseWebsite(string $input): string {
134
		$parts = parse_url($input);
135
		if (!isset($parts['scheme']) || ($parts['scheme'] !== 'https' && $parts['scheme'] !== 'http')) {
136
			throw new \InvalidArgumentException(self::PROPERTY_WEBSITE);
137
		}
138
139
		if (!isset($parts['host']) || $parts['host'] === '') {
140
			throw new \InvalidArgumentException(self::PROPERTY_WEBSITE);
141
		}
142
143
		return $input;
144
	}
145
146
	protected function sanitizeLength(array &$propertyData, bool $throwOnData = false): void {
147
		if (isset($propertyData['value']) && strlen($propertyData['value']) > 2048) {
148
			if ($throwOnData) {
149
				throw new \InvalidArgumentException();
150
			} else {
151
				$propertyData['value'] = '';
152
			}
153
		}
154
	}
155
156
	protected function testValueLengths(array &$data, bool $throwOnData = false): void {
157
		try {
158
			foreach ($data as $propertyName => &$propertyData) {
159
				if ($this->isCollection($propertyName)) {
160
					$this->testValueLengths($propertyData, $throwOnData);
161
				} else {
162
					$this->sanitizeLength($propertyData, $throwOnData);
163
				}
164
			}
165
		} catch (\InvalidArgumentException $e) {
166
			throw new \InvalidArgumentException($propertyName);
167
		}
168
	}
169
170
	protected function testPropertyScopes(array &$data, array $allowedScopes, bool $throwOnData = false, string $parentPropertyName = null): void {
171
		foreach ($data as $propertyNameOrIndex => &$propertyData) {
172
			if ($this->isCollection($propertyNameOrIndex)) {
173
				$this->testPropertyScopes($propertyData, $allowedScopes, $throwOnData);
174
			} elseif (isset($propertyData['scope'])) {
175
				$effectivePropertyName = $parentPropertyName ?? $propertyNameOrIndex;
176
177
				if ($throwOnData && !in_array($propertyData['scope'], $allowedScopes, true)) {
178
					throw new \InvalidArgumentException('scope');
179
				}
180
181
				if (
182
					$propertyData['scope'] === self::SCOPE_PRIVATE
183
					&& ($effectivePropertyName === self::PROPERTY_DISPLAYNAME || $effectivePropertyName === self::PROPERTY_EMAIL)
184
				) {
185
					if ($throwOnData) {
186
						// v2-private is not available for these fields
187
						throw new \InvalidArgumentException('scope');
188
					} else {
189
						// default to local
190
						$data[$propertyNameOrIndex]['scope'] = self::SCOPE_LOCAL;
191
					}
192
				} else {
193
					// migrate scope values to the new format
194
					// invalid scopes are mapped to a default value
195
					$data[$propertyNameOrIndex]['scope'] = AccountProperty::mapScopeToV2($propertyData['scope']);
196
				}
197
			}
198
		}
199
	}
200
201
	/**
202
	 * update user record
203
	 *
204
	 * @param IUser $user
205
	 * @param array $data
206
	 * @param bool $throwOnData Set to true if you can inform the user about invalid data
207
	 * @return array The potentially modified data (e.g. phone numbers are converted to E.164 format)
208
	 * @throws \InvalidArgumentException Message is the property that was invalid
209
	 */
210
	public function updateUser(IUser $user, array $data, bool $throwOnData = false): array {
211
		$userData = $this->getUser($user);
212
		$updated = true;
213
214
		if (isset($data[self::PROPERTY_PHONE]) && $data[self::PROPERTY_PHONE]['value'] !== '') {
215
			// Sanitize null value.
216
			$data[self::PROPERTY_PHONE]['value'] = $data[self::PROPERTY_PHONE]['value'] ?? '';
217
218
			try {
219
				$data[self::PROPERTY_PHONE]['value'] = $this->parsePhoneNumber($data[self::PROPERTY_PHONE]['value']);
220
			} catch (\InvalidArgumentException $e) {
221
				if ($throwOnData) {
222
					throw $e;
223
				}
224
				$data[self::PROPERTY_PHONE]['value'] = '';
225
			}
226
		}
227
228
		$this->testValueLengths($data);
229
230
		if (isset($data[self::PROPERTY_WEBSITE]) && $data[self::PROPERTY_WEBSITE]['value'] !== '') {
231
			try {
232
				$data[self::PROPERTY_WEBSITE]['value'] = $this->parseWebsite($data[self::PROPERTY_WEBSITE]['value']);
233
			} catch (\InvalidArgumentException $e) {
234
				if ($throwOnData) {
235
					throw $e;
236
				}
237
				$data[self::PROPERTY_WEBSITE]['value'] = '';
238
			}
239
		}
240
241
		$allowedScopes = [
242
			self::SCOPE_PRIVATE,
243
			self::SCOPE_LOCAL,
244
			self::SCOPE_FEDERATED,
245
			self::SCOPE_PUBLISHED,
246
			self::VISIBILITY_PRIVATE,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\Accounts\IAccountManager::VISIBILITY_PRIVATE has been deprecated: 21.0.1 ( Ignorable by Annotation )

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

246
			/** @scrutinizer ignore-deprecated */ self::VISIBILITY_PRIVATE,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
247
			self::VISIBILITY_CONTACTS_ONLY,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\Accounts\IAccountMan...ISIBILITY_CONTACTS_ONLY has been deprecated: 21.0.1 ( Ignorable by Annotation )

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

247
			/** @scrutinizer ignore-deprecated */ self::VISIBILITY_CONTACTS_ONLY,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
248
			self::VISIBILITY_PUBLIC,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\Accounts\IAccountManager::VISIBILITY_PUBLIC has been deprecated: 21.0.1 ( Ignorable by Annotation )

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

248
			/** @scrutinizer ignore-deprecated */ self::VISIBILITY_PUBLIC,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
249
		];
250
251
		$this->testPropertyScopes($data, $allowedScopes, $throwOnData);
252
253
		if (empty($userData)) {
254
			$this->insertNewUser($user, $data);
255
		} elseif ($userData !== $data) {
256
			$data = $this->checkEmailVerification($userData, $data, $user);
257
			$data = $this->updateVerifyStatus($userData, $data);
258
			$this->updateExistingUser($user, $data);
259
		} else {
260
			// nothing needs to be done if new and old data set are the same
261
			$updated = false;
262
		}
263
264
		if ($updated) {
265
			$this->eventDispatcher->dispatch(
266
				'OC\AccountManager::userUpdated',
0 ignored issues
show
Bug introduced by
'OC\AccountManager::userUpdated' 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

266
				/** @scrutinizer ignore-type */ 'OC\AccountManager::userUpdated',
Loading history...
267
				new GenericEvent($user, $data)
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...ericEvent($user, $data). ( Ignorable by Annotation )

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

267
			$this->eventDispatcher->/** @scrutinizer ignore-call */ 
268
                           dispatch(

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...
268
			);
269
		}
270
271
		return $data;
272
	}
273
274
	/**
275
	 * delete user from accounts table
276
	 *
277
	 * @param IUser $user
278
	 */
279
	public function deleteUser(IUser $user) {
280
		$uid = $user->getUID();
281
		$query = $this->connection->getQueryBuilder();
282
		$query->delete($this->table)
283
			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
284
			->execute();
285
286
		$this->deleteUserData($user);
287
	}
288
289
	/**
290
	 * delete user from accounts table
291
	 *
292
	 * @param IUser $user
293
	 */
294
	public function deleteUserData(IUser $user): void {
295
		$uid = $user->getUID();
296
		$query = $this->connection->getQueryBuilder();
297
		$query->delete($this->dataTable)
298
			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
299
			->execute();
300
	}
301
302
	/**
303
	 * get stored data from a given user
304
	 *
305
	 * @deprecated use getAccount instead to make sure migrated properties work correctly
306
	 */
307
	public function getUser(IUser $user, bool $insertIfNotExists = true): array {
308
		$uid = $user->getUID();
309
		$query = $this->connection->getQueryBuilder();
310
		$query->select('data')
311
			->from($this->table)
312
			->where($query->expr()->eq('uid', $query->createParameter('uid')))
313
			->setParameter('uid', $uid);
314
		$result = $query->execute();
315
		$accountData = $result->fetchAll();
316
		$result->closeCursor();
317
318
		if (empty($accountData)) {
319
			$userData = $this->buildDefaultUserRecord($user);
320
			if ($insertIfNotExists) {
321
				$this->insertNewUser($user, $userData);
322
			}
323
			return $userData;
324
		}
325
326
		$userDataArray = json_decode($accountData[0]['data'], true);
327
		$jsonError = json_last_error();
328
		if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) {
329
			$this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record");
330
			return $this->buildDefaultUserRecord($user);
331
		}
332
333
		return $this->addMissingDefaultValues($userDataArray);
334
	}
335
336
	public function searchUsers(string $property, array $values): array {
337
		$chunks = array_chunk($values, 500);
338
		$query = $this->connection->getQueryBuilder();
339
		$query->select('*')
340
			->from($this->dataTable)
341
			->where($query->expr()->eq('name', $query->createNamedParameter($property)))
342
			->andWhere($query->expr()->in('value', $query->createParameter('values')));
343
344
		$matches = [];
345
		foreach ($chunks as $chunk) {
346
			$query->setParameter('values', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
347
			$result = $query->execute();
348
349
			while ($row = $result->fetch()) {
350
				$matches[$row['uid']] = $row['value'];
351
			}
352
			$result->closeCursor();
353
		}
354
355
		$result = array_merge($matches, $this->searchUsersForRelatedCollection($property, $values));
356
357
		return array_flip($result);
358
	}
359
360
	protected function searchUsersForRelatedCollection(string $property, array $values): array {
361
		switch ($property) {
362
			case IAccountManager::PROPERTY_EMAIL:
363
				return array_flip($this->searchUsers(IAccountManager::COLLECTION_EMAIL, $values));
364
			default:
365
				return [];
366
		}
367
	}
368
369
	/**
370
	 * check if we need to ask the server for email verification, if yes we create a cronjob
371
	 *
372
	 * @param $oldData
373
	 * @param $newData
374
	 * @param IUser $user
375
	 * @return array
376
	 */
377
	protected function checkEmailVerification($oldData, $newData, IUser $user): array {
378
		if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
379
			$this->jobList->add(VerifyUserData::class,
380
				[
381
					'verificationCode' => '',
382
					'data' => $newData[self::PROPERTY_EMAIL]['value'],
383
					'type' => self::PROPERTY_EMAIL,
384
					'uid' => $user->getUID(),
385
					'try' => 0,
386
					'lastRun' => time()
387
				]
388
			);
389
			$newData[self::PROPERTY_EMAIL]['verified'] = self::VERIFICATION_IN_PROGRESS;
390
		}
391
392
		return $newData;
393
	}
394
395
	/**
396
	 * make sure that all expected data are set
397
	 *
398
	 * @param array $userData
399
	 * @return array
400
	 */
401
	protected function addMissingDefaultValues(array $userData) {
402
		foreach ($userData as $key => $value) {
403
			if (!isset($userData[$key]['verified'])) {
404
				$userData[$key]['verified'] = self::NOT_VERIFIED;
405
			}
406
		}
407
408
		return $userData;
409
	}
410
411
	/**
412
	 * reset verification status if personal data changed
413
	 *
414
	 * @param array $oldData
415
	 * @param array $newData
416
	 * @return array
417
	 */
418
	protected function updateVerifyStatus(array $oldData, array $newData): array {
419
420
		// which account was already verified successfully?
421
		$twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
422
		$websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
423
		$emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
424
425
		// keep old verification status if we don't have a new one
426
		if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
427
			// keep old verification status if value didn't changed and an old value exists
428
			$keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
429
			$newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
430
		}
431
432
		if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
433
			// keep old verification status if value didn't changed and an old value exists
434
			$keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
435
			$newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
436
		}
437
438
		if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
439
			// keep old verification status if value didn't changed and an old value exists
440
			$keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
441
			$newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
442
		}
443
444
		// reset verification status if a value from a previously verified data was changed
445
		if ($twitterVerified &&
446
			$oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
447
		) {
448
			$newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
449
		}
450
451
		if ($websiteVerified &&
452
			$oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
453
		) {
454
			$newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
455
		}
456
457
		if ($emailVerified &&
458
			$oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
459
		) {
460
			$newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
461
		}
462
463
		return $newData;
464
	}
465
466
	/**
467
	 * add new user to accounts table
468
	 *
469
	 * @param IUser $user
470
	 * @param array $data
471
	 */
472
	protected function insertNewUser(IUser $user, array $data): void {
473
		$uid = $user->getUID();
474
		$jsonEncodedData = json_encode($data);
475
		$query = $this->connection->getQueryBuilder();
476
		$query->insert($this->table)
477
			->values(
478
				[
479
					'uid' => $query->createNamedParameter($uid),
480
					'data' => $query->createNamedParameter($jsonEncodedData),
481
				]
482
			)
483
			->execute();
484
485
		$this->deleteUserData($user);
486
		$this->writeUserData($user, $data);
487
	}
488
489
	/**
490
	 * update existing user in accounts table
491
	 *
492
	 * @param IUser $user
493
	 * @param array $data
494
	 */
495
	protected function updateExistingUser(IUser $user, array $data): void {
496
		$uid = $user->getUID();
497
		$jsonEncodedData = json_encode($data);
498
		$query = $this->connection->getQueryBuilder();
499
		$query->update($this->table)
500
			->set('data', $query->createNamedParameter($jsonEncodedData))
501
			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
502
			->execute();
503
504
		$this->deleteUserData($user);
505
		$this->writeUserData($user, $data);
506
	}
507
508
	protected function writeUserData(IUser $user, array $data): void {
509
		$query = $this->connection->getQueryBuilder();
510
		$query->insert($this->dataTable)
511
			->values(
512
				[
513
					'uid' => $query->createNamedParameter($user->getUID()),
514
					'name' => $query->createParameter('name'),
515
					'value' => $query->createParameter('value'),
516
				]
517
			);
518
		$this->writeUserDataProperties($query, $data);
519
	}
520
521
	protected function writeUserDataProperties(IQueryBuilder $query, array $data, string $parentPropertyName = null): void {
522
		foreach ($data as $propertyName => $property) {
523
			if ($this->isCollection($propertyName)) {
524
				$this->writeUserDataProperties($query, $property, $propertyName);
525
				continue;
526
			}
527
			if (($parentPropertyName ?? $propertyName) === self::PROPERTY_AVATAR) {
528
				continue;
529
			}
530
531
			$query->setParameter('name', $parentPropertyName ?? $propertyName)
532
				->setParameter('value', $property['value'] ?? '');
533
			$query->execute();
534
		}
535
	}
536
537
	/**
538
	 * build default user record in case not data set exists yet
539
	 *
540
	 * @param IUser $user
541
	 * @return array
542
	 */
543
	protected function buildDefaultUserRecord(IUser $user) {
544
		return [
545
			self::PROPERTY_DISPLAYNAME =>
546
				[
547
					'value' => $user->getDisplayName(),
548
					'scope' => self::SCOPE_FEDERATED,
549
					'verified' => self::NOT_VERIFIED,
550
				],
551
			self::PROPERTY_ADDRESS =>
552
				[
553
					'value' => '',
554
					'scope' => self::SCOPE_LOCAL,
555
					'verified' => self::NOT_VERIFIED,
556
				],
557
			self::PROPERTY_WEBSITE =>
558
				[
559
					'value' => '',
560
					'scope' => self::SCOPE_LOCAL,
561
					'verified' => self::NOT_VERIFIED,
562
				],
563
			self::PROPERTY_EMAIL =>
564
				[
565
					'value' => $user->getEMailAddress(),
566
					'scope' => self::SCOPE_FEDERATED,
567
					'verified' => self::NOT_VERIFIED,
568
				],
569
			self::PROPERTY_AVATAR =>
570
				[
571
					'scope' => self::SCOPE_FEDERATED
572
				],
573
			self::PROPERTY_PHONE =>
574
				[
575
					'value' => '',
576
					'scope' => self::SCOPE_LOCAL,
577
					'verified' => self::NOT_VERIFIED,
578
				],
579
			self::PROPERTY_TWITTER =>
580
				[
581
					'value' => '',
582
					'scope' => self::SCOPE_LOCAL,
583
					'verified' => self::NOT_VERIFIED,
584
				],
585
		];
586
	}
587
588
	private function parseAccountData(IUser $user, $data): Account {
589
		$account = new Account($user);
590
		foreach ($data as $property => $accountData) {
591
			$account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED);
592
		}
593
		return $account;
594
	}
595
596
	public function getAccount(IUser $user): IAccount {
597
		return $this->parseAccountData($user, $this->getUser($user));
598
	}
599
600
	public function updateAccount(IAccount $account): void {
601
		$data = [];
602
603
		foreach ($account->getProperties() as $property) {
604
			$data[$property->getName()] = [
605
				'value' => $property->getValue(),
606
				'scope' => $property->getScope(),
607
				'verified' => $property->getVerified(),
608
			];
609
		}
610
611
		$this->updateUser($account->getUser(), $data, true);
612
	}
613
}
614