Passed
Push — master ( 365569...3ab659 )
by Roeland
25:34 queued 11s
created
lib/private/Accounts/AccountManager.php 1 patch
Indentation   +477 added lines, -477 removed lines patch added patch discarded remove patch
@@ -58,481 +58,481 @@
 block discarded – undo
58 58
  */
59 59
 class AccountManager implements IAccountManager {
60 60
 
61
-	/** @var  IDBConnection database connection */
62
-	private $connection;
63
-
64
-	/** @var IConfig */
65
-	private $config;
66
-
67
-	/** @var string table name */
68
-	private $table = 'accounts';
69
-
70
-	/** @var string table name */
71
-	private $dataTable = 'accounts_data';
72
-
73
-	/** @var EventDispatcherInterface */
74
-	private $eventDispatcher;
75
-
76
-	/** @var IJobList */
77
-	private $jobList;
78
-
79
-	/** @var LoggerInterface */
80
-	private $logger;
81
-
82
-	public function __construct(IDBConnection $connection,
83
-								IConfig $config,
84
-								EventDispatcherInterface $eventDispatcher,
85
-								IJobList $jobList,
86
-								LoggerInterface $logger) {
87
-		$this->connection = $connection;
88
-		$this->config = $config;
89
-		$this->eventDispatcher = $eventDispatcher;
90
-		$this->jobList = $jobList;
91
-		$this->logger = $logger;
92
-	}
93
-
94
-	/**
95
-	 * @param string $input
96
-	 * @return string Provided phone number in E.164 format when it was a valid number
97
-	 * @throws \InvalidArgumentException When the phone number was invalid or no default region is set and the number doesn't start with a country code
98
-	 */
99
-	protected function parsePhoneNumber(string $input): string {
100
-		$defaultRegion = $this->config->getSystemValueString('default_phone_region', '');
101
-
102
-		if ($defaultRegion === '') {
103
-			// When no default region is set, only +49… numbers are valid
104
-			if (strpos($input, '+') !== 0) {
105
-				throw new \InvalidArgumentException(self::PROPERTY_PHONE);
106
-			}
107
-
108
-			$defaultRegion = 'EN';
109
-		}
110
-
111
-		$phoneUtil = PhoneNumberUtil::getInstance();
112
-		try {
113
-			$phoneNumber = $phoneUtil->parse($input, $defaultRegion);
114
-			if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
115
-				return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
116
-			}
117
-		} catch (NumberParseException $e) {
118
-		}
119
-
120
-		throw new \InvalidArgumentException(self::PROPERTY_PHONE);
121
-	}
122
-
123
-	/**
124
-	 * update user record
125
-	 *
126
-	 * @param IUser $user
127
-	 * @param array $data
128
-	 * @param bool $throwOnData Set to true if you can inform the user about invalid data
129
-	 * @return array The potentially modified data (e.g. phone numbers are converted to E.164 format)
130
-	 * @throws \InvalidArgumentException Message is the property that was invalid
131
-	 */
132
-	public function updateUser(IUser $user, array $data, bool $throwOnData = false): array {
133
-		$userData = $this->getUser($user);
134
-		$updated = true;
135
-
136
-		if (isset($data[self::PROPERTY_PHONE]) && $data[self::PROPERTY_PHONE]['value'] !== '') {
137
-			try {
138
-				$data[self::PROPERTY_PHONE]['value'] = $this->parsePhoneNumber($data[self::PROPERTY_PHONE]['value']);
139
-			} catch (\InvalidArgumentException $e) {
140
-				if ($throwOnData) {
141
-					throw $e;
142
-				}
143
-				$data[self::PROPERTY_PHONE]['value'] = '';
144
-			}
145
-		}
146
-
147
-		// set a max length
148
-		foreach ($data as $propertyName => $propertyData) {
149
-			if (isset($data[$propertyName]) && isset($data[$propertyName]['value']) && strlen($data[$propertyName]['value']) > 2048) {
150
-				if ($throwOnData) {
151
-					throw new \InvalidArgumentException($propertyName);
152
-				} else {
153
-					$data[$propertyName]['value'] = '';
154
-				}
155
-			}
156
-		}
157
-
158
-		$allowedScopes = [
159
-			self::SCOPE_PRIVATE,
160
-			self::SCOPE_LOCAL,
161
-			self::SCOPE_FEDERATED,
162
-			self::SCOPE_PUBLISHED,
163
-			self::VISIBILITY_PRIVATE,
164
-			self::VISIBILITY_CONTACTS_ONLY,
165
-			self::VISIBILITY_PUBLIC,
166
-		];
167
-
168
-		// validate and convert scope values
169
-		foreach ($data as $propertyName => $propertyData) {
170
-			if (isset($propertyData['scope'])) {
171
-				if ($throwOnData && !in_array($propertyData['scope'], $allowedScopes, true)) {
172
-					throw new \InvalidArgumentException('scope');
173
-				}
174
-
175
-				if (
176
-					$propertyData['scope'] === self::SCOPE_PRIVATE
177
-					&& ($propertyName === self::PROPERTY_DISPLAYNAME || $propertyName === self::PROPERTY_EMAIL)
178
-				) {
179
-					if ($throwOnData) {
180
-						// v2-private is not available for these fields
181
-						throw new \InvalidArgumentException('scope');
182
-					} else {
183
-						// default to local
184
-						$data[$propertyName]['scope'] = self::SCOPE_LOCAL;
185
-					}
186
-				} else {
187
-					// migrate scope values to the new format
188
-					// invalid scopes are mapped to a default value
189
-					$data[$propertyName]['scope'] = AccountProperty::mapScopeToV2($propertyData['scope']);
190
-				}
191
-			}
192
-		}
193
-
194
-		if (empty($userData)) {
195
-			$this->insertNewUser($user, $data);
196
-		} elseif ($userData !== $data) {
197
-			$data = $this->checkEmailVerification($userData, $data, $user);
198
-			$data = $this->updateVerifyStatus($userData, $data);
199
-			$this->updateExistingUser($user, $data);
200
-		} else {
201
-			// nothing needs to be done if new and old data set are the same
202
-			$updated = false;
203
-		}
204
-
205
-		if ($updated) {
206
-			$this->eventDispatcher->dispatch(
207
-				'OC\AccountManager::userUpdated',
208
-				new GenericEvent($user, $data)
209
-			);
210
-		}
211
-
212
-		return $data;
213
-	}
214
-
215
-	/**
216
-	 * delete user from accounts table
217
-	 *
218
-	 * @param IUser $user
219
-	 */
220
-	public function deleteUser(IUser $user) {
221
-		$uid = $user->getUID();
222
-		$query = $this->connection->getQueryBuilder();
223
-		$query->delete($this->table)
224
-			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
225
-			->execute();
226
-
227
-		$this->deleteUserData($user);
228
-	}
229
-
230
-	/**
231
-	 * delete user from accounts table
232
-	 *
233
-	 * @param IUser $user
234
-	 */
235
-	public function deleteUserData(IUser $user): void {
236
-		$uid = $user->getUID();
237
-		$query = $this->connection->getQueryBuilder();
238
-		$query->delete($this->dataTable)
239
-			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
240
-			->execute();
241
-	}
242
-
243
-	/**
244
-	 * get stored data from a given user
245
-	 *
246
-	 * @param IUser $user
247
-	 * @return array
248
-	 *
249
-	 * @deprecated use getAccount instead to make sure migrated properties work correctly
250
-	 */
251
-	public function getUser(IUser $user) {
252
-		$uid = $user->getUID();
253
-		$query = $this->connection->getQueryBuilder();
254
-		$query->select('data')
255
-			->from($this->table)
256
-			->where($query->expr()->eq('uid', $query->createParameter('uid')))
257
-			->setParameter('uid', $uid);
258
-		$result = $query->execute();
259
-		$accountData = $result->fetchAll();
260
-		$result->closeCursor();
261
-
262
-		if (empty($accountData)) {
263
-			$userData = $this->buildDefaultUserRecord($user);
264
-			$this->insertNewUser($user, $userData);
265
-			return $userData;
266
-		}
267
-
268
-		$userDataArray = json_decode($accountData[0]['data'], true);
269
-		$jsonError = json_last_error();
270
-		if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) {
271
-			$this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record");
272
-			return $this->buildDefaultUserRecord($user);
273
-		}
274
-
275
-		$userDataArray = $this->addMissingDefaultValues($userDataArray);
276
-
277
-		return $userDataArray;
278
-	}
279
-
280
-	public function searchUsers(string $property, array $values): array {
281
-		$chunks = array_chunk($values, 500);
282
-		$query = $this->connection->getQueryBuilder();
283
-		$query->select('*')
284
-			->from($this->dataTable)
285
-			->where($query->expr()->eq('name', $query->createNamedParameter($property)))
286
-			->andWhere($query->expr()->in('value', $query->createParameter('values')));
287
-
288
-		$matches = [];
289
-		foreach ($chunks as $chunk) {
290
-			$query->setParameter('values', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
291
-			$result = $query->execute();
292
-
293
-			while ($row = $result->fetch()) {
294
-				$matches[$row['value']] = $row['uid'];
295
-			}
296
-			$result->closeCursor();
297
-		}
298
-
299
-		return $matches;
300
-	}
301
-
302
-	/**
303
-	 * check if we need to ask the server for email verification, if yes we create a cronjob
304
-	 *
305
-	 * @param $oldData
306
-	 * @param $newData
307
-	 * @param IUser $user
308
-	 * @return array
309
-	 */
310
-	protected function checkEmailVerification($oldData, $newData, IUser $user) {
311
-		if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
312
-			$this->jobList->add(VerifyUserData::class,
313
-				[
314
-					'verificationCode' => '',
315
-					'data' => $newData[self::PROPERTY_EMAIL]['value'],
316
-					'type' => self::PROPERTY_EMAIL,
317
-					'uid' => $user->getUID(),
318
-					'try' => 0,
319
-					'lastRun' => time()
320
-				]
321
-			);
322
-			$newData[self::PROPERTY_EMAIL]['verified'] = self::VERIFICATION_IN_PROGRESS;
323
-		}
324
-
325
-		return $newData;
326
-	}
327
-
328
-	/**
329
-	 * make sure that all expected data are set
330
-	 *
331
-	 * @param array $userData
332
-	 * @return array
333
-	 */
334
-	protected function addMissingDefaultValues(array $userData) {
335
-		foreach ($userData as $key => $value) {
336
-			if (!isset($userData[$key]['verified'])) {
337
-				$userData[$key]['verified'] = self::NOT_VERIFIED;
338
-			}
339
-		}
340
-
341
-		return $userData;
342
-	}
343
-
344
-	/**
345
-	 * reset verification status if personal data changed
346
-	 *
347
-	 * @param array $oldData
348
-	 * @param array $newData
349
-	 * @return array
350
-	 */
351
-	protected function updateVerifyStatus($oldData, $newData) {
352
-
353
-		// which account was already verified successfully?
354
-		$twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
355
-		$websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
356
-		$emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
357
-
358
-		// keep old verification status if we don't have a new one
359
-		if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
360
-			// keep old verification status if value didn't changed and an old value exists
361
-			$keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
362
-			$newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
363
-		}
364
-
365
-		if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
366
-			// keep old verification status if value didn't changed and an old value exists
367
-			$keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
368
-			$newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
369
-		}
370
-
371
-		if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
372
-			// keep old verification status if value didn't changed and an old value exists
373
-			$keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
374
-			$newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
375
-		}
376
-
377
-		// reset verification status if a value from a previously verified data was changed
378
-		if ($twitterVerified &&
379
-			$oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
380
-		) {
381
-			$newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
382
-		}
383
-
384
-		if ($websiteVerified &&
385
-			$oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
386
-		) {
387
-			$newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
388
-		}
389
-
390
-		if ($emailVerified &&
391
-			$oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
392
-		) {
393
-			$newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
394
-		}
395
-
396
-		return $newData;
397
-	}
398
-
399
-	/**
400
-	 * add new user to accounts table
401
-	 *
402
-	 * @param IUser $user
403
-	 * @param array $data
404
-	 */
405
-	protected function insertNewUser(IUser $user, array $data): void {
406
-		$uid = $user->getUID();
407
-		$jsonEncodedData = json_encode($data);
408
-		$query = $this->connection->getQueryBuilder();
409
-		$query->insert($this->table)
410
-			->values(
411
-				[
412
-					'uid' => $query->createNamedParameter($uid),
413
-					'data' => $query->createNamedParameter($jsonEncodedData),
414
-				]
415
-			)
416
-			->execute();
417
-
418
-		$this->deleteUserData($user);
419
-		$this->writeUserData($user, $data);
420
-	}
421
-
422
-	/**
423
-	 * update existing user in accounts table
424
-	 *
425
-	 * @param IUser $user
426
-	 * @param array $data
427
-	 */
428
-	protected function updateExistingUser(IUser $user, array $data): void {
429
-		$uid = $user->getUID();
430
-		$jsonEncodedData = json_encode($data);
431
-		$query = $this->connection->getQueryBuilder();
432
-		$query->update($this->table)
433
-			->set('data', $query->createNamedParameter($jsonEncodedData))
434
-			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
435
-			->execute();
436
-
437
-		$this->deleteUserData($user);
438
-		$this->writeUserData($user, $data);
439
-	}
440
-
441
-	protected function writeUserData(IUser $user, array $data): void {
442
-		$query = $this->connection->getQueryBuilder();
443
-		$query->insert($this->dataTable)
444
-			->values(
445
-				[
446
-					'uid' => $query->createNamedParameter($user->getUID()),
447
-					'name' => $query->createParameter('name'),
448
-					'value' => $query->createParameter('value'),
449
-				]
450
-			);
451
-		foreach ($data as $propertyName => $property) {
452
-			if ($propertyName === self::PROPERTY_AVATAR) {
453
-				continue;
454
-			}
455
-
456
-			$query->setParameter('name', $propertyName)
457
-				->setParameter('value', $property['value'] ?? '');
458
-			$query->execute();
459
-		}
460
-	}
461
-
462
-	/**
463
-	 * build default user record in case not data set exists yet
464
-	 *
465
-	 * @param IUser $user
466
-	 * @return array
467
-	 */
468
-	protected function buildDefaultUserRecord(IUser $user) {
469
-		return [
470
-			self::PROPERTY_DISPLAYNAME =>
471
-				[
472
-					'value' => $user->getDisplayName(),
473
-					'scope' => self::SCOPE_FEDERATED,
474
-					'verified' => self::NOT_VERIFIED,
475
-				],
476
-			self::PROPERTY_ADDRESS =>
477
-				[
478
-					'value' => '',
479
-					'scope' => self::SCOPE_LOCAL,
480
-					'verified' => self::NOT_VERIFIED,
481
-				],
482
-			self::PROPERTY_WEBSITE =>
483
-				[
484
-					'value' => '',
485
-					'scope' => self::SCOPE_LOCAL,
486
-					'verified' => self::NOT_VERIFIED,
487
-				],
488
-			self::PROPERTY_EMAIL =>
489
-				[
490
-					'value' => $user->getEMailAddress(),
491
-					'scope' => self::SCOPE_FEDERATED,
492
-					'verified' => self::NOT_VERIFIED,
493
-				],
494
-			self::PROPERTY_AVATAR =>
495
-				[
496
-					'scope' => self::SCOPE_FEDERATED
497
-				],
498
-			self::PROPERTY_PHONE =>
499
-				[
500
-					'value' => '',
501
-					'scope' => self::SCOPE_LOCAL,
502
-					'verified' => self::NOT_VERIFIED,
503
-				],
504
-			self::PROPERTY_TWITTER =>
505
-				[
506
-					'value' => '',
507
-					'scope' => self::SCOPE_LOCAL,
508
-					'verified' => self::NOT_VERIFIED,
509
-				],
510
-		];
511
-	}
512
-
513
-	private function parseAccountData(IUser $user, $data): Account {
514
-		$account = new Account($user);
515
-		foreach ($data as $property => $accountData) {
516
-			$account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED);
517
-		}
518
-		return $account;
519
-	}
520
-
521
-	public function getAccount(IUser $user): IAccount {
522
-		return $this->parseAccountData($user, $this->getUser($user));
523
-	}
524
-
525
-	public function updateAccount(IAccount $account): void {
526
-		$data = [];
527
-
528
-		foreach ($account->getProperties() as $property) {
529
-			$data[$property->getName()] = [
530
-				'value' => $property->getValue(),
531
-				'scope' => $property->getScope(),
532
-				'verified' => $property->getVerified(),
533
-			];
534
-		}
535
-
536
-		$this->updateUser($account->getUser(), $data, true);
537
-	}
61
+    /** @var  IDBConnection database connection */
62
+    private $connection;
63
+
64
+    /** @var IConfig */
65
+    private $config;
66
+
67
+    /** @var string table name */
68
+    private $table = 'accounts';
69
+
70
+    /** @var string table name */
71
+    private $dataTable = 'accounts_data';
72
+
73
+    /** @var EventDispatcherInterface */
74
+    private $eventDispatcher;
75
+
76
+    /** @var IJobList */
77
+    private $jobList;
78
+
79
+    /** @var LoggerInterface */
80
+    private $logger;
81
+
82
+    public function __construct(IDBConnection $connection,
83
+                                IConfig $config,
84
+                                EventDispatcherInterface $eventDispatcher,
85
+                                IJobList $jobList,
86
+                                LoggerInterface $logger) {
87
+        $this->connection = $connection;
88
+        $this->config = $config;
89
+        $this->eventDispatcher = $eventDispatcher;
90
+        $this->jobList = $jobList;
91
+        $this->logger = $logger;
92
+    }
93
+
94
+    /**
95
+     * @param string $input
96
+     * @return string Provided phone number in E.164 format when it was a valid number
97
+     * @throws \InvalidArgumentException When the phone number was invalid or no default region is set and the number doesn't start with a country code
98
+     */
99
+    protected function parsePhoneNumber(string $input): string {
100
+        $defaultRegion = $this->config->getSystemValueString('default_phone_region', '');
101
+
102
+        if ($defaultRegion === '') {
103
+            // When no default region is set, only +49… numbers are valid
104
+            if (strpos($input, '+') !== 0) {
105
+                throw new \InvalidArgumentException(self::PROPERTY_PHONE);
106
+            }
107
+
108
+            $defaultRegion = 'EN';
109
+        }
110
+
111
+        $phoneUtil = PhoneNumberUtil::getInstance();
112
+        try {
113
+            $phoneNumber = $phoneUtil->parse($input, $defaultRegion);
114
+            if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
115
+                return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
116
+            }
117
+        } catch (NumberParseException $e) {
118
+        }
119
+
120
+        throw new \InvalidArgumentException(self::PROPERTY_PHONE);
121
+    }
122
+
123
+    /**
124
+     * update user record
125
+     *
126
+     * @param IUser $user
127
+     * @param array $data
128
+     * @param bool $throwOnData Set to true if you can inform the user about invalid data
129
+     * @return array The potentially modified data (e.g. phone numbers are converted to E.164 format)
130
+     * @throws \InvalidArgumentException Message is the property that was invalid
131
+     */
132
+    public function updateUser(IUser $user, array $data, bool $throwOnData = false): array {
133
+        $userData = $this->getUser($user);
134
+        $updated = true;
135
+
136
+        if (isset($data[self::PROPERTY_PHONE]) && $data[self::PROPERTY_PHONE]['value'] !== '') {
137
+            try {
138
+                $data[self::PROPERTY_PHONE]['value'] = $this->parsePhoneNumber($data[self::PROPERTY_PHONE]['value']);
139
+            } catch (\InvalidArgumentException $e) {
140
+                if ($throwOnData) {
141
+                    throw $e;
142
+                }
143
+                $data[self::PROPERTY_PHONE]['value'] = '';
144
+            }
145
+        }
146
+
147
+        // set a max length
148
+        foreach ($data as $propertyName => $propertyData) {
149
+            if (isset($data[$propertyName]) && isset($data[$propertyName]['value']) && strlen($data[$propertyName]['value']) > 2048) {
150
+                if ($throwOnData) {
151
+                    throw new \InvalidArgumentException($propertyName);
152
+                } else {
153
+                    $data[$propertyName]['value'] = '';
154
+                }
155
+            }
156
+        }
157
+
158
+        $allowedScopes = [
159
+            self::SCOPE_PRIVATE,
160
+            self::SCOPE_LOCAL,
161
+            self::SCOPE_FEDERATED,
162
+            self::SCOPE_PUBLISHED,
163
+            self::VISIBILITY_PRIVATE,
164
+            self::VISIBILITY_CONTACTS_ONLY,
165
+            self::VISIBILITY_PUBLIC,
166
+        ];
167
+
168
+        // validate and convert scope values
169
+        foreach ($data as $propertyName => $propertyData) {
170
+            if (isset($propertyData['scope'])) {
171
+                if ($throwOnData && !in_array($propertyData['scope'], $allowedScopes, true)) {
172
+                    throw new \InvalidArgumentException('scope');
173
+                }
174
+
175
+                if (
176
+                    $propertyData['scope'] === self::SCOPE_PRIVATE
177
+                    && ($propertyName === self::PROPERTY_DISPLAYNAME || $propertyName === self::PROPERTY_EMAIL)
178
+                ) {
179
+                    if ($throwOnData) {
180
+                        // v2-private is not available for these fields
181
+                        throw new \InvalidArgumentException('scope');
182
+                    } else {
183
+                        // default to local
184
+                        $data[$propertyName]['scope'] = self::SCOPE_LOCAL;
185
+                    }
186
+                } else {
187
+                    // migrate scope values to the new format
188
+                    // invalid scopes are mapped to a default value
189
+                    $data[$propertyName]['scope'] = AccountProperty::mapScopeToV2($propertyData['scope']);
190
+                }
191
+            }
192
+        }
193
+
194
+        if (empty($userData)) {
195
+            $this->insertNewUser($user, $data);
196
+        } elseif ($userData !== $data) {
197
+            $data = $this->checkEmailVerification($userData, $data, $user);
198
+            $data = $this->updateVerifyStatus($userData, $data);
199
+            $this->updateExistingUser($user, $data);
200
+        } else {
201
+            // nothing needs to be done if new and old data set are the same
202
+            $updated = false;
203
+        }
204
+
205
+        if ($updated) {
206
+            $this->eventDispatcher->dispatch(
207
+                'OC\AccountManager::userUpdated',
208
+                new GenericEvent($user, $data)
209
+            );
210
+        }
211
+
212
+        return $data;
213
+    }
214
+
215
+    /**
216
+     * delete user from accounts table
217
+     *
218
+     * @param IUser $user
219
+     */
220
+    public function deleteUser(IUser $user) {
221
+        $uid = $user->getUID();
222
+        $query = $this->connection->getQueryBuilder();
223
+        $query->delete($this->table)
224
+            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
225
+            ->execute();
226
+
227
+        $this->deleteUserData($user);
228
+    }
229
+
230
+    /**
231
+     * delete user from accounts table
232
+     *
233
+     * @param IUser $user
234
+     */
235
+    public function deleteUserData(IUser $user): void {
236
+        $uid = $user->getUID();
237
+        $query = $this->connection->getQueryBuilder();
238
+        $query->delete($this->dataTable)
239
+            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
240
+            ->execute();
241
+    }
242
+
243
+    /**
244
+     * get stored data from a given user
245
+     *
246
+     * @param IUser $user
247
+     * @return array
248
+     *
249
+     * @deprecated use getAccount instead to make sure migrated properties work correctly
250
+     */
251
+    public function getUser(IUser $user) {
252
+        $uid = $user->getUID();
253
+        $query = $this->connection->getQueryBuilder();
254
+        $query->select('data')
255
+            ->from($this->table)
256
+            ->where($query->expr()->eq('uid', $query->createParameter('uid')))
257
+            ->setParameter('uid', $uid);
258
+        $result = $query->execute();
259
+        $accountData = $result->fetchAll();
260
+        $result->closeCursor();
261
+
262
+        if (empty($accountData)) {
263
+            $userData = $this->buildDefaultUserRecord($user);
264
+            $this->insertNewUser($user, $userData);
265
+            return $userData;
266
+        }
267
+
268
+        $userDataArray = json_decode($accountData[0]['data'], true);
269
+        $jsonError = json_last_error();
270
+        if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) {
271
+            $this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record");
272
+            return $this->buildDefaultUserRecord($user);
273
+        }
274
+
275
+        $userDataArray = $this->addMissingDefaultValues($userDataArray);
276
+
277
+        return $userDataArray;
278
+    }
279
+
280
+    public function searchUsers(string $property, array $values): array {
281
+        $chunks = array_chunk($values, 500);
282
+        $query = $this->connection->getQueryBuilder();
283
+        $query->select('*')
284
+            ->from($this->dataTable)
285
+            ->where($query->expr()->eq('name', $query->createNamedParameter($property)))
286
+            ->andWhere($query->expr()->in('value', $query->createParameter('values')));
287
+
288
+        $matches = [];
289
+        foreach ($chunks as $chunk) {
290
+            $query->setParameter('values', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
291
+            $result = $query->execute();
292
+
293
+            while ($row = $result->fetch()) {
294
+                $matches[$row['value']] = $row['uid'];
295
+            }
296
+            $result->closeCursor();
297
+        }
298
+
299
+        return $matches;
300
+    }
301
+
302
+    /**
303
+     * check if we need to ask the server for email verification, if yes we create a cronjob
304
+     *
305
+     * @param $oldData
306
+     * @param $newData
307
+     * @param IUser $user
308
+     * @return array
309
+     */
310
+    protected function checkEmailVerification($oldData, $newData, IUser $user) {
311
+        if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
312
+            $this->jobList->add(VerifyUserData::class,
313
+                [
314
+                    'verificationCode' => '',
315
+                    'data' => $newData[self::PROPERTY_EMAIL]['value'],
316
+                    'type' => self::PROPERTY_EMAIL,
317
+                    'uid' => $user->getUID(),
318
+                    'try' => 0,
319
+                    'lastRun' => time()
320
+                ]
321
+            );
322
+            $newData[self::PROPERTY_EMAIL]['verified'] = self::VERIFICATION_IN_PROGRESS;
323
+        }
324
+
325
+        return $newData;
326
+    }
327
+
328
+    /**
329
+     * make sure that all expected data are set
330
+     *
331
+     * @param array $userData
332
+     * @return array
333
+     */
334
+    protected function addMissingDefaultValues(array $userData) {
335
+        foreach ($userData as $key => $value) {
336
+            if (!isset($userData[$key]['verified'])) {
337
+                $userData[$key]['verified'] = self::NOT_VERIFIED;
338
+            }
339
+        }
340
+
341
+        return $userData;
342
+    }
343
+
344
+    /**
345
+     * reset verification status if personal data changed
346
+     *
347
+     * @param array $oldData
348
+     * @param array $newData
349
+     * @return array
350
+     */
351
+    protected function updateVerifyStatus($oldData, $newData) {
352
+
353
+        // which account was already verified successfully?
354
+        $twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
355
+        $websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
356
+        $emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
357
+
358
+        // keep old verification status if we don't have a new one
359
+        if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
360
+            // keep old verification status if value didn't changed and an old value exists
361
+            $keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
362
+            $newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
363
+        }
364
+
365
+        if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
366
+            // keep old verification status if value didn't changed and an old value exists
367
+            $keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
368
+            $newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
369
+        }
370
+
371
+        if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
372
+            // keep old verification status if value didn't changed and an old value exists
373
+            $keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
374
+            $newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
375
+        }
376
+
377
+        // reset verification status if a value from a previously verified data was changed
378
+        if ($twitterVerified &&
379
+            $oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
380
+        ) {
381
+            $newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
382
+        }
383
+
384
+        if ($websiteVerified &&
385
+            $oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
386
+        ) {
387
+            $newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
388
+        }
389
+
390
+        if ($emailVerified &&
391
+            $oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
392
+        ) {
393
+            $newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
394
+        }
395
+
396
+        return $newData;
397
+    }
398
+
399
+    /**
400
+     * add new user to accounts table
401
+     *
402
+     * @param IUser $user
403
+     * @param array $data
404
+     */
405
+    protected function insertNewUser(IUser $user, array $data): void {
406
+        $uid = $user->getUID();
407
+        $jsonEncodedData = json_encode($data);
408
+        $query = $this->connection->getQueryBuilder();
409
+        $query->insert($this->table)
410
+            ->values(
411
+                [
412
+                    'uid' => $query->createNamedParameter($uid),
413
+                    'data' => $query->createNamedParameter($jsonEncodedData),
414
+                ]
415
+            )
416
+            ->execute();
417
+
418
+        $this->deleteUserData($user);
419
+        $this->writeUserData($user, $data);
420
+    }
421
+
422
+    /**
423
+     * update existing user in accounts table
424
+     *
425
+     * @param IUser $user
426
+     * @param array $data
427
+     */
428
+    protected function updateExistingUser(IUser $user, array $data): void {
429
+        $uid = $user->getUID();
430
+        $jsonEncodedData = json_encode($data);
431
+        $query = $this->connection->getQueryBuilder();
432
+        $query->update($this->table)
433
+            ->set('data', $query->createNamedParameter($jsonEncodedData))
434
+            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
435
+            ->execute();
436
+
437
+        $this->deleteUserData($user);
438
+        $this->writeUserData($user, $data);
439
+    }
440
+
441
+    protected function writeUserData(IUser $user, array $data): void {
442
+        $query = $this->connection->getQueryBuilder();
443
+        $query->insert($this->dataTable)
444
+            ->values(
445
+                [
446
+                    'uid' => $query->createNamedParameter($user->getUID()),
447
+                    'name' => $query->createParameter('name'),
448
+                    'value' => $query->createParameter('value'),
449
+                ]
450
+            );
451
+        foreach ($data as $propertyName => $property) {
452
+            if ($propertyName === self::PROPERTY_AVATAR) {
453
+                continue;
454
+            }
455
+
456
+            $query->setParameter('name', $propertyName)
457
+                ->setParameter('value', $property['value'] ?? '');
458
+            $query->execute();
459
+        }
460
+    }
461
+
462
+    /**
463
+     * build default user record in case not data set exists yet
464
+     *
465
+     * @param IUser $user
466
+     * @return array
467
+     */
468
+    protected function buildDefaultUserRecord(IUser $user) {
469
+        return [
470
+            self::PROPERTY_DISPLAYNAME =>
471
+                [
472
+                    'value' => $user->getDisplayName(),
473
+                    'scope' => self::SCOPE_FEDERATED,
474
+                    'verified' => self::NOT_VERIFIED,
475
+                ],
476
+            self::PROPERTY_ADDRESS =>
477
+                [
478
+                    'value' => '',
479
+                    'scope' => self::SCOPE_LOCAL,
480
+                    'verified' => self::NOT_VERIFIED,
481
+                ],
482
+            self::PROPERTY_WEBSITE =>
483
+                [
484
+                    'value' => '',
485
+                    'scope' => self::SCOPE_LOCAL,
486
+                    'verified' => self::NOT_VERIFIED,
487
+                ],
488
+            self::PROPERTY_EMAIL =>
489
+                [
490
+                    'value' => $user->getEMailAddress(),
491
+                    'scope' => self::SCOPE_FEDERATED,
492
+                    'verified' => self::NOT_VERIFIED,
493
+                ],
494
+            self::PROPERTY_AVATAR =>
495
+                [
496
+                    'scope' => self::SCOPE_FEDERATED
497
+                ],
498
+            self::PROPERTY_PHONE =>
499
+                [
500
+                    'value' => '',
501
+                    'scope' => self::SCOPE_LOCAL,
502
+                    'verified' => self::NOT_VERIFIED,
503
+                ],
504
+            self::PROPERTY_TWITTER =>
505
+                [
506
+                    'value' => '',
507
+                    'scope' => self::SCOPE_LOCAL,
508
+                    'verified' => self::NOT_VERIFIED,
509
+                ],
510
+        ];
511
+    }
512
+
513
+    private function parseAccountData(IUser $user, $data): Account {
514
+        $account = new Account($user);
515
+        foreach ($data as $property => $accountData) {
516
+            $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED);
517
+        }
518
+        return $account;
519
+    }
520
+
521
+    public function getAccount(IUser $user): IAccount {
522
+        return $this->parseAccountData($user, $this->getUser($user));
523
+    }
524
+
525
+    public function updateAccount(IAccount $account): void {
526
+        $data = [];
527
+
528
+        foreach ($account->getProperties() as $property) {
529
+            $data[$property->getName()] = [
530
+                'value' => $property->getValue(),
531
+                'scope' => $property->getScope(),
532
+                'verified' => $property->getVerified(),
533
+            ];
534
+        }
535
+
536
+        $this->updateUser($account->getUser(), $data, true);
537
+    }
538 538
 }
Please login to merge, or discard this patch.