Completed
Pull Request — master (#3869)
by Jan-Christoph
22:50
created
apps/user_ldap/lib/User/User.php 1 patch
Indentation   +643 added lines, -643 removed lines patch added patch discarded remove patch
@@ -43,653 +43,653 @@
 block discarded – undo
43 43
  * represents an LDAP user, gets and holds user-specific information from LDAP
44 44
  */
45 45
 class User {
46
-	/**
47
-	 * @var IUserTools
48
-	 */
49
-	protected $access;
50
-	/**
51
-	 * @var Connection
52
-	 */
53
-	protected $connection;
54
-	/**
55
-	 * @var IConfig
56
-	 */
57
-	protected $config;
58
-	/**
59
-	 * @var FilesystemHelper
60
-	 */
61
-	protected $fs;
62
-	/**
63
-	 * @var Image
64
-	 */
65
-	protected $image;
66
-	/**
67
-	 * @var LogWrapper
68
-	 */
69
-	protected $log;
70
-	/**
71
-	 * @var IAvatarManager
72
-	 */
73
-	protected $avatarManager;
74
-	/**
75
-	 * @var IUserManager
76
-	 */
77
-	protected $userManager;
78
-	/**
79
-	 * @var INotificationManager
80
-	 */
81
-	protected $notificationManager;
82
-	/**
83
-	 * @var string
84
-	 */
85
-	protected $dn;
86
-	/**
87
-	 * @var string
88
-	 */
89
-	protected $uid;
90
-	/**
91
-	 * @var string[]
92
-	 */
93
-	protected $refreshedFeatures = array();
94
-	/**
95
-	 * @var string
96
-	 */
97
-	protected $avatarImage;
98
-
99
-	/**
100
-	 * DB config keys for user preferences
101
-	 */
102
-	const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
103
-	const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
104
-
105
-	/**
106
-	 * @brief constructor, make sure the subclasses call this one!
107
-	 * @param string $username the internal username
108
-	 * @param string $dn the LDAP DN
109
-	 * @param IUserTools $access an instance that implements IUserTools for
110
-	 * LDAP interaction
111
-	 * @param IConfig $config
112
-	 * @param FilesystemHelper $fs
113
-	 * @param Image $image any empty instance
114
-	 * @param LogWrapper $log
115
-	 * @param IAvatarManager $avatarManager
116
-	 * @param IUserManager $userManager
117
-	 * @param INotificationManager $notificationManager
118
-	 */
119
-	public function __construct($username, $dn, IUserTools $access,
120
-		IConfig $config, FilesystemHelper $fs, Image $image,
121
-		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
122
-		INotificationManager $notificationManager) {
46
+    /**
47
+     * @var IUserTools
48
+     */
49
+    protected $access;
50
+    /**
51
+     * @var Connection
52
+     */
53
+    protected $connection;
54
+    /**
55
+     * @var IConfig
56
+     */
57
+    protected $config;
58
+    /**
59
+     * @var FilesystemHelper
60
+     */
61
+    protected $fs;
62
+    /**
63
+     * @var Image
64
+     */
65
+    protected $image;
66
+    /**
67
+     * @var LogWrapper
68
+     */
69
+    protected $log;
70
+    /**
71
+     * @var IAvatarManager
72
+     */
73
+    protected $avatarManager;
74
+    /**
75
+     * @var IUserManager
76
+     */
77
+    protected $userManager;
78
+    /**
79
+     * @var INotificationManager
80
+     */
81
+    protected $notificationManager;
82
+    /**
83
+     * @var string
84
+     */
85
+    protected $dn;
86
+    /**
87
+     * @var string
88
+     */
89
+    protected $uid;
90
+    /**
91
+     * @var string[]
92
+     */
93
+    protected $refreshedFeatures = array();
94
+    /**
95
+     * @var string
96
+     */
97
+    protected $avatarImage;
98
+
99
+    /**
100
+     * DB config keys for user preferences
101
+     */
102
+    const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
103
+    const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
104
+
105
+    /**
106
+     * @brief constructor, make sure the subclasses call this one!
107
+     * @param string $username the internal username
108
+     * @param string $dn the LDAP DN
109
+     * @param IUserTools $access an instance that implements IUserTools for
110
+     * LDAP interaction
111
+     * @param IConfig $config
112
+     * @param FilesystemHelper $fs
113
+     * @param Image $image any empty instance
114
+     * @param LogWrapper $log
115
+     * @param IAvatarManager $avatarManager
116
+     * @param IUserManager $userManager
117
+     * @param INotificationManager $notificationManager
118
+     */
119
+    public function __construct($username, $dn, IUserTools $access,
120
+        IConfig $config, FilesystemHelper $fs, Image $image,
121
+        LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
122
+        INotificationManager $notificationManager) {
123 123
 	
124
-		if ($username === null) {
125
-			$log->log("uid for '$dn' must not be null!", Util::ERROR);
126
-			throw new \InvalidArgumentException('uid must not be null!');
127
-		} else if ($username === '') {
128
-			$log->log("uid for '$dn' must not be an empty string", Util::ERROR);
129
-			throw new \InvalidArgumentException('uid must not be an empty string!');
130
-		}
131
-
132
-		$this->access              = $access;
133
-		$this->connection          = $access->getConnection();
134
-		$this->config              = $config;
135
-		$this->fs                  = $fs;
136
-		$this->dn                  = $dn;
137
-		$this->uid                 = $username;
138
-		$this->image               = $image;
139
-		$this->log                 = $log;
140
-		$this->avatarManager       = $avatarManager;
141
-		$this->userManager         = $userManager;
142
-		$this->notificationManager = $notificationManager;
143
-
144
-		\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
145
-	}
146
-
147
-	/**
148
-	 * @brief updates properties like email, quota or avatar provided by LDAP
149
-	 * @return null
150
-	 */
151
-	public function update() {
152
-		if(is_null($this->dn)) {
153
-			return null;
154
-		}
155
-
156
-		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
157
-				self::USER_PREFKEY_FIRSTLOGIN, 0);
158
-
159
-		if($this->needsRefresh()) {
160
-			$this->updateEmail();
161
-			$this->updateQuota();
162
-			if($hasLoggedIn !== 0) {
163
-				//we do not need to try it, when the user has not been logged in
164
-				//before, because the file system will not be ready.
165
-				$this->updateAvatar();
166
-				//in order to get an avatar as soon as possible, mark the user
167
-				//as refreshed only when updating the avatar did happen
168
-				$this->markRefreshTime();
169
-			}
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * processes results from LDAP for attributes as returned by getAttributesToRead()
175
-	 * @param array $ldapEntry the user entry as retrieved from LDAP
176
-	 */
177
-	public function processAttributes($ldapEntry) {
178
-		$this->markRefreshTime();
179
-		//Quota
180
-		$attr = strtolower($this->connection->ldapQuotaAttribute);
181
-		if(isset($ldapEntry[$attr])) {
182
-			$this->updateQuota($ldapEntry[$attr][0]);
183
-		} else {
184
-			if ($this->connection->ldapQuotaDefault !== '') {
185
-				$this->updateQuota();
186
-			}
187
-		}
188
-		unset($attr);
189
-
190
-		//Email
191
-		$attr = strtolower($this->connection->ldapEmailAttribute);
192
-		if(isset($ldapEntry[$attr])) {
193
-			$this->updateEmail($ldapEntry[$attr][0]);
194
-		}
195
-		unset($attr);
196
-
197
-		//displayName
198
-		$displayName = $displayName2 = '';
199
-		$attr = strtolower($this->connection->ldapUserDisplayName);
200
-		if(isset($ldapEntry[$attr])) {
201
-			$displayName = strval($ldapEntry[$attr][0]);
202
-		}
203
-		$attr = strtolower($this->connection->ldapUserDisplayName2);
204
-		if(isset($ldapEntry[$attr])) {
205
-			$displayName2 = strval($ldapEntry[$attr][0]);
206
-		}
207
-		if ($displayName !== '') {
208
-			$this->composeAndStoreDisplayName($displayName);
209
-			$this->access->cacheUserDisplayName(
210
-				$this->getUsername(),
211
-				$displayName,
212
-				$displayName2
213
-			);
214
-		}
215
-		unset($attr);
216
-
217
-		// LDAP Username, needed for s2s sharing
218
-		if(isset($ldapEntry['uid'])) {
219
-			$this->storeLDAPUserName($ldapEntry['uid'][0]);
220
-		} else if(isset($ldapEntry['samaccountname'])) {
221
-			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
222
-		}
223
-
224
-		//homePath
225
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
226
-			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
227
-			if(isset($ldapEntry[$attr])) {
228
-				$this->access->cacheUserHome(
229
-					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
230
-			}
231
-		}
232
-
233
-		//memberOf groups
234
-		$cacheKey = 'getMemberOf'.$this->getUsername();
235
-		$groups = false;
236
-		if(isset($ldapEntry['memberof'])) {
237
-			$groups = $ldapEntry['memberof'];
238
-		}
239
-		$this->connection->writeToCache($cacheKey, $groups);
240
-
241
-		//Avatar
242
-		$attrs = array('jpegphoto', 'thumbnailphoto');
243
-		foreach ($attrs as $attr)  {
244
-			if(isset($ldapEntry[$attr])) {
245
-				$this->avatarImage = $ldapEntry[$attr][0];
246
-				// the call to the method that saves the avatar in the file
247
-				// system must be postponed after the login. It is to ensure
248
-				// external mounts are mounted properly (e.g. with login
249
-				// credentials from the session).
250
-				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
251
-				break;
252
-			}
253
-		}
254
-	}
255
-
256
-	/**
257
-	 * @brief returns the LDAP DN of the user
258
-	 * @return string
259
-	 */
260
-	public function getDN() {
261
-		return $this->dn;
262
-	}
263
-
264
-	/**
265
-	 * @brief returns the Nextcloud internal username of the user
266
-	 * @return string
267
-	 */
268
-	public function getUsername() {
269
-		return $this->uid;
270
-	}
271
-
272
-	/**
273
-	 * returns the home directory of the user if specified by LDAP settings
274
-	 * @param string $valueFromLDAP
275
-	 * @return bool|string
276
-	 * @throws \Exception
277
-	 */
278
-	public function getHomePath($valueFromLDAP = null) {
279
-		$path = strval($valueFromLDAP);
280
-		$attr = null;
281
-
282
-		if (is_null($valueFromLDAP)
283
-		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
284
-		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
285
-		{
286
-			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
287
-			$homedir = $this->access->readAttribute(
288
-				$this->access->username2dn($this->getUsername()), $attr);
289
-			if ($homedir && isset($homedir[0])) {
290
-				$path = $homedir[0];
291
-			}
292
-		}
293
-
294
-		if ($path !== '') {
295
-			//if attribute's value is an absolute path take this, otherwise append it to data dir
296
-			//check for / at the beginning or pattern c:\ resp. c:/
297
-			if(   '/' !== $path[0]
298
-			   && !(3 < strlen($path) && ctype_alpha($path[0])
299
-			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
300
-			) {
301
-				$path = $this->config->getSystemValue('datadirectory',
302
-						\OC::$SERVERROOT.'/data' ) . '/' . $path;
303
-			}
304
-			//we need it to store it in the DB as well in case a user gets
305
-			//deleted so we can clean up afterwards
306
-			$this->config->setUserValue(
307
-				$this->getUsername(), 'user_ldap', 'homePath', $path
308
-			);
309
-			return $path;
310
-		}
311
-
312
-		if(    !is_null($attr)
313
-			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
314
-		) {
315
-			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
316
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
317
-		}
318
-
319
-		//false will apply default behaviour as defined and done by OC_User
320
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
321
-		return false;
322
-	}
323
-
324
-	public function getMemberOfGroups() {
325
-		$cacheKey = 'getMemberOf'.$this->getUsername();
326
-		$memberOfGroups = $this->connection->getFromCache($cacheKey);
327
-		if(!is_null($memberOfGroups)) {
328
-			return $memberOfGroups;
329
-		}
330
-		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
331
-		$this->connection->writeToCache($cacheKey, $groupDNs);
332
-		return $groupDNs;
333
-	}
334
-
335
-	/**
336
-	 * @brief reads the image from LDAP that shall be used as Avatar
337
-	 * @return string data (provided by LDAP) | false
338
-	 */
339
-	public function getAvatarImage() {
340
-		if(!is_null($this->avatarImage)) {
341
-			return $this->avatarImage;
342
-		}
343
-
344
-		$this->avatarImage = false;
345
-		$attributes = array('jpegPhoto', 'thumbnailPhoto');
346
-		foreach($attributes as $attribute) {
347
-			$result = $this->access->readAttribute($this->dn, $attribute);
348
-			if($result !== false && is_array($result) && isset($result[0])) {
349
-				$this->avatarImage = $result[0];
350
-				break;
351
-			}
352
-		}
353
-
354
-		return $this->avatarImage;
355
-	}
356
-
357
-	/**
358
-	 * @brief marks the user as having logged in at least once
359
-	 * @return null
360
-	 */
361
-	public function markLogin() {
362
-		$this->config->setUserValue(
363
-			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
364
-	}
365
-
366
-	/**
367
-	 * @brief marks the time when user features like email have been updated
368
-	 * @return null
369
-	 */
370
-	public function markRefreshTime() {
371
-		$this->config->setUserValue(
372
-			$this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
373
-	}
374
-
375
-	/**
376
-	 * @brief checks whether user features needs to be updated again by
377
-	 * comparing the difference of time of the last refresh to now with the
378
-	 * desired interval
379
-	 * @return bool
380
-	 */
381
-	private function needsRefresh() {
382
-		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
383
-			self::USER_PREFKEY_LASTREFRESH, 0);
384
-
385
-		//TODO make interval configurable
386
-		if((time() - intval($lastChecked)) < 86400 ) {
387
-			return false;
388
-		}
389
-		return  true;
390
-	}
391
-
392
-	/**
393
-	 * Stores a key-value pair in relation to this user
394
-	 *
395
-	 * @param string $key
396
-	 * @param string $value
397
-	 */
398
-	private function store($key, $value) {
399
-		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
400
-	}
401
-
402
-	/**
403
-	 * Composes the display name and stores it in the database. The final
404
-	 * display name is returned.
405
-	 *
406
-	 * @param string $displayName
407
-	 * @param string $displayName2
408
-	 * @returns string the effective display name
409
-	 */
410
-	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
411
-		$displayName2 = strval($displayName2);
412
-		if($displayName2 !== '') {
413
-			$displayName .= ' (' . $displayName2 . ')';
414
-		}
415
-		$this->store('displayName', $displayName);
416
-		return $displayName;
417
-	}
418
-
419
-	/**
420
-	 * Stores the LDAP Username in the Database
421
-	 * @param string $userName
422
-	 */
423
-	public function storeLDAPUserName($userName) {
424
-		$this->store('uid', $userName);
425
-	}
426
-
427
-	/**
428
-	 * @brief checks whether an update method specified by feature was run
429
-	 * already. If not, it will marked like this, because it is expected that
430
-	 * the method will be run, when false is returned.
431
-	 * @param string $feature email | quota | avatar (can be extended)
432
-	 * @return bool
433
-	 */
434
-	private function wasRefreshed($feature) {
435
-		if(isset($this->refreshedFeatures[$feature])) {
436
-			return true;
437
-		}
438
-		$this->refreshedFeatures[$feature] = 1;
439
-		return false;
440
-	}
441
-
442
-	/**
443
-	 * fetches the email from LDAP and stores it as Nextcloud user value
444
-	 * @param string $valueFromLDAP if known, to save an LDAP read request
445
-	 * @return null
446
-	 */
447
-	public function updateEmail($valueFromLDAP = null) {
448
-		if($this->wasRefreshed('email')) {
449
-			return;
450
-		}
451
-		$email = strval($valueFromLDAP);
452
-		if(is_null($valueFromLDAP)) {
453
-			$emailAttribute = $this->connection->ldapEmailAttribute;
454
-			if ($emailAttribute !== '') {
455
-				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
456
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
457
-					$email = strval($aEmail[0]);
458
-				}
459
-			}
460
-		}
461
-		if ($email !== '') {
462
-			$user = $this->userManager->get($this->uid);
463
-			if (!is_null($user)) {
464
-				$currentEmail = strval($user->getEMailAddress());
465
-				if ($currentEmail !== $email) {
466
-					$user->setEMailAddress($email);
467
-				}
468
-			}
469
-		}
470
-	}
471
-
472
-	/**
473
-	 * Overall process goes as follow:
474
-	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
475
-	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
476
-	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
477
-	 * 4. check if the target user exists and set the quota for the user.
478
-	 *
479
-	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
480
-	 * parameter can be passed with the value of the attribute. This value will be considered as the
481
-	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
482
-	 * fetch all the user's attributes in one call and use the fetched values in this function.
483
-	 * The expected value for that parameter is a string describing the quota for the user. Valid
484
-	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
485
-	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
486
-	 *
487
-	 * fetches the quota from LDAP and stores it as Nextcloud user value
488
-	 * @param string $valueFromLDAP the quota attribute's value can be passed,
489
-	 * to save the readAttribute request
490
-	 * @return null
491
-	 */
492
-	public function updateQuota($valueFromLDAP = null) {
493
-		if($this->wasRefreshed('quota')) {
494
-			return;
495
-		}
496
-
497
-		$quota = false;
498
-		if(is_null($valueFromLDAP)) {
499
-			$quotaAttribute = $this->connection->ldapQuotaAttribute;
500
-			if ($quotaAttribute !== '') {
501
-				$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
502
-				if($aQuota && (count($aQuota) > 0)) {
503
-					if ($this->verifyQuotaValue($aQuota[0])) {
504
-						$quota = $aQuota[0];
505
-					} else {
506
-						$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', \OCP\Util::WARN);
507
-					}
508
-				}
509
-			}
510
-		} else {
511
-			if ($this->verifyQuotaValue($valueFromLDAP)) {
512
-				$quota = $valueFromLDAP;
513
-			} else {
514
-				$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', \OCP\Util::WARN);
515
-			}
516
-		}
517
-
518
-		if ($quota === false) {
519
-			// quota not found using the LDAP attribute (or not parseable). Try the default quota
520
-			$defaultQuota = $this->connection->ldapQuotaDefault;
521
-			if ($this->verifyQuotaValue($defaultQuota)) {
522
-				$quota = $defaultQuota;
523
-			}
524
-		}
525
-
526
-		$targetUser = $this->userManager->get($this->uid);
527
-		if ($targetUser) {
528
-			if($quota !== false) {
529
-				$targetUser->setQuota($quota);
530
-			} else {
531
-				$this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', \OCP\Util::WARN);
532
-				$targetUser->setQuota('default');
533
-			}
534
-		} else {
535
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', \OCP\Util::ERROR);
536
-		}
537
-	}
538
-
539
-	private function verifyQuotaValue($quotaValue) {
540
-		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
541
-	}
542
-
543
-	/**
544
-	 * called by a post_login hook to save the avatar picture
545
-	 *
546
-	 * @param array $params
547
-	 */
548
-	public function updateAvatarPostLogin($params) {
549
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
550
-			$this->updateAvatar();
551
-		}
552
-	}
553
-
554
-	/**
555
-	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
556
-	 * @return null
557
-	 */
558
-	public function updateAvatar() {
559
-		if($this->wasRefreshed('avatar')) {
560
-			return;
561
-		}
562
-		$avatarImage = $this->getAvatarImage();
563
-		if($avatarImage === false) {
564
-			//not set, nothing left to do;
565
-			return;
566
-		}
567
-		$this->image->loadFromBase64(base64_encode($avatarImage));
568
-		$this->setOwnCloudAvatar();
569
-	}
570
-
571
-	/**
572
-	 * @brief sets an image as Nextcloud avatar
573
-	 * @return null
574
-	 */
575
-	private function setOwnCloudAvatar() {
576
-		if(!$this->image->valid()) {
577
-			$this->log->log('jpegPhoto data invalid for '.$this->dn, \OCP\Util::ERROR);
578
-			return;
579
-		}
580
-		//make sure it is a square and not bigger than 128x128
581
-		$size = min(array($this->image->width(), $this->image->height(), 128));
582
-		if(!$this->image->centerCrop($size)) {
583
-			$this->log->log('croping image for avatar failed for '.$this->dn, \OCP\Util::ERROR);
584
-			return;
585
-		}
586
-
587
-		if(!$this->fs->isLoaded()) {
588
-			$this->fs->setup($this->uid);
589
-		}
590
-
591
-		try {
592
-			$avatar = $this->avatarManager->getAvatar($this->uid);
593
-			$avatar->set($this->image);
594
-		} catch (\Exception $e) {
595
-			\OC::$server->getLogger()->notice(
596
-				'Could not set avatar for ' . $this->dn	. ', because: ' . $e->getMessage(),
597
-				['app' => 'user_ldap']);
598
-		}
599
-	}
600
-
601
-	/**
602
-	 * called by a post_login hook to handle password expiry
603
-	 *
604
-	 * @param array $params
605
-	 */
606
-	public function handlePasswordExpiry($params) {
607
-		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
608
-		if (empty($ppolicyDN) || (intval($this->connection->turnOnPasswordChange) !== 1)) {
609
-			return;//password expiry handling disabled
610
-		}
611
-		$uid = $params['uid'];
612
-		if(isset($uid) && $uid === $this->getUsername()) {
613
-			//retrieve relevant user attributes
614
-			$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
124
+        if ($username === null) {
125
+            $log->log("uid for '$dn' must not be null!", Util::ERROR);
126
+            throw new \InvalidArgumentException('uid must not be null!');
127
+        } else if ($username === '') {
128
+            $log->log("uid for '$dn' must not be an empty string", Util::ERROR);
129
+            throw new \InvalidArgumentException('uid must not be an empty string!');
130
+        }
131
+
132
+        $this->access              = $access;
133
+        $this->connection          = $access->getConnection();
134
+        $this->config              = $config;
135
+        $this->fs                  = $fs;
136
+        $this->dn                  = $dn;
137
+        $this->uid                 = $username;
138
+        $this->image               = $image;
139
+        $this->log                 = $log;
140
+        $this->avatarManager       = $avatarManager;
141
+        $this->userManager         = $userManager;
142
+        $this->notificationManager = $notificationManager;
143
+
144
+        \OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
145
+    }
146
+
147
+    /**
148
+     * @brief updates properties like email, quota or avatar provided by LDAP
149
+     * @return null
150
+     */
151
+    public function update() {
152
+        if(is_null($this->dn)) {
153
+            return null;
154
+        }
155
+
156
+        $hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
157
+                self::USER_PREFKEY_FIRSTLOGIN, 0);
158
+
159
+        if($this->needsRefresh()) {
160
+            $this->updateEmail();
161
+            $this->updateQuota();
162
+            if($hasLoggedIn !== 0) {
163
+                //we do not need to try it, when the user has not been logged in
164
+                //before, because the file system will not be ready.
165
+                $this->updateAvatar();
166
+                //in order to get an avatar as soon as possible, mark the user
167
+                //as refreshed only when updating the avatar did happen
168
+                $this->markRefreshTime();
169
+            }
170
+        }
171
+    }
172
+
173
+    /**
174
+     * processes results from LDAP for attributes as returned by getAttributesToRead()
175
+     * @param array $ldapEntry the user entry as retrieved from LDAP
176
+     */
177
+    public function processAttributes($ldapEntry) {
178
+        $this->markRefreshTime();
179
+        //Quota
180
+        $attr = strtolower($this->connection->ldapQuotaAttribute);
181
+        if(isset($ldapEntry[$attr])) {
182
+            $this->updateQuota($ldapEntry[$attr][0]);
183
+        } else {
184
+            if ($this->connection->ldapQuotaDefault !== '') {
185
+                $this->updateQuota();
186
+            }
187
+        }
188
+        unset($attr);
189
+
190
+        //Email
191
+        $attr = strtolower($this->connection->ldapEmailAttribute);
192
+        if(isset($ldapEntry[$attr])) {
193
+            $this->updateEmail($ldapEntry[$attr][0]);
194
+        }
195
+        unset($attr);
196
+
197
+        //displayName
198
+        $displayName = $displayName2 = '';
199
+        $attr = strtolower($this->connection->ldapUserDisplayName);
200
+        if(isset($ldapEntry[$attr])) {
201
+            $displayName = strval($ldapEntry[$attr][0]);
202
+        }
203
+        $attr = strtolower($this->connection->ldapUserDisplayName2);
204
+        if(isset($ldapEntry[$attr])) {
205
+            $displayName2 = strval($ldapEntry[$attr][0]);
206
+        }
207
+        if ($displayName !== '') {
208
+            $this->composeAndStoreDisplayName($displayName);
209
+            $this->access->cacheUserDisplayName(
210
+                $this->getUsername(),
211
+                $displayName,
212
+                $displayName2
213
+            );
214
+        }
215
+        unset($attr);
216
+
217
+        // LDAP Username, needed for s2s sharing
218
+        if(isset($ldapEntry['uid'])) {
219
+            $this->storeLDAPUserName($ldapEntry['uid'][0]);
220
+        } else if(isset($ldapEntry['samaccountname'])) {
221
+            $this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
222
+        }
223
+
224
+        //homePath
225
+        if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
226
+            $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
227
+            if(isset($ldapEntry[$attr])) {
228
+                $this->access->cacheUserHome(
229
+                    $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
230
+            }
231
+        }
232
+
233
+        //memberOf groups
234
+        $cacheKey = 'getMemberOf'.$this->getUsername();
235
+        $groups = false;
236
+        if(isset($ldapEntry['memberof'])) {
237
+            $groups = $ldapEntry['memberof'];
238
+        }
239
+        $this->connection->writeToCache($cacheKey, $groups);
240
+
241
+        //Avatar
242
+        $attrs = array('jpegphoto', 'thumbnailphoto');
243
+        foreach ($attrs as $attr)  {
244
+            if(isset($ldapEntry[$attr])) {
245
+                $this->avatarImage = $ldapEntry[$attr][0];
246
+                // the call to the method that saves the avatar in the file
247
+                // system must be postponed after the login. It is to ensure
248
+                // external mounts are mounted properly (e.g. with login
249
+                // credentials from the session).
250
+                \OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
251
+                break;
252
+            }
253
+        }
254
+    }
255
+
256
+    /**
257
+     * @brief returns the LDAP DN of the user
258
+     * @return string
259
+     */
260
+    public function getDN() {
261
+        return $this->dn;
262
+    }
263
+
264
+    /**
265
+     * @brief returns the Nextcloud internal username of the user
266
+     * @return string
267
+     */
268
+    public function getUsername() {
269
+        return $this->uid;
270
+    }
271
+
272
+    /**
273
+     * returns the home directory of the user if specified by LDAP settings
274
+     * @param string $valueFromLDAP
275
+     * @return bool|string
276
+     * @throws \Exception
277
+     */
278
+    public function getHomePath($valueFromLDAP = null) {
279
+        $path = strval($valueFromLDAP);
280
+        $attr = null;
281
+
282
+        if (is_null($valueFromLDAP)
283
+           && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
284
+           && $this->access->connection->homeFolderNamingRule !== 'attr:')
285
+        {
286
+            $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
287
+            $homedir = $this->access->readAttribute(
288
+                $this->access->username2dn($this->getUsername()), $attr);
289
+            if ($homedir && isset($homedir[0])) {
290
+                $path = $homedir[0];
291
+            }
292
+        }
293
+
294
+        if ($path !== '') {
295
+            //if attribute's value is an absolute path take this, otherwise append it to data dir
296
+            //check for / at the beginning or pattern c:\ resp. c:/
297
+            if(   '/' !== $path[0]
298
+               && !(3 < strlen($path) && ctype_alpha($path[0])
299
+                   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
300
+            ) {
301
+                $path = $this->config->getSystemValue('datadirectory',
302
+                        \OC::$SERVERROOT.'/data' ) . '/' . $path;
303
+            }
304
+            //we need it to store it in the DB as well in case a user gets
305
+            //deleted so we can clean up afterwards
306
+            $this->config->setUserValue(
307
+                $this->getUsername(), 'user_ldap', 'homePath', $path
308
+            );
309
+            return $path;
310
+        }
311
+
312
+        if(    !is_null($attr)
313
+            && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
314
+        ) {
315
+            // a naming rule attribute is defined, but it doesn't exist for that LDAP user
316
+            throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
317
+        }
318
+
319
+        //false will apply default behaviour as defined and done by OC_User
320
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
321
+        return false;
322
+    }
323
+
324
+    public function getMemberOfGroups() {
325
+        $cacheKey = 'getMemberOf'.$this->getUsername();
326
+        $memberOfGroups = $this->connection->getFromCache($cacheKey);
327
+        if(!is_null($memberOfGroups)) {
328
+            return $memberOfGroups;
329
+        }
330
+        $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
331
+        $this->connection->writeToCache($cacheKey, $groupDNs);
332
+        return $groupDNs;
333
+    }
334
+
335
+    /**
336
+     * @brief reads the image from LDAP that shall be used as Avatar
337
+     * @return string data (provided by LDAP) | false
338
+     */
339
+    public function getAvatarImage() {
340
+        if(!is_null($this->avatarImage)) {
341
+            return $this->avatarImage;
342
+        }
343
+
344
+        $this->avatarImage = false;
345
+        $attributes = array('jpegPhoto', 'thumbnailPhoto');
346
+        foreach($attributes as $attribute) {
347
+            $result = $this->access->readAttribute($this->dn, $attribute);
348
+            if($result !== false && is_array($result) && isset($result[0])) {
349
+                $this->avatarImage = $result[0];
350
+                break;
351
+            }
352
+        }
353
+
354
+        return $this->avatarImage;
355
+    }
356
+
357
+    /**
358
+     * @brief marks the user as having logged in at least once
359
+     * @return null
360
+     */
361
+    public function markLogin() {
362
+        $this->config->setUserValue(
363
+            $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
364
+    }
365
+
366
+    /**
367
+     * @brief marks the time when user features like email have been updated
368
+     * @return null
369
+     */
370
+    public function markRefreshTime() {
371
+        $this->config->setUserValue(
372
+            $this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
373
+    }
374
+
375
+    /**
376
+     * @brief checks whether user features needs to be updated again by
377
+     * comparing the difference of time of the last refresh to now with the
378
+     * desired interval
379
+     * @return bool
380
+     */
381
+    private function needsRefresh() {
382
+        $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
383
+            self::USER_PREFKEY_LASTREFRESH, 0);
384
+
385
+        //TODO make interval configurable
386
+        if((time() - intval($lastChecked)) < 86400 ) {
387
+            return false;
388
+        }
389
+        return  true;
390
+    }
391
+
392
+    /**
393
+     * Stores a key-value pair in relation to this user
394
+     *
395
+     * @param string $key
396
+     * @param string $value
397
+     */
398
+    private function store($key, $value) {
399
+        $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
400
+    }
401
+
402
+    /**
403
+     * Composes the display name and stores it in the database. The final
404
+     * display name is returned.
405
+     *
406
+     * @param string $displayName
407
+     * @param string $displayName2
408
+     * @returns string the effective display name
409
+     */
410
+    public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
411
+        $displayName2 = strval($displayName2);
412
+        if($displayName2 !== '') {
413
+            $displayName .= ' (' . $displayName2 . ')';
414
+        }
415
+        $this->store('displayName', $displayName);
416
+        return $displayName;
417
+    }
418
+
419
+    /**
420
+     * Stores the LDAP Username in the Database
421
+     * @param string $userName
422
+     */
423
+    public function storeLDAPUserName($userName) {
424
+        $this->store('uid', $userName);
425
+    }
426
+
427
+    /**
428
+     * @brief checks whether an update method specified by feature was run
429
+     * already. If not, it will marked like this, because it is expected that
430
+     * the method will be run, when false is returned.
431
+     * @param string $feature email | quota | avatar (can be extended)
432
+     * @return bool
433
+     */
434
+    private function wasRefreshed($feature) {
435
+        if(isset($this->refreshedFeatures[$feature])) {
436
+            return true;
437
+        }
438
+        $this->refreshedFeatures[$feature] = 1;
439
+        return false;
440
+    }
441
+
442
+    /**
443
+     * fetches the email from LDAP and stores it as Nextcloud user value
444
+     * @param string $valueFromLDAP if known, to save an LDAP read request
445
+     * @return null
446
+     */
447
+    public function updateEmail($valueFromLDAP = null) {
448
+        if($this->wasRefreshed('email')) {
449
+            return;
450
+        }
451
+        $email = strval($valueFromLDAP);
452
+        if(is_null($valueFromLDAP)) {
453
+            $emailAttribute = $this->connection->ldapEmailAttribute;
454
+            if ($emailAttribute !== '') {
455
+                $aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
456
+                if(is_array($aEmail) && (count($aEmail) > 0)) {
457
+                    $email = strval($aEmail[0]);
458
+                }
459
+            }
460
+        }
461
+        if ($email !== '') {
462
+            $user = $this->userManager->get($this->uid);
463
+            if (!is_null($user)) {
464
+                $currentEmail = strval($user->getEMailAddress());
465
+                if ($currentEmail !== $email) {
466
+                    $user->setEMailAddress($email);
467
+                }
468
+            }
469
+        }
470
+    }
471
+
472
+    /**
473
+     * Overall process goes as follow:
474
+     * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
475
+     * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
476
+     * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
477
+     * 4. check if the target user exists and set the quota for the user.
478
+     *
479
+     * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
480
+     * parameter can be passed with the value of the attribute. This value will be considered as the
481
+     * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
482
+     * fetch all the user's attributes in one call and use the fetched values in this function.
483
+     * The expected value for that parameter is a string describing the quota for the user. Valid
484
+     * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
485
+     * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
486
+     *
487
+     * fetches the quota from LDAP and stores it as Nextcloud user value
488
+     * @param string $valueFromLDAP the quota attribute's value can be passed,
489
+     * to save the readAttribute request
490
+     * @return null
491
+     */
492
+    public function updateQuota($valueFromLDAP = null) {
493
+        if($this->wasRefreshed('quota')) {
494
+            return;
495
+        }
496
+
497
+        $quota = false;
498
+        if(is_null($valueFromLDAP)) {
499
+            $quotaAttribute = $this->connection->ldapQuotaAttribute;
500
+            if ($quotaAttribute !== '') {
501
+                $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
502
+                if($aQuota && (count($aQuota) > 0)) {
503
+                    if ($this->verifyQuotaValue($aQuota[0])) {
504
+                        $quota = $aQuota[0];
505
+                    } else {
506
+                        $this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', \OCP\Util::WARN);
507
+                    }
508
+                }
509
+            }
510
+        } else {
511
+            if ($this->verifyQuotaValue($valueFromLDAP)) {
512
+                $quota = $valueFromLDAP;
513
+            } else {
514
+                $this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', \OCP\Util::WARN);
515
+            }
516
+        }
517
+
518
+        if ($quota === false) {
519
+            // quota not found using the LDAP attribute (or not parseable). Try the default quota
520
+            $defaultQuota = $this->connection->ldapQuotaDefault;
521
+            if ($this->verifyQuotaValue($defaultQuota)) {
522
+                $quota = $defaultQuota;
523
+            }
524
+        }
525
+
526
+        $targetUser = $this->userManager->get($this->uid);
527
+        if ($targetUser) {
528
+            if($quota !== false) {
529
+                $targetUser->setQuota($quota);
530
+            } else {
531
+                $this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', \OCP\Util::WARN);
532
+                $targetUser->setQuota('default');
533
+            }
534
+        } else {
535
+            $this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', \OCP\Util::ERROR);
536
+        }
537
+    }
538
+
539
+    private function verifyQuotaValue($quotaValue) {
540
+        return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
541
+    }
542
+
543
+    /**
544
+     * called by a post_login hook to save the avatar picture
545
+     *
546
+     * @param array $params
547
+     */
548
+    public function updateAvatarPostLogin($params) {
549
+        if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
550
+            $this->updateAvatar();
551
+        }
552
+    }
553
+
554
+    /**
555
+     * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
556
+     * @return null
557
+     */
558
+    public function updateAvatar() {
559
+        if($this->wasRefreshed('avatar')) {
560
+            return;
561
+        }
562
+        $avatarImage = $this->getAvatarImage();
563
+        if($avatarImage === false) {
564
+            //not set, nothing left to do;
565
+            return;
566
+        }
567
+        $this->image->loadFromBase64(base64_encode($avatarImage));
568
+        $this->setOwnCloudAvatar();
569
+    }
570
+
571
+    /**
572
+     * @brief sets an image as Nextcloud avatar
573
+     * @return null
574
+     */
575
+    private function setOwnCloudAvatar() {
576
+        if(!$this->image->valid()) {
577
+            $this->log->log('jpegPhoto data invalid for '.$this->dn, \OCP\Util::ERROR);
578
+            return;
579
+        }
580
+        //make sure it is a square and not bigger than 128x128
581
+        $size = min(array($this->image->width(), $this->image->height(), 128));
582
+        if(!$this->image->centerCrop($size)) {
583
+            $this->log->log('croping image for avatar failed for '.$this->dn, \OCP\Util::ERROR);
584
+            return;
585
+        }
586
+
587
+        if(!$this->fs->isLoaded()) {
588
+            $this->fs->setup($this->uid);
589
+        }
590
+
591
+        try {
592
+            $avatar = $this->avatarManager->getAvatar($this->uid);
593
+            $avatar->set($this->image);
594
+        } catch (\Exception $e) {
595
+            \OC::$server->getLogger()->notice(
596
+                'Could not set avatar for ' . $this->dn	. ', because: ' . $e->getMessage(),
597
+                ['app' => 'user_ldap']);
598
+        }
599
+    }
600
+
601
+    /**
602
+     * called by a post_login hook to handle password expiry
603
+     *
604
+     * @param array $params
605
+     */
606
+    public function handlePasswordExpiry($params) {
607
+        $ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
608
+        if (empty($ppolicyDN) || (intval($this->connection->turnOnPasswordChange) !== 1)) {
609
+            return;//password expiry handling disabled
610
+        }
611
+        $uid = $params['uid'];
612
+        if(isset($uid) && $uid === $this->getUsername()) {
613
+            //retrieve relevant user attributes
614
+            $result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
615 615
 			
616
-			if(array_key_exists('pwdpolicysubentry', $result[0])) {
617
-				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
618
-				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
619
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
620
-				}
621
-			}
616
+            if(array_key_exists('pwdpolicysubentry', $result[0])) {
617
+                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
618
+                if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
619
+                    $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
620
+                }
621
+            }
622 622
 			
623
-			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
624
-			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
625
-			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
623
+            $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
624
+            $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
625
+            $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
626 626
 			
627
-			//retrieve relevant password policy attributes
628
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
629
-			$result = $this->connection->getFromCache($cacheKey);
630
-			if(is_null($result)) {
631
-				$result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
632
-				$this->connection->writeToCache($cacheKey, $result);
633
-			}
627
+            //retrieve relevant password policy attributes
628
+            $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
629
+            $result = $this->connection->getFromCache($cacheKey);
630
+            if(is_null($result)) {
631
+                $result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
632
+                $this->connection->writeToCache($cacheKey, $result);
633
+            }
634 634
 			
635
-			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
636
-			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
637
-			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
635
+            $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
636
+            $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
637
+            $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
638 638
 			
639
-			//handle grace login
640
-			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
641
-			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
642
-				if($pwdGraceAuthNLimit 
643
-					&& (count($pwdGraceAuthNLimit) > 0)
644
-					&&($pwdGraceUseTimeCount < intval($pwdGraceAuthNLimit[0]))) { //at least one more grace login available?
645
-					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
646
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
647
-					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
648
-				} else { //no more grace login available
649
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
650
-					'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
651
-				}
652
-				exit();
653
-			}
654
-			//handle pwdReset attribute
655
-			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
656
-				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
657
-				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
658
-				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
659
-				exit();
660
-			}
661
-			//handle password expiry warning
662
-			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
663
-				if($pwdMaxAge && (count($pwdMaxAge) > 0)
664
-					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
665
-					$pwdMaxAgeInt = intval($pwdMaxAge[0]);
666
-					$pwdExpireWarningInt = intval($pwdExpireWarning[0]);
667
-					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
668
-						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
669
-						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
670
-						$currentDateTime = new \DateTime();
671
-						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
672
-						if($secondsToExpiry <= $pwdExpireWarningInt) {
673
-							//remove last password expiry warning if any
674
-							$notification = $this->notificationManager->createNotification();
675
-							$notification->setApp('user_ldap')
676
-								->setUser($uid)
677
-								->setObject('pwd_exp_warn', $uid)
678
-							;
679
-							$this->notificationManager->markProcessed($notification);
680
-							//create new password expiry warning
681
-							$notification = $this->notificationManager->createNotification();
682
-							$notification->setApp('user_ldap')
683
-								->setUser($uid)
684
-								->setDateTime($currentDateTime)
685
-								->setObject('pwd_exp_warn', $uid) 
686
-								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
687
-							;
688
-							$this->notificationManager->notify($notification);
689
-						}
690
-					}
691
-				}
692
-			}
693
-		}
694
-	}
639
+            //handle grace login
640
+            $pwdGraceUseTimeCount = count($pwdGraceUseTime);
641
+            if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
642
+                if($pwdGraceAuthNLimit 
643
+                    && (count($pwdGraceAuthNLimit) > 0)
644
+                    &&($pwdGraceUseTimeCount < intval($pwdGraceAuthNLimit[0]))) { //at least one more grace login available?
645
+                    $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
646
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
647
+                    'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
648
+                } else { //no more grace login available
649
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
650
+                    'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
651
+                }
652
+                exit();
653
+            }
654
+            //handle pwdReset attribute
655
+            if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
656
+                $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
657
+                header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
658
+                'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
659
+                exit();
660
+            }
661
+            //handle password expiry warning
662
+            if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
663
+                if($pwdMaxAge && (count($pwdMaxAge) > 0)
664
+                    && $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
665
+                    $pwdMaxAgeInt = intval($pwdMaxAge[0]);
666
+                    $pwdExpireWarningInt = intval($pwdExpireWarning[0]);
667
+                    if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
668
+                        $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
669
+                        $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
670
+                        $currentDateTime = new \DateTime();
671
+                        $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
672
+                        if($secondsToExpiry <= $pwdExpireWarningInt) {
673
+                            //remove last password expiry warning if any
674
+                            $notification = $this->notificationManager->createNotification();
675
+                            $notification->setApp('user_ldap')
676
+                                ->setUser($uid)
677
+                                ->setObject('pwd_exp_warn', $uid)
678
+                            ;
679
+                            $this->notificationManager->markProcessed($notification);
680
+                            //create new password expiry warning
681
+                            $notification = $this->notificationManager->createNotification();
682
+                            $notification->setApp('user_ldap')
683
+                                ->setUser($uid)
684
+                                ->setDateTime($currentDateTime)
685
+                                ->setObject('pwd_exp_warn', $uid) 
686
+                                ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
687
+                            ;
688
+                            $this->notificationManager->notify($notification);
689
+                        }
690
+                    }
691
+                }
692
+            }
693
+        }
694
+    }
695 695
 }
Please login to merge, or discard this patch.
settings/Controller/MailSettingsController.php 1 patch
Indentation   +131 added lines, -131 removed lines patch added patch discarded remove patch
@@ -39,136 +39,136 @@
 block discarded – undo
39 39
  */
40 40
 class MailSettingsController extends Controller {
41 41
 
42
-	/** @var IL10N */
43
-	private $l10n;
44
-	/** @var IConfig */
45
-	private $config;
46
-	/** @var IUserSession */
47
-	private $userSession;
48
-	/** @var IMailer */
49
-	private $mailer;
50
-	/** @var string */
51
-	private $defaultMailAddress;
52
-
53
-	/**
54
-	 * @param string $appName
55
-	 * @param IRequest $request
56
-	 * @param IL10N $l10n
57
-	 * @param IConfig $config
58
-	 * @param IUserSession $userSession
59
-	 * @param IMailer $mailer
60
-	 * @param string $fromMailAddress
61
-	 */
62
-	public function __construct($appName,
63
-								IRequest $request,
64
-								IL10N $l10n,
65
-								IConfig $config,
66
-								IUserSession $userSession,
67
-								IMailer $mailer,
68
-								$fromMailAddress) {
69
-		parent::__construct($appName, $request);
70
-		$this->l10n = $l10n;
71
-		$this->config = $config;
72
-		$this->userSession = $userSession;
73
-		$this->mailer = $mailer;
74
-		$this->defaultMailAddress = $fromMailAddress;
75
-	}
76
-
77
-	/**
78
-	 * Sets the email settings
79
-	 *
80
-	 * @PasswordConfirmationRequired
81
-	 *
82
-	 * @param string $mail_domain
83
-	 * @param string $mail_from_address
84
-	 * @param string $mail_smtpmode
85
-	 * @param string $mail_smtpsecure
86
-	 * @param string $mail_smtphost
87
-	 * @param string $mail_smtpauthtype
88
-	 * @param int $mail_smtpauth
89
-	 * @param string $mail_smtpport
90
-	 * @return DataResponse
91
-	 */
92
-	public function setMailSettings($mail_domain,
93
-									$mail_from_address,
94
-									$mail_smtpmode,
95
-									$mail_smtpsecure,
96
-									$mail_smtphost,
97
-									$mail_smtpauthtype,
98
-									$mail_smtpauth,
99
-									$mail_smtpport) {
100
-
101
-		$params = get_defined_vars();
102
-		$configs = [];
103
-		foreach($params as $key => $value) {
104
-			$configs[$key] = (empty($value)) ? null : $value;
105
-		}
106
-
107
-		// Delete passwords from config in case no auth is specified
108
-		if ($params['mail_smtpauth'] !== 1) {
109
-			$configs['mail_smtpname'] = null;
110
-			$configs['mail_smtppassword'] = null;
111
-		}
112
-
113
-		$this->config->setSystemValues($configs);
114
-
115
-		return new DataResponse();
116
-	}
117
-
118
-	/**
119
-	 * Store the credentials used for SMTP in the config
120
-	 *
121
-	 * @PasswordConfirmationRequired
122
-	 *
123
-	 * @param string $mail_smtpname
124
-	 * @param string $mail_smtppassword
125
-	 * @return DataResponse
126
-	 */
127
-	public function storeCredentials($mail_smtpname, $mail_smtppassword) {
128
-		if ($mail_smtppassword === '********') {
129
-			return new DataResponse($this->l10n->t('Invalid SMTP password.'), Http::STATUS_BAD_REQUEST);
130
-		}
131
-
132
-		$this->config->setSystemValues([
133
-			'mail_smtpname'		=> $mail_smtpname,
134
-			'mail_smtppassword'	=> $mail_smtppassword,
135
-		]);
136
-
137
-		return new DataResponse();
138
-	}
139
-
140
-	/**
141
-	 * Send a mail to test the settings
142
-	 * @return DataResponse
143
-	 */
144
-	public function sendTestMail() {
145
-		$email = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'email', '');
146
-		if (!empty($email)) {
147
-			try {
148
-				$displayName = $this->userSession->getUser()->getDisplayName();
149
-
150
-				$template = $this->mailer->createEMailTemplate();
151
-				$template->addHeader();
152
-				$template->addHeading($this->l10n->t('Well done, %s!', [$displayName]));
153
-				$template->addBodyText($this->l10n->t('If you received this email, the email configuration seems to be correct.'));
154
-				$template->addFooter();
155
-
156
-				$message = $this->mailer->createMessage();
157
-				$message->setTo([$email => $displayName]);
158
-				$message->setSubject($this->l10n->t('Email setting test'));
159
-				$message->setHtmlBody($template->renderHtml());
160
-				$message->setPlainBody($template->renderText());
161
-				$errors = $this->mailer->send($message);
162
-				if (!empty($errors)) {
163
-					throw new \RuntimeException($this->l10n->t('Email could not be sent. Check your mail server log'));
164
-				}
165
-				return new DataResponse();
166
-			} catch (\Exception $e) {
167
-				return new DataResponse($this->l10n->t('A problem occurred while sending the email. Please revise your settings. (Error: %s)', [$e->getMessage()]), Http::STATUS_BAD_REQUEST);
168
-			}
169
-		}
170
-
171
-		return new DataResponse($this->l10n->t('You need to set your user email before being able to send test emails.'), Http::STATUS_BAD_REQUEST);
172
-	}
42
+    /** @var IL10N */
43
+    private $l10n;
44
+    /** @var IConfig */
45
+    private $config;
46
+    /** @var IUserSession */
47
+    private $userSession;
48
+    /** @var IMailer */
49
+    private $mailer;
50
+    /** @var string */
51
+    private $defaultMailAddress;
52
+
53
+    /**
54
+     * @param string $appName
55
+     * @param IRequest $request
56
+     * @param IL10N $l10n
57
+     * @param IConfig $config
58
+     * @param IUserSession $userSession
59
+     * @param IMailer $mailer
60
+     * @param string $fromMailAddress
61
+     */
62
+    public function __construct($appName,
63
+                                IRequest $request,
64
+                                IL10N $l10n,
65
+                                IConfig $config,
66
+                                IUserSession $userSession,
67
+                                IMailer $mailer,
68
+                                $fromMailAddress) {
69
+        parent::__construct($appName, $request);
70
+        $this->l10n = $l10n;
71
+        $this->config = $config;
72
+        $this->userSession = $userSession;
73
+        $this->mailer = $mailer;
74
+        $this->defaultMailAddress = $fromMailAddress;
75
+    }
76
+
77
+    /**
78
+     * Sets the email settings
79
+     *
80
+     * @PasswordConfirmationRequired
81
+     *
82
+     * @param string $mail_domain
83
+     * @param string $mail_from_address
84
+     * @param string $mail_smtpmode
85
+     * @param string $mail_smtpsecure
86
+     * @param string $mail_smtphost
87
+     * @param string $mail_smtpauthtype
88
+     * @param int $mail_smtpauth
89
+     * @param string $mail_smtpport
90
+     * @return DataResponse
91
+     */
92
+    public function setMailSettings($mail_domain,
93
+                                    $mail_from_address,
94
+                                    $mail_smtpmode,
95
+                                    $mail_smtpsecure,
96
+                                    $mail_smtphost,
97
+                                    $mail_smtpauthtype,
98
+                                    $mail_smtpauth,
99
+                                    $mail_smtpport) {
100
+
101
+        $params = get_defined_vars();
102
+        $configs = [];
103
+        foreach($params as $key => $value) {
104
+            $configs[$key] = (empty($value)) ? null : $value;
105
+        }
106
+
107
+        // Delete passwords from config in case no auth is specified
108
+        if ($params['mail_smtpauth'] !== 1) {
109
+            $configs['mail_smtpname'] = null;
110
+            $configs['mail_smtppassword'] = null;
111
+        }
112
+
113
+        $this->config->setSystemValues($configs);
114
+
115
+        return new DataResponse();
116
+    }
117
+
118
+    /**
119
+     * Store the credentials used for SMTP in the config
120
+     *
121
+     * @PasswordConfirmationRequired
122
+     *
123
+     * @param string $mail_smtpname
124
+     * @param string $mail_smtppassword
125
+     * @return DataResponse
126
+     */
127
+    public function storeCredentials($mail_smtpname, $mail_smtppassword) {
128
+        if ($mail_smtppassword === '********') {
129
+            return new DataResponse($this->l10n->t('Invalid SMTP password.'), Http::STATUS_BAD_REQUEST);
130
+        }
131
+
132
+        $this->config->setSystemValues([
133
+            'mail_smtpname'		=> $mail_smtpname,
134
+            'mail_smtppassword'	=> $mail_smtppassword,
135
+        ]);
136
+
137
+        return new DataResponse();
138
+    }
139
+
140
+    /**
141
+     * Send a mail to test the settings
142
+     * @return DataResponse
143
+     */
144
+    public function sendTestMail() {
145
+        $email = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'email', '');
146
+        if (!empty($email)) {
147
+            try {
148
+                $displayName = $this->userSession->getUser()->getDisplayName();
149
+
150
+                $template = $this->mailer->createEMailTemplate();
151
+                $template->addHeader();
152
+                $template->addHeading($this->l10n->t('Well done, %s!', [$displayName]));
153
+                $template->addBodyText($this->l10n->t('If you received this email, the email configuration seems to be correct.'));
154
+                $template->addFooter();
155
+
156
+                $message = $this->mailer->createMessage();
157
+                $message->setTo([$email => $displayName]);
158
+                $message->setSubject($this->l10n->t('Email setting test'));
159
+                $message->setHtmlBody($template->renderHtml());
160
+                $message->setPlainBody($template->renderText());
161
+                $errors = $this->mailer->send($message);
162
+                if (!empty($errors)) {
163
+                    throw new \RuntimeException($this->l10n->t('Email could not be sent. Check your mail server log'));
164
+                }
165
+                return new DataResponse();
166
+            } catch (\Exception $e) {
167
+                return new DataResponse($this->l10n->t('A problem occurred while sending the email. Please revise your settings. (Error: %s)', [$e->getMessage()]), Http::STATUS_BAD_REQUEST);
168
+            }
169
+        }
170
+
171
+        return new DataResponse($this->l10n->t('You need to set your user email before being able to send test emails.'), Http::STATUS_BAD_REQUEST);
172
+    }
173 173
 
174 174
 }
Please login to merge, or discard this patch.
lib/private/Accounts/AccountManager.php 2 patches
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -195,38 +195,38 @@
 block discarded – undo
195 195
 		$emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
196 196
 
197 197
 		// keep old verification status if we don't have a new one
198
-		if(!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
198
+		if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
199 199
 			// keep old verification status if value didn't changed and an old value exists
200 200
 			$keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
201 201
 			$newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
202 202
 		}
203 203
 
204
-		if(!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
204
+		if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
205 205
 			// keep old verification status if value didn't changed and an old value exists
206 206
 			$keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
207 207
 			$newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
208 208
 		}
209 209
 
210
-		if(!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
210
+		if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
211 211
 			// keep old verification status if value didn't changed and an old value exists
212 212
 			$keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
213 213
 			$newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
214 214
 		}
215 215
 
216 216
 		// reset verification status if a value from a previously verified data was changed
217
-		if($twitterVerified &&
217
+		if ($twitterVerified &&
218 218
 			$oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
219 219
 		) {
220 220
 			$newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
221 221
 		}
222 222
 
223
-		if($websiteVerified &&
223
+		if ($websiteVerified &&
224 224
 			$oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
225 225
 		) {
226 226
 			$newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
227 227
 		}
228 228
 
229
-		if($emailVerified &&
229
+		if ($emailVerified &&
230 230
 			$oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
231 231
 		) {
232 232
 			$newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
Please login to merge, or discard this patch.
Indentation   +283 added lines, -283 removed lines patch added patch discarded remove patch
@@ -39,288 +39,288 @@
 block discarded – undo
39 39
  */
40 40
 class AccountManager {
41 41
 
42
-	/** nobody can see my account details */
43
-	const VISIBILITY_PRIVATE = 'private';
44
-	/** only contacts, especially trusted servers can see my contact details */
45
-	const VISIBILITY_CONTACTS_ONLY = 'contacts';
46
-	/** every body ca see my contact detail, will be published to the lookup server */
47
-	const VISIBILITY_PUBLIC = 'public';
48
-
49
-	const PROPERTY_AVATAR = 'avatar';
50
-	const PROPERTY_DISPLAYNAME = 'displayname';
51
-	const PROPERTY_PHONE = 'phone';
52
-	const PROPERTY_EMAIL = 'email';
53
-	const PROPERTY_WEBSITE = 'website';
54
-	const PROPERTY_ADDRESS = 'address';
55
-	const PROPERTY_TWITTER = 'twitter';
56
-
57
-	const NOT_VERIFIED = '0';
58
-	const VERIFICATION_IN_PROGRESS = '1';
59
-	const VERIFIED = '2';
60
-
61
-	/** @var  IDBConnection database connection */
62
-	private $connection;
63
-
64
-	/** @var string table name */
65
-	private $table = 'accounts';
66
-
67
-	/** @var EventDispatcherInterface */
68
-	private $eventDispatcher;
69
-
70
-	/** @var IJobList */
71
-	private $jobList;
72
-
73
-	/**
74
-	 * AccountManager constructor.
75
-	 *
76
-	 * @param IDBConnection $connection
77
-	 * @param EventDispatcherInterface $eventDispatcher
78
-	 * @param IJobList $jobList
79
-	 */
80
-	public function __construct(IDBConnection $connection,
81
-								EventDispatcherInterface $eventDispatcher,
82
-								IJobList $jobList) {
83
-		$this->connection = $connection;
84
-		$this->eventDispatcher = $eventDispatcher;
85
-		$this->jobList = $jobList;
86
-	}
87
-
88
-	/**
89
-	 * update user record
90
-	 *
91
-	 * @param IUser $user
92
-	 * @param $data
93
-	 */
94
-	public function updateUser(IUser $user, $data) {
95
-		$userData = $this->getUser($user);
96
-		$updated = true;
97
-		if (empty($userData)) {
98
-			$this->insertNewUser($user, $data);
99
-		} elseif ($userData !== $data) {
100
-			$this->checkEmailVerification($userData, $data, $user);
101
-			$data = $this->updateVerifyStatus($userData, $data);
102
-			$this->updateExistingUser($user, $data);
103
-		} else {
104
-			// nothing needs to be done if new and old data set are the same
105
-			$updated = false;
106
-		}
107
-
108
-		if ($updated) {
109
-			$this->eventDispatcher->dispatch(
110
-				'OC\AccountManager::userUpdated',
111
-				new GenericEvent($user, $data)
112
-			);
113
-		}
114
-	}
115
-
116
-	/**
117
-	 * get stored data from a given user
118
-	 *
119
-	 * @param IUser $user
120
-	 * @return array
121
-	 */
122
-	public function getUser(IUser $user) {
123
-		$uid = $user->getUID();
124
-		$query = $this->connection->getQueryBuilder();
125
-		$query->select('data')->from($this->table)
126
-			->where($query->expr()->eq('uid', $query->createParameter('uid')))
127
-			->setParameter('uid', $uid);
128
-		$query->execute();
129
-		$result = $query->execute()->fetchAll();
130
-
131
-		if (empty($result)) {
132
-			$userData = $this->buildDefaultUserRecord($user);
133
-			$this->insertNewUser($user, $userData);
134
-			return $userData;
135
-		}
136
-
137
-		$userDataArray = json_decode($result[0]['data'], true);
138
-
139
-		$userDataArray = $this->addMissingDefaultValues($userDataArray);
140
-
141
-		return $userDataArray;
142
-	}
143
-
144
-	/**
145
-	 * check if we need to ask the server for email verification, if yes we create a cronjob
146
-	 *
147
-	 * @param $oldData
148
-	 * @param $newData
149
-	 * @param IUser $user
150
-	 */
151
-	protected function checkEmailVerification($oldData, $newData, IUser $user) {
152
-		if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
153
-			$this->jobList->add('OC\Settings\BackgroundJobs\VerifyUserData',
154
-				[
155
-					'verificationCode' => '',
156
-					'data' => $newData[self::PROPERTY_EMAIL]['value'],
157
-					'type' => self::PROPERTY_EMAIL,
158
-					'uid' => $user->getUID(),
159
-					'try' => 0,
160
-					'lastRun' => time()
161
-				]
162
-			);
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * make sure that all expected data are set
168
-	 *
169
-	 * @param array $userData
170
-	 * @return array
171
-	 */
172
-	protected function addMissingDefaultValues(array $userData) {
173
-
174
-		foreach ($userData as $key => $value) {
175
-			if (!isset($userData[$key]['verified'])) {
176
-				$userData[$key]['verified'] = self::NOT_VERIFIED;
177
-			}
178
-		}
179
-
180
-		return $userData;
181
-	}
182
-
183
-	/**
184
-	 * reset verification status if personal data changed
185
-	 *
186
-	 * @param array $oldData
187
-	 * @param array $newData
188
-	 * @return array
189
-	 */
190
-	protected function updateVerifyStatus($oldData, $newData) {
191
-
192
-		// which account was already verified successfully?
193
-		$twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
194
-		$websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
195
-		$emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
196
-
197
-		// keep old verification status if we don't have a new one
198
-		if(!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
199
-			// keep old verification status if value didn't changed and an old value exists
200
-			$keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
201
-			$newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
202
-		}
203
-
204
-		if(!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
205
-			// keep old verification status if value didn't changed and an old value exists
206
-			$keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
207
-			$newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
208
-		}
209
-
210
-		if(!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
211
-			// keep old verification status if value didn't changed and an old value exists
212
-			$keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
213
-			$newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
214
-		}
215
-
216
-		// reset verification status if a value from a previously verified data was changed
217
-		if($twitterVerified &&
218
-			$oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
219
-		) {
220
-			$newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
221
-		}
222
-
223
-		if($websiteVerified &&
224
-			$oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
225
-		) {
226
-			$newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
227
-		}
228
-
229
-		if($emailVerified &&
230
-			$oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
231
-		) {
232
-			$newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
233
-		}
234
-
235
-		return $newData;
236
-
237
-	}
238
-
239
-	/**
240
-	 * add new user to accounts table
241
-	 *
242
-	 * @param IUser $user
243
-	 * @param array $data
244
-	 */
245
-	protected function insertNewUser(IUser $user, $data) {
246
-		$uid = $user->getUID();
247
-		$jsonEncodedData = json_encode($data);
248
-		$query = $this->connection->getQueryBuilder();
249
-		$query->insert($this->table)
250
-			->values(
251
-				[
252
-					'uid' => $query->createNamedParameter($uid),
253
-					'data' => $query->createNamedParameter($jsonEncodedData),
254
-				]
255
-			)
256
-			->execute();
257
-	}
258
-
259
-	/**
260
-	 * update existing user in accounts table
261
-	 *
262
-	 * @param IUser $user
263
-	 * @param array $data
264
-	 */
265
-	protected function updateExistingUser(IUser $user, $data) {
266
-		$uid = $user->getUID();
267
-		$jsonEncodedData = json_encode($data);
268
-		$query = $this->connection->getQueryBuilder();
269
-		$query->update($this->table)
270
-			->set('data', $query->createNamedParameter($jsonEncodedData))
271
-			->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
272
-			->execute();
273
-	}
274
-
275
-	/**
276
-	 * build default user record in case not data set exists yet
277
-	 *
278
-	 * @param IUser $user
279
-	 * @return array
280
-	 */
281
-	protected function buildDefaultUserRecord(IUser $user) {
282
-		return [
283
-			self::PROPERTY_DISPLAYNAME =>
284
-				[
285
-					'value' => $user->getDisplayName(),
286
-					'scope' => self::VISIBILITY_CONTACTS_ONLY,
287
-					'verified' => self::NOT_VERIFIED,
288
-				],
289
-			self::PROPERTY_ADDRESS =>
290
-				[
291
-					'value' => '',
292
-					'scope' => self::VISIBILITY_PRIVATE,
293
-					'verified' => self::NOT_VERIFIED,
294
-				],
295
-			self::PROPERTY_WEBSITE =>
296
-				[
297
-					'value' => '',
298
-					'scope' => self::VISIBILITY_PRIVATE,
299
-					'verified' => self::NOT_VERIFIED,
300
-				],
301
-			self::PROPERTY_EMAIL =>
302
-				[
303
-					'value' => $user->getEMailAddress(),
304
-					'scope' => self::VISIBILITY_CONTACTS_ONLY,
305
-					'verified' => self::NOT_VERIFIED,
306
-				],
307
-			self::PROPERTY_AVATAR =>
308
-				[
309
-					'scope' => self::VISIBILITY_CONTACTS_ONLY
310
-				],
311
-			self::PROPERTY_PHONE =>
312
-				[
313
-					'value' => '',
314
-					'scope' => self::VISIBILITY_PRIVATE,
315
-					'verified' => self::NOT_VERIFIED,
316
-				],
317
-			self::PROPERTY_TWITTER =>
318
-				[
319
-					'value' => '',
320
-					'scope' => self::VISIBILITY_PRIVATE,
321
-					'verified' => self::NOT_VERIFIED,
322
-				],
323
-		];
324
-	}
42
+    /** nobody can see my account details */
43
+    const VISIBILITY_PRIVATE = 'private';
44
+    /** only contacts, especially trusted servers can see my contact details */
45
+    const VISIBILITY_CONTACTS_ONLY = 'contacts';
46
+    /** every body ca see my contact detail, will be published to the lookup server */
47
+    const VISIBILITY_PUBLIC = 'public';
48
+
49
+    const PROPERTY_AVATAR = 'avatar';
50
+    const PROPERTY_DISPLAYNAME = 'displayname';
51
+    const PROPERTY_PHONE = 'phone';
52
+    const PROPERTY_EMAIL = 'email';
53
+    const PROPERTY_WEBSITE = 'website';
54
+    const PROPERTY_ADDRESS = 'address';
55
+    const PROPERTY_TWITTER = 'twitter';
56
+
57
+    const NOT_VERIFIED = '0';
58
+    const VERIFICATION_IN_PROGRESS = '1';
59
+    const VERIFIED = '2';
60
+
61
+    /** @var  IDBConnection database connection */
62
+    private $connection;
63
+
64
+    /** @var string table name */
65
+    private $table = 'accounts';
66
+
67
+    /** @var EventDispatcherInterface */
68
+    private $eventDispatcher;
69
+
70
+    /** @var IJobList */
71
+    private $jobList;
72
+
73
+    /**
74
+     * AccountManager constructor.
75
+     *
76
+     * @param IDBConnection $connection
77
+     * @param EventDispatcherInterface $eventDispatcher
78
+     * @param IJobList $jobList
79
+     */
80
+    public function __construct(IDBConnection $connection,
81
+                                EventDispatcherInterface $eventDispatcher,
82
+                                IJobList $jobList) {
83
+        $this->connection = $connection;
84
+        $this->eventDispatcher = $eventDispatcher;
85
+        $this->jobList = $jobList;
86
+    }
87
+
88
+    /**
89
+     * update user record
90
+     *
91
+     * @param IUser $user
92
+     * @param $data
93
+     */
94
+    public function updateUser(IUser $user, $data) {
95
+        $userData = $this->getUser($user);
96
+        $updated = true;
97
+        if (empty($userData)) {
98
+            $this->insertNewUser($user, $data);
99
+        } elseif ($userData !== $data) {
100
+            $this->checkEmailVerification($userData, $data, $user);
101
+            $data = $this->updateVerifyStatus($userData, $data);
102
+            $this->updateExistingUser($user, $data);
103
+        } else {
104
+            // nothing needs to be done if new and old data set are the same
105
+            $updated = false;
106
+        }
107
+
108
+        if ($updated) {
109
+            $this->eventDispatcher->dispatch(
110
+                'OC\AccountManager::userUpdated',
111
+                new GenericEvent($user, $data)
112
+            );
113
+        }
114
+    }
115
+
116
+    /**
117
+     * get stored data from a given user
118
+     *
119
+     * @param IUser $user
120
+     * @return array
121
+     */
122
+    public function getUser(IUser $user) {
123
+        $uid = $user->getUID();
124
+        $query = $this->connection->getQueryBuilder();
125
+        $query->select('data')->from($this->table)
126
+            ->where($query->expr()->eq('uid', $query->createParameter('uid')))
127
+            ->setParameter('uid', $uid);
128
+        $query->execute();
129
+        $result = $query->execute()->fetchAll();
130
+
131
+        if (empty($result)) {
132
+            $userData = $this->buildDefaultUserRecord($user);
133
+            $this->insertNewUser($user, $userData);
134
+            return $userData;
135
+        }
136
+
137
+        $userDataArray = json_decode($result[0]['data'], true);
138
+
139
+        $userDataArray = $this->addMissingDefaultValues($userDataArray);
140
+
141
+        return $userDataArray;
142
+    }
143
+
144
+    /**
145
+     * check if we need to ask the server for email verification, if yes we create a cronjob
146
+     *
147
+     * @param $oldData
148
+     * @param $newData
149
+     * @param IUser $user
150
+     */
151
+    protected function checkEmailVerification($oldData, $newData, IUser $user) {
152
+        if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
153
+            $this->jobList->add('OC\Settings\BackgroundJobs\VerifyUserData',
154
+                [
155
+                    'verificationCode' => '',
156
+                    'data' => $newData[self::PROPERTY_EMAIL]['value'],
157
+                    'type' => self::PROPERTY_EMAIL,
158
+                    'uid' => $user->getUID(),
159
+                    'try' => 0,
160
+                    'lastRun' => time()
161
+                ]
162
+            );
163
+        }
164
+    }
165
+
166
+    /**
167
+     * make sure that all expected data are set
168
+     *
169
+     * @param array $userData
170
+     * @return array
171
+     */
172
+    protected function addMissingDefaultValues(array $userData) {
173
+
174
+        foreach ($userData as $key => $value) {
175
+            if (!isset($userData[$key]['verified'])) {
176
+                $userData[$key]['verified'] = self::NOT_VERIFIED;
177
+            }
178
+        }
179
+
180
+        return $userData;
181
+    }
182
+
183
+    /**
184
+     * reset verification status if personal data changed
185
+     *
186
+     * @param array $oldData
187
+     * @param array $newData
188
+     * @return array
189
+     */
190
+    protected function updateVerifyStatus($oldData, $newData) {
191
+
192
+        // which account was already verified successfully?
193
+        $twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
194
+        $websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
195
+        $emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
196
+
197
+        // keep old verification status if we don't have a new one
198
+        if(!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
199
+            // keep old verification status if value didn't changed and an old value exists
200
+            $keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
201
+            $newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
202
+        }
203
+
204
+        if(!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
205
+            // keep old verification status if value didn't changed and an old value exists
206
+            $keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
207
+            $newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
208
+        }
209
+
210
+        if(!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
211
+            // keep old verification status if value didn't changed and an old value exists
212
+            $keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
213
+            $newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
214
+        }
215
+
216
+        // reset verification status if a value from a previously verified data was changed
217
+        if($twitterVerified &&
218
+            $oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
219
+        ) {
220
+            $newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
221
+        }
222
+
223
+        if($websiteVerified &&
224
+            $oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
225
+        ) {
226
+            $newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
227
+        }
228
+
229
+        if($emailVerified &&
230
+            $oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
231
+        ) {
232
+            $newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
233
+        }
234
+
235
+        return $newData;
236
+
237
+    }
238
+
239
+    /**
240
+     * add new user to accounts table
241
+     *
242
+     * @param IUser $user
243
+     * @param array $data
244
+     */
245
+    protected function insertNewUser(IUser $user, $data) {
246
+        $uid = $user->getUID();
247
+        $jsonEncodedData = json_encode($data);
248
+        $query = $this->connection->getQueryBuilder();
249
+        $query->insert($this->table)
250
+            ->values(
251
+                [
252
+                    'uid' => $query->createNamedParameter($uid),
253
+                    'data' => $query->createNamedParameter($jsonEncodedData),
254
+                ]
255
+            )
256
+            ->execute();
257
+    }
258
+
259
+    /**
260
+     * update existing user in accounts table
261
+     *
262
+     * @param IUser $user
263
+     * @param array $data
264
+     */
265
+    protected function updateExistingUser(IUser $user, $data) {
266
+        $uid = $user->getUID();
267
+        $jsonEncodedData = json_encode($data);
268
+        $query = $this->connection->getQueryBuilder();
269
+        $query->update($this->table)
270
+            ->set('data', $query->createNamedParameter($jsonEncodedData))
271
+            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
272
+            ->execute();
273
+    }
274
+
275
+    /**
276
+     * build default user record in case not data set exists yet
277
+     *
278
+     * @param IUser $user
279
+     * @return array
280
+     */
281
+    protected function buildDefaultUserRecord(IUser $user) {
282
+        return [
283
+            self::PROPERTY_DISPLAYNAME =>
284
+                [
285
+                    'value' => $user->getDisplayName(),
286
+                    'scope' => self::VISIBILITY_CONTACTS_ONLY,
287
+                    'verified' => self::NOT_VERIFIED,
288
+                ],
289
+            self::PROPERTY_ADDRESS =>
290
+                [
291
+                    'value' => '',
292
+                    'scope' => self::VISIBILITY_PRIVATE,
293
+                    'verified' => self::NOT_VERIFIED,
294
+                ],
295
+            self::PROPERTY_WEBSITE =>
296
+                [
297
+                    'value' => '',
298
+                    'scope' => self::VISIBILITY_PRIVATE,
299
+                    'verified' => self::NOT_VERIFIED,
300
+                ],
301
+            self::PROPERTY_EMAIL =>
302
+                [
303
+                    'value' => $user->getEMailAddress(),
304
+                    'scope' => self::VISIBILITY_CONTACTS_ONLY,
305
+                    'verified' => self::NOT_VERIFIED,
306
+                ],
307
+            self::PROPERTY_AVATAR =>
308
+                [
309
+                    'scope' => self::VISIBILITY_CONTACTS_ONLY
310
+                ],
311
+            self::PROPERTY_PHONE =>
312
+                [
313
+                    'value' => '',
314
+                    'scope' => self::VISIBILITY_PRIVATE,
315
+                    'verified' => self::NOT_VERIFIED,
316
+                ],
317
+            self::PROPERTY_TWITTER =>
318
+                [
319
+                    'value' => '',
320
+                    'scope' => self::VISIBILITY_PRIVATE,
321
+                    'verified' => self::NOT_VERIFIED,
322
+                ],
323
+        ];
324
+    }
325 325
 
326 326
 }
Please login to merge, or discard this patch.
settings/templates/personal.php 2 patches
Indentation   +54 added lines, -54 removed lines patch added patch discarded remove patch
@@ -11,14 +11,14 @@  discard block
 block discarded – undo
11 11
 <div id="app-navigation">
12 12
 	<ul class="with-icon">
13 13
 	<?php foreach($_['forms'] as $form) {
14
-		if (isset($form['anchor'])) {
15
-			$anchor = '#' . $form['anchor'];
16
-			$class = 'nav-icon-' . $form['anchor'];
17
-			$sectionName = $form['section-name'];
18
-			print_unescaped(sprintf("<li><a href='%s' class='%s'>%s</a></li>", \OCP\Util::sanitizeHTML($anchor),
19
-			\OCP\Util::sanitizeHTML($class), \OCP\Util::sanitizeHTML($sectionName)));
20
-		}
21
-	}?>
14
+        if (isset($form['anchor'])) {
15
+            $anchor = '#' . $form['anchor'];
16
+            $class = 'nav-icon-' . $form['anchor'];
17
+            $sectionName = $form['section-name'];
18
+            print_unescaped(sprintf("<li><a href='%s' class='%s'>%s</a></li>", \OCP\Util::sanitizeHTML($anchor),
19
+            \OCP\Util::sanitizeHTML($class), \OCP\Util::sanitizeHTML($sectionName)));
20
+        }
21
+    }?>
22 22
 	</ul>
23 23
 </div>
24 24
 
@@ -30,10 +30,10 @@  discard block
 block discarded – undo
30 30
 		<p id="quotatext">
31 31
 			<?php if ($_['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED): ?>
32 32
 				<?php print_unescaped($l->t('You are using <strong>%s</strong> of <strong>%s</strong>',
33
-					[$_['usage'], $_['total_space']]));?>
33
+                    [$_['usage'], $_['total_space']]));?>
34 34
 			<?php else: ?>
35 35
 				<?php print_unescaped($l->t('You are using <strong>%s</strong> of <strong>%s</strong> (<strong>%s %%</strong>)',
36
-					[$_['usage'], $_['total_space'],  $_['usage_relative']]));?>
36
+                    [$_['usage'], $_['total_space'],  $_['usage_relative']]));?>
37 37
 			<?php endif ?>
38 38
 		</p>
39 39
 	</div>
@@ -101,17 +101,17 @@  discard block
 block discarded – undo
101 101
 			</h2>
102 102
 			<div class="verify">
103 103
 				<img id="verify-email" <?php
104
-				switch($_['emailVerification']) {
105
-					case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
106
-						print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
107
-						break;
108
-					case \OC\Accounts\AccountManager::VERIFIED:
109
-						print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
110
-						break;
111
-					default:
112
-						print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '"');
113
-				}
114
-				?>>
104
+                switch($_['emailVerification']) {
105
+                    case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
106
+                        print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
107
+                        break;
108
+                    case \OC\Accounts\AccountManager::VERIFIED:
109
+                        print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
110
+                        break;
111
+                    default:
112
+                        print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '"');
113
+                }
114
+                ?>>
115 115
 			</div>
116 116
 			<input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
117 117
 				<?php if(!$_['displayNameChangeSupported']) { print_unescaped('class="hidden"'); } ?>
@@ -167,17 +167,17 @@  discard block
 block discarded – undo
167 167
 			</h2>
168 168
 			<div class="verify">
169 169
 				<img id="verify-website" <?php
170
-				switch($_['websiteVerification']) {
171
-					case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
172
-						print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
173
-						break;
174
-					case \OC\Accounts\AccountManager::VERIFIED:
175
-						print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
176
-						break;
177
-					default:
178
-						print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '" class="verify-action"');
179
-				}
180
-				?>>
170
+                switch($_['websiteVerification']) {
171
+                    case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
172
+                        print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
173
+                        break;
174
+                    case \OC\Accounts\AccountManager::VERIFIED:
175
+                        print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
176
+                        break;
177
+                    default:
178
+                        print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '" class="verify-action"');
179
+                }
180
+                ?>>
181 181
 				<div class="verification-dialog popovermenu bubble menu">
182 182
 					<div class="verification-dialog-content">
183 183
 						<p class="explainVerification"></p>
@@ -201,17 +201,17 @@  discard block
 block discarded – undo
201 201
 			</h2>
202 202
 			<div class="verify">
203 203
 				<img id="verify-twitter" <?php
204
-				switch($_['twitterVerification']) {
205
-					case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
206
-						print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
207
-						break;
208
-					case \OC\Accounts\AccountManager::VERIFIED:
209
-						print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
210
-						break;
211
-					default:
212
-						print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '" class="verify-action"');
213
-				}
214
-				?>>
204
+                switch($_['twitterVerification']) {
205
+                    case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
206
+                        print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
207
+                        break;
208
+                    case \OC\Accounts\AccountManager::VERIFIED:
209
+                        print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
210
+                        break;
211
+                    default:
212
+                        print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '" class="verify-action"');
213
+                }
214
+                ?>>
215 215
 				<div class="verification-dialog popovermenu bubble menu">
216 216
 					<div class="verification-dialog-content">
217 217
 						<p class="explainVerification"></p>
@@ -242,7 +242,7 @@  discard block
 block discarded – undo
242 242
 
243 243
 <?php
244 244
 if($_['passwordChangeSupported']) {
245
-	script('jquery-showpassword');
245
+    script('jquery-showpassword');
246 246
 ?>
247 247
 <form id="passwordform" class="section">
248 248
 	<h2 class="inlineblock"><?php p($l->t('Password'));?></h2>
@@ -311,15 +311,15 @@  discard block
 block discarded – undo
311 311
 
312 312
 		<p>
313 313
 			<?php print_unescaped(str_replace(
314
-				[
315
-					'{contributeopen}',
316
-					'{linkclose}',
317
-				],
318
-				[
319
-					'<a href="https://nextcloud.com/contribute" target="_blank" rel="noreferrer">',
320
-					'</a>',
321
-				],
322
-				$l->t('If you want to support the project {contributeopen}join development{linkclose} or {contributeopen}spread the word{linkclose}!'))); ?>
314
+                [
315
+                    '{contributeopen}',
316
+                    '{linkclose}',
317
+                ],
318
+                [
319
+                    '<a href="https://nextcloud.com/contribute" target="_blank" rel="noreferrer">',
320
+                    '</a>',
321
+                ],
322
+                $l->t('If you want to support the project {contributeopen}join development{linkclose} or {contributeopen}spread the word{linkclose}!'))); ?>
323 323
 		</p>
324 324
 
325 325
 	<?php if(OC_APP::isEnabled('firstrunwizard')) {?>
@@ -380,7 +380,7 @@  discard block
 block discarded – undo
380 380
 </div>
381 381
 
382 382
 <?php foreach($_['forms'] as $form) {
383
-	if (isset($form['form'])) {?>
383
+    if (isset($form['form'])) {?>
384 384
 	<div id="<?php isset($form['anchor']) ? p($form['anchor']) : p('');?>"><?php print_unescaped($form['form']);?></div>
385 385
 	<?php }
386 386
 };?>
Please login to merge, or discard this patch.
Spacing   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -10,10 +10,10 @@  discard block
 block discarded – undo
10 10
 
11 11
 <div id="app-navigation">
12 12
 	<ul class="with-icon">
13
-	<?php foreach($_['forms'] as $form) {
13
+	<?php foreach ($_['forms'] as $form) {
14 14
 		if (isset($form['anchor'])) {
15
-			$anchor = '#' . $form['anchor'];
16
-			$class = 'nav-icon-' . $form['anchor'];
15
+			$anchor = '#'.$form['anchor'];
16
+			$class = 'nav-icon-'.$form['anchor'];
17 17
 			$sectionName = $form['section-name'];
18 18
 			print_unescaped(sprintf("<li><a href='%s' class='%s'>%s</a></li>", \OCP\Util::sanitizeHTML($anchor),
19 19
 			\OCP\Util::sanitizeHTML($class), \OCP\Util::sanitizeHTML($sectionName)));
@@ -25,15 +25,15 @@  discard block
 block discarded – undo
25 25
 <div id="app-content">
26 26
 
27 27
 <div id="quota" class="section">
28
-	<div style="width:<?php p($_['usage_relative']);?>%"
29
-		<?php if($_['usage_relative'] > 80): ?> class="quota-warning" <?php endif; ?>>
28
+	<div style="width:<?php p($_['usage_relative']); ?>%"
29
+		<?php if ($_['usage_relative'] > 80): ?> class="quota-warning" <?php endif; ?>>
30 30
 		<p id="quotatext">
31 31
 			<?php if ($_['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED): ?>
32 32
 				<?php print_unescaped($l->t('You are using <strong>%s</strong> of <strong>%s</strong>',
33
-					[$_['usage'], $_['total_space']]));?>
33
+					[$_['usage'], $_['total_space']])); ?>
34 34
 			<?php else: ?>
35 35
 				<?php print_unescaped($l->t('You are using <strong>%s</strong> of <strong>%s</strong> (<strong>%s %%</strong>)',
36
-					[$_['usage'], $_['total_space'],  $_['usage_relative']]));?>
36
+					[$_['usage'], $_['total_space'], $_['usage_relative']])); ?>
37 37
 			<?php endif ?>
38 38
 		</p>
39 39
 	</div>
@@ -67,7 +67,7 @@  discard block
 block discarded – undo
67 67
 			</div>
68 68
 		</div>
69 69
 		<span class="icon-checkmark hidden"/>
70
-		<?php if($_['lookupServerUploadEnabled']) { ?>
70
+		<?php if ($_['lookupServerUploadEnabled']) { ?>
71 71
 		<input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>">
72 72
 		<?php } ?>
73 73
 	</form>
@@ -81,14 +81,14 @@  discard block
 block discarded – undo
81 81
 				<span class="icon-password"/>
82 82
 			</h2>
83 83
 			<input type="text" id="displayname" name="displayname"
84
-				<?php if(!$_['displayNameChangeSupported']) { print_unescaped('class="hidden"'); } ?>
84
+				<?php if (!$_['displayNameChangeSupported']) { print_unescaped('class="hidden"'); } ?>
85 85
 				value="<?php p($_['displayName']) ?>"
86 86
 				autocomplete="on" autocapitalize="none" autocorrect="off" />
87
-			<?php if(!$_['displayNameChangeSupported']) { ?>
88
-				<span><?php if(isset($_['displayName']) && !empty($_['displayName'])) { p($_['displayName']); } else { p($l->t('No display name set')); } ?></span>
87
+			<?php if (!$_['displayNameChangeSupported']) { ?>
88
+				<span><?php if (isset($_['displayName']) && !empty($_['displayName'])) { p($_['displayName']); } else { p($l->t('No display name set')); } ?></span>
89 89
 			<?php } ?>
90 90
 			<span class="icon-checkmark hidden"/>
91
-			<?php if($_['lookupServerUploadEnabled']) { ?>
91
+			<?php if ($_['lookupServerUploadEnabled']) { ?>
92 92
 			<input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>">
93 93
 			<?php } ?>
94 94
 		</form>
@@ -101,36 +101,36 @@  discard block
 block discarded – undo
101 101
 			</h2>
102 102
 			<div class="verify">
103 103
 				<img id="verify-email" <?php
104
-				switch($_['emailVerification']) {
104
+				switch ($_['emailVerification']) {
105 105
 					case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
106
-						print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
106
+						print_unescaped('src="'.image_path('core', 'actions/verifying.svg').'" title="'.\OC::$server->getL10N()->t('Verifying …').'"');
107 107
 						break;
108 108
 					case \OC\Accounts\AccountManager::VERIFIED:
109
-						print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
109
+						print_unescaped('src="'.image_path('core', 'actions/verified.svg').'"'.\OC::$server->getL10N()->t('Verified').'"');
110 110
 						break;
111 111
 					default:
112
-						print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '"');
112
+						print_unescaped('src="'.image_path('core', 'actions/verify.svg').'" title="'.\OC::$server->getL10N()->t('Verify').'"');
113 113
 				}
114 114
 				?>>
115 115
 			</div>
116 116
 			<input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
117
-				<?php if(!$_['displayNameChangeSupported']) { print_unescaped('class="hidden"'); } ?>
117
+				<?php if (!$_['displayNameChangeSupported']) { print_unescaped('class="hidden"'); } ?>
118 118
 				placeholder="<?php p($l->t('Your email address')); ?>"
119 119
 				autocomplete="on" autocapitalize="none" autocorrect="off" />
120
-			<?php if(!$_['displayNameChangeSupported']) { ?>
121
-				<span><?php if(isset($_['email']) && !empty($_['email'])) { p($_['email']); } else { p($l->t('No email address set')); }?></span>
120
+			<?php if (!$_['displayNameChangeSupported']) { ?>
121
+				<span><?php if (isset($_['email']) && !empty($_['email'])) { p($_['email']); } else { p($l->t('No email address set')); }?></span>
122 122
 			<?php } ?>
123
-			<?php if($_['displayNameChangeSupported']) { ?>
123
+			<?php if ($_['displayNameChangeSupported']) { ?>
124 124
 				<br />
125 125
 				<em><?php p($l->t('For password reset and notifications')); ?></em>
126 126
 			<?php } ?>
127 127
 			<span class="icon-checkmark hidden"/>
128
-			<?php if($_['lookupServerUploadEnabled']) { ?>
128
+			<?php if ($_['lookupServerUploadEnabled']) { ?>
129 129
 			<input type="hidden" id="emailscope" value="<?php p($_['emailScope']) ?>">
130 130
 			<?php } ?>
131 131
 		</form>
132 132
 	</div>
133
-	<?php if($_['lookupServerUploadEnabled']) { ?>
133
+	<?php if ($_['lookupServerUploadEnabled']) { ?>
134 134
 	<div class="personal-settings-setting-box">
135 135
 		<form id="phoneform" class="section">
136 136
 			<h2>
@@ -167,22 +167,22 @@  discard block
 block discarded – undo
167 167
 			</h2>
168 168
 			<div class="verify">
169 169
 				<img id="verify-website" <?php
170
-				switch($_['websiteVerification']) {
170
+				switch ($_['websiteVerification']) {
171 171
 					case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
172
-						print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
172
+						print_unescaped('src="'.image_path('core', 'actions/verifying.svg').'" title="'.\OC::$server->getL10N()->t('Verifying …').'"');
173 173
 						break;
174 174
 					case \OC\Accounts\AccountManager::VERIFIED:
175
-						print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
175
+						print_unescaped('src="'.image_path('core', 'actions/verified.svg').'"'.\OC::$server->getL10N()->t('Verified').'"');
176 176
 						break;
177 177
 					default:
178
-						print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '" class="verify-action"');
178
+						print_unescaped('src="'.image_path('core', 'actions/verify.svg').'" title="'.\OC::$server->getL10N()->t('Verify').'" class="verify-action"');
179 179
 				}
180 180
 				?>>
181 181
 				<div class="verification-dialog popovermenu bubble menu">
182 182
 					<div class="verification-dialog-content">
183 183
 						<p class="explainVerification"></p>
184 184
 						<p class="verificationCode"></p>
185
-						<p><?php p(\OC::$server->getL10N('settings')->t('It can take up to 24 hours before the account is displayed as verified.'));?></p>
185
+						<p><?php p(\OC::$server->getL10N('settings')->t('It can take up to 24 hours before the account is displayed as verified.')); ?></p>
186 186
 					</div>
187 187
 				</div>
188 188
 			</div>
@@ -201,22 +201,22 @@  discard block
 block discarded – undo
201 201
 			</h2>
202 202
 			<div class="verify">
203 203
 				<img id="verify-twitter" <?php
204
-				switch($_['twitterVerification']) {
204
+				switch ($_['twitterVerification']) {
205 205
 					case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
206
-						print_unescaped('src="' . image_path('core', 'actions/verifying.svg') . '" title="' . \OC::$server->getL10N()->t('Verifying …') . '"');
206
+						print_unescaped('src="'.image_path('core', 'actions/verifying.svg').'" title="'.\OC::$server->getL10N()->t('Verifying …').'"');
207 207
 						break;
208 208
 					case \OC\Accounts\AccountManager::VERIFIED:
209
-						print_unescaped('src="' . image_path('core', 'actions/verified.svg') . '"' . \OC::$server->getL10N()->t('Verified') . '"');
209
+						print_unescaped('src="'.image_path('core', 'actions/verified.svg').'"'.\OC::$server->getL10N()->t('Verified').'"');
210 210
 						break;
211 211
 					default:
212
-						print_unescaped('src="' . image_path('core', 'actions/verify.svg') . '" title="' . \OC::$server->getL10N()->t('Verify') . '" class="verify-action"');
212
+						print_unescaped('src="'.image_path('core', 'actions/verify.svg').'" title="'.\OC::$server->getL10N()->t('Verify').'" class="verify-action"');
213 213
 				}
214 214
 				?>>
215 215
 				<div class="verification-dialog popovermenu bubble menu">
216 216
 					<div class="verification-dialog-content">
217 217
 						<p class="explainVerification"></p>
218 218
 						<p class="verificationCode"></p>
219
-						<p><?php p(\OC::$server->getL10N('settings')->t('It can take up to 24 hours before the account is displayed as verified.'));?></p>
219
+						<p><?php p(\OC::$server->getL10N('settings')->t('It can take up to 24 hours before the account is displayed as verified.')); ?></p>
220 220
 					</div>
221 221
 				</div>
222 222
 			</div>
@@ -241,19 +241,19 @@  discard block
 block discarded – undo
241 241
 </div>
242 242
 
243 243
 <?php
244
-if($_['passwordChangeSupported']) {
244
+if ($_['passwordChangeSupported']) {
245 245
 	script('jquery-showpassword');
246 246
 ?>
247 247
 <form id="passwordform" class="section">
248
-	<h2 class="inlineblock"><?php p($l->t('Password'));?></h2>
248
+	<h2 class="inlineblock"><?php p($l->t('Password')); ?></h2>
249 249
 	<div id="password-error-msg" class="msg success inlineblock" style="display: none;">Saved</div>
250 250
 	<br>
251 251
 	<label for="pass1" class="hidden-visually"><?php p($l->t('Current password')); ?>: </label>
252 252
 	<input type="password" id="pass1" name="oldpassword"
253
-		placeholder="<?php p($l->t('Current password'));?>"
253
+		placeholder="<?php p($l->t('Current password')); ?>"
254 254
 		autocomplete="off" autocapitalize="none" autocorrect="off" />
255 255
 	<div class="personal-show-container">
256
-		<label for="pass2" class="hidden-visually"><?php p($l->t('New password'));?>: </label>
256
+		<label for="pass2" class="hidden-visually"><?php p($l->t('New password')); ?>: </label>
257 257
 		<input type="password" id="pass2" name="newpassword"
258 258
 			placeholder="<?php p($l->t('New password')); ?>"
259 259
 			data-typetoggle="#personal-show"
@@ -269,44 +269,44 @@  discard block
 block discarded – undo
269 269
 
270 270
 <form id="language" class="section">
271 271
 	<h2>
272
-		<label for="languageinput"><?php p($l->t('Language'));?></label>
272
+		<label for="languageinput"><?php p($l->t('Language')); ?></label>
273 273
 	</h2>
274
-	<select id="languageinput" name="lang" data-placeholder="<?php p($l->t('Language'));?>">
275
-		<option value="<?php p($_['activelanguage']['code']);?>">
276
-			<?php p($_['activelanguage']['name']);?>
274
+	<select id="languageinput" name="lang" data-placeholder="<?php p($l->t('Language')); ?>">
275
+		<option value="<?php p($_['activelanguage']['code']); ?>">
276
+			<?php p($_['activelanguage']['name']); ?>
277 277
 		</option>
278
-		<?php foreach($_['commonlanguages'] as $language):?>
279
-			<option value="<?php p($language['code']);?>">
280
-				<?php p($language['name']);?>
278
+		<?php foreach ($_['commonlanguages'] as $language):?>
279
+			<option value="<?php p($language['code']); ?>">
280
+				<?php p($language['name']); ?>
281 281
 			</option>
282
-		<?php endforeach;?>
282
+		<?php endforeach; ?>
283 283
 		<optgroup label="––––––––––"></optgroup>
284
-		<?php foreach($_['languages'] as $language):?>
285
-			<option value="<?php p($language['code']);?>">
286
-				<?php p($language['name']);?>
284
+		<?php foreach ($_['languages'] as $language):?>
285
+			<option value="<?php p($language['code']); ?>">
286
+				<?php p($language['name']); ?>
287 287
 			</option>
288
-		<?php endforeach;?>
288
+		<?php endforeach; ?>
289 289
 	</select>
290 290
 	<a href="https://www.transifex.com/nextcloud/nextcloud/"
291 291
 		target="_blank" rel="noreferrer">
292
-		<em><?php p($l->t('Help translate'));?></em>
292
+		<em><?php p($l->t('Help translate')); ?></em>
293 293
 	</a>
294 294
 </form>
295 295
 
296 296
 
297 297
 <div id="clientsbox" class="section clientsbox">
298
-	<h2><?php p($l->t('Get the apps to sync your files'));?></h2>
298
+	<h2><?php p($l->t('Get the apps to sync your files')); ?></h2>
299 299
 	<a href="<?php p($_['clients']['desktop']); ?>" rel="noreferrer" target="_blank">
300 300
 		<img src="<?php print_unescaped(image_path('core', 'desktopapp.svg')); ?>"
301
-			 alt="<?php p($l->t('Desktop client'));?>" />
301
+			 alt="<?php p($l->t('Desktop client')); ?>" />
302 302
 	</a>
303 303
 	<a href="<?php p($_['clients']['android']); ?>" rel="noreferrer" target="_blank">
304 304
 		<img src="<?php print_unescaped(image_path('core', 'googleplay.png')); ?>"
305
-			 alt="<?php p($l->t('Android app'));?>" />
305
+			 alt="<?php p($l->t('Android app')); ?>" />
306 306
 	</a>
307 307
 	<a href="<?php p($_['clients']['ios']); ?>" rel="noreferrer" target="_blank">
308 308
 		<img src="<?php print_unescaped(image_path('core', 'appstore.svg')); ?>"
309
-			 alt="<?php p($l->t('iOS app'));?>" />
309
+			 alt="<?php p($l->t('iOS app')); ?>" />
310 310
 	</a>
311 311
 
312 312
 		<p>
@@ -322,19 +322,19 @@  discard block
 block discarded – undo
322 322
 				$l->t('If you want to support the project {contributeopen}join development{linkclose} or {contributeopen}spread the word{linkclose}!'))); ?>
323 323
 		</p>
324 324
 
325
-	<?php if(OC_APP::isEnabled('firstrunwizard')) {?>
326
-		<p><a class="button" href="#" id="showWizard"><?php p($l->t('Show First Run Wizard again'));?></a></p>
325
+	<?php if (OC_APP::isEnabled('firstrunwizard')) {?>
326
+		<p><a class="button" href="#" id="showWizard"><?php p($l->t('Show First Run Wizard again')); ?></a></p>
327 327
 	<?php }?>
328 328
 </div>
329 329
 
330 330
 <div id="sessions" class="section">
331
-	<h2><?php p($l->t('Sessions'));?></h2>
332
-	<span class="hidden-when-empty"><?php p($l->t('Web, desktop and mobile clients currently logged in to your account.'));?></span>
331
+	<h2><?php p($l->t('Sessions')); ?></h2>
332
+	<span class="hidden-when-empty"><?php p($l->t('Web, desktop and mobile clients currently logged in to your account.')); ?></span>
333 333
 	<table class="icon-loading">
334 334
 		<thead class="token-list-header">
335 335
 			<tr>
336
-				<th><?php p($l->t('Device'));?></th>
337
-				<th><?php p($l->t('Last activity'));?></th>
336
+				<th><?php p($l->t('Device')); ?></th>
337
+				<th><?php p($l->t('Last activity')); ?></th>
338 338
 				<th></th>
339 339
 			</tr>
340 340
 		</thead>
@@ -344,13 +344,13 @@  discard block
 block discarded – undo
344 344
 </div>
345 345
 
346 346
 <div id="apppasswords" class="section">
347
-	<h2><?php p($l->t('App passwords'));?></h2>
348
-	<p><?php p($l->t('Passcodes that give an app or device permissions to access your account.'));?></p>
347
+	<h2><?php p($l->t('App passwords')); ?></h2>
348
+	<p><?php p($l->t('Passcodes that give an app or device permissions to access your account.')); ?></p>
349 349
 	<table class="icon-loading">
350 350
 		<thead class="hidden-when-empty">
351 351
 			<tr>
352
-				<th><?php p($l->t('Name'));?></th>
353
-				<th><?php p($l->t('Last activity'));?></th>
352
+				<th><?php p($l->t('Name')); ?></th>
353
+				<th><?php p($l->t('Last activity')); ?></th>
354 354
 				<th></th>
355 355
 			</tr>
356 356
 		</thead>
@@ -379,14 +379,14 @@  discard block
 block discarded – undo
379 379
 	</div>
380 380
 </div>
381 381
 
382
-<?php foreach($_['forms'] as $form) {
382
+<?php foreach ($_['forms'] as $form) {
383 383
 	if (isset($form['form'])) {?>
384
-	<div id="<?php isset($form['anchor']) ? p($form['anchor']) : p('');?>"><?php print_unescaped($form['form']);?></div>
384
+	<div id="<?php isset($form['anchor']) ? p($form['anchor']) : p(''); ?>"><?php print_unescaped($form['form']); ?></div>
385 385
 	<?php }
386 386
 };?>
387 387
 
388 388
 <div class="section">
389
-	<h2><?php p($l->t('Version'));?></h2>
389
+	<h2><?php p($l->t('Version')); ?></h2>
390 390
 	<p><a href="<?php print_unescaped($theme->getBaseUrl()); ?>" target="_blank"><?php p($theme->getTitle()); ?></a> <?php p(OC_Util::getHumanVersion()) ?></p>
391 391
 	<p><?php include('settings.development.notice.php'); ?></p>
392 392
 </div>
Please login to merge, or discard this patch.