Passed
Push — master ( bbb39c...5026d2 )
by Christoph
27:34 queued 12:27
created
apps/settings/lib/Controller/UsersController.php 2 patches
Indentation   +493 added lines, -493 removed lines patch added patch discarded remove patch
@@ -70,497 +70,497 @@
 block discarded – undo
70 70
 use function in_array;
71 71
 
72 72
 class UsersController extends Controller {
73
-	/** @var UserManager */
74
-	private $userManager;
75
-	/** @var GroupManager */
76
-	private $groupManager;
77
-	/** @var IUserSession */
78
-	private $userSession;
79
-	/** @var IConfig */
80
-	private $config;
81
-	/** @var bool */
82
-	private $isAdmin;
83
-	/** @var IL10N */
84
-	private $l10n;
85
-	/** @var IMailer */
86
-	private $mailer;
87
-	/** @var Factory */
88
-	private $l10nFactory;
89
-	/** @var IAppManager */
90
-	private $appManager;
91
-	/** @var AccountManager */
92
-	private $accountManager;
93
-	/** @var Manager */
94
-	private $keyManager;
95
-	/** @var IJobList */
96
-	private $jobList;
97
-	/** @var IManager */
98
-	private $encryptionManager;
99
-	/** @var IEventDispatcher */
100
-	private $dispatcher;
101
-
102
-
103
-	public function __construct(
104
-		string $appName,
105
-		IRequest $request,
106
-		IUserManager $userManager,
107
-		IGroupManager $groupManager,
108
-		IUserSession $userSession,
109
-		IConfig $config,
110
-		bool $isAdmin,
111
-		IL10N $l10n,
112
-		IMailer $mailer,
113
-		IFactory $l10nFactory,
114
-		IAppManager $appManager,
115
-		AccountManager $accountManager,
116
-		Manager $keyManager,
117
-		IJobList $jobList,
118
-		IManager $encryptionManager,
119
-		IEventDispatcher $dispatcher
120
-	) {
121
-		parent::__construct($appName, $request);
122
-		$this->userManager = $userManager;
123
-		$this->groupManager = $groupManager;
124
-		$this->userSession = $userSession;
125
-		$this->config = $config;
126
-		$this->isAdmin = $isAdmin;
127
-		$this->l10n = $l10n;
128
-		$this->mailer = $mailer;
129
-		$this->l10nFactory = $l10nFactory;
130
-		$this->appManager = $appManager;
131
-		$this->accountManager = $accountManager;
132
-		$this->keyManager = $keyManager;
133
-		$this->jobList = $jobList;
134
-		$this->encryptionManager = $encryptionManager;
135
-		$this->dispatcher = $dispatcher;
136
-	}
137
-
138
-
139
-	/**
140
-	 * @NoCSRFRequired
141
-	 * @NoAdminRequired
142
-	 *
143
-	 * Display users list template
144
-	 *
145
-	 * @return TemplateResponse
146
-	 */
147
-	public function usersListByGroup(): TemplateResponse {
148
-		return $this->usersList();
149
-	}
150
-
151
-	/**
152
-	 * @NoCSRFRequired
153
-	 * @NoAdminRequired
154
-	 *
155
-	 * Display users list template
156
-	 *
157
-	 * @return TemplateResponse
158
-	 */
159
-	public function usersList(): TemplateResponse {
160
-		$user = $this->userSession->getUser();
161
-		$uid = $user->getUID();
162
-
163
-		\OC::$server->getNavigationManager()->setActiveEntry('core_users');
164
-
165
-		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
166
-		$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
167
-		$isLDAPUsed = false;
168
-		if ($this->config->getSystemValue('sort_groups_by_name', false)) {
169
-			$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
170
-		} else {
171
-			if ($this->appManager->isEnabledForUser('user_ldap')) {
172
-				$isLDAPUsed =
173
-					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
174
-				if ($isLDAPUsed) {
175
-					// LDAP user count can be slow, so we sort by group name here
176
-					$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
177
-				}
178
-			}
179
-		}
180
-
181
-		$canChangePassword = $this->canAdminChangeUserPasswords();
182
-
183
-		/* GROUPS */
184
-		$groupsInfo = new \OC\Group\MetaData(
185
-			$uid,
186
-			$this->isAdmin,
187
-			$this->groupManager,
188
-			$this->userSession
189
-		);
190
-
191
-		$groupsInfo->setSorting($sortGroupsBy);
192
-		[$adminGroup, $groups] = $groupsInfo->get();
193
-
194
-		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
195
-			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
196
-				return $ldapFound || $backend instanceof User_Proxy;
197
-			});
198
-		}
199
-
200
-		$disabledUsers = -1;
201
-		$userCount = 0;
202
-
203
-		if (!$isLDAPUsed) {
204
-			if ($this->isAdmin) {
205
-				$disabledUsers = $this->userManager->countDisabledUsers();
206
-				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
207
-					return $v + (int)$w;
208
-				}, 0);
209
-			} else {
210
-				// User is subadmin !
211
-				// Map group list to names to retrieve the countDisabledUsersOfGroups
212
-				$userGroups = $this->groupManager->getUserGroups($user);
213
-				$groupsNames = [];
214
-
215
-				foreach ($groups as $key => $group) {
216
-					// $userCount += (int)$group['usercount'];
217
-					array_push($groupsNames, $group['name']);
218
-					// we prevent subadmins from looking up themselves
219
-					// so we lower the count of the groups he belongs to
220
-					if (array_key_exists($group['id'], $userGroups)) {
221
-						$groups[$key]['usercount']--;
222
-						$userCount -= 1; // we also lower from one the total count
223
-					}
224
-				}
225
-				$userCount += $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
226
-				$disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsNames);
227
-			}
228
-
229
-			$userCount -= $disabledUsers;
230
-		}
231
-
232
-		$disabledUsersGroup = [
233
-			'id' => 'disabled',
234
-			'name' => 'Disabled users',
235
-			'usercount' => $disabledUsers
236
-		];
237
-
238
-		/* QUOTAS PRESETS */
239
-		$quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
240
-		$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
241
-
242
-		$event = new BeforeTemplateRenderedEvent();
243
-		$this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
244
-		$this->dispatcher->dispatchTyped($event);
245
-
246
-		/* LANGUAGES */
247
-		$languages = $this->l10nFactory->getLanguages();
248
-
249
-		/* FINAL DATA */
250
-		$serverData = [];
251
-		// groups
252
-		$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
253
-		// Various data
254
-		$serverData['isAdmin'] = $this->isAdmin;
255
-		$serverData['sortGroups'] = $sortGroupsBy;
256
-		$serverData['quotaPreset'] = $quotaPreset;
257
-		$serverData['userCount'] = $userCount;
258
-		$serverData['languages'] = $languages;
259
-		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
260
-		$serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
261
-		// Settings
262
-		$serverData['defaultQuota'] = $defaultQuota;
263
-		$serverData['canChangePassword'] = $canChangePassword;
264
-		$serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
265
-		$serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
266
-		$serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
267
-
268
-		return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
269
-	}
270
-
271
-	/**
272
-	 * @param string $key
273
-	 * @param string $value
274
-	 *
275
-	 * @return JSONResponse
276
-	 */
277
-	public function setPreference(string $key, string $value): JSONResponse {
278
-		$allowed = ['newUser.sendEmail'];
279
-		if (!in_array($key, $allowed, true)) {
280
-			return new JSONResponse([], Http::STATUS_FORBIDDEN);
281
-		}
282
-
283
-		$this->config->setAppValue('core', $key, $value);
284
-
285
-		return new JSONResponse([]);
286
-	}
287
-
288
-	/**
289
-	 * Parse the app value for quota_present
290
-	 *
291
-	 * @param string $quotaPreset
292
-	 * @return array
293
-	 */
294
-	protected function parseQuotaPreset(string $quotaPreset): array {
295
-		// 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
296
-		$presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
297
-		// Drop default and none, Make array indexes numerically
298
-		return array_values(array_diff($presets, ['default', 'none']));
299
-	}
300
-
301
-	/**
302
-	 * check if the admin can change the users password
303
-	 *
304
-	 * The admin can change the passwords if:
305
-	 *
306
-	 *   - no encryption module is loaded and encryption is disabled
307
-	 *   - encryption module is loaded but it doesn't require per user keys
308
-	 *
309
-	 * The admin can not change the passwords if:
310
-	 *
311
-	 *   - an encryption module is loaded and it uses per-user keys
312
-	 *   - encryption is enabled but no encryption modules are loaded
313
-	 *
314
-	 * @return bool
315
-	 */
316
-	protected function canAdminChangeUserPasswords(): bool {
317
-		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
318
-		try {
319
-			$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
320
-			$isEncryptionModuleLoaded = true;
321
-		} catch (ModuleDoesNotExistsException $e) {
322
-			$noUserSpecificEncryptionKeys = true;
323
-			$isEncryptionModuleLoaded = false;
324
-		}
325
-		$canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
326
-			|| (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
327
-
328
-		return $canChangePassword;
329
-	}
330
-
331
-	/**
332
-	 * @NoAdminRequired
333
-	 * @NoSubAdminRequired
334
-	 * @PasswordConfirmationRequired
335
-	 *
336
-	 * @param string|null $avatarScope
337
-	 * @param string|null $displayname
338
-	 * @param string|null $displaynameScope
339
-	 * @param string|null $phone
340
-	 * @param string|null $phoneScope
341
-	 * @param string|null $email
342
-	 * @param string|null $emailScope
343
-	 * @param string|null $website
344
-	 * @param string|null $websiteScope
345
-	 * @param string|null $address
346
-	 * @param string|null $addressScope
347
-	 * @param string|null $twitter
348
-	 * @param string|null $twitterScope
349
-	 *
350
-	 * @return DataResponse
351
-	 */
352
-	public function setUserSettings(?string $avatarScope = null,
353
-									?string $displayname = null,
354
-									?string $displaynameScope = null,
355
-									?string $phone = null,
356
-									?string $phoneScope = null,
357
-									?string $email = null,
358
-									?string $emailScope = null,
359
-									?string $website = null,
360
-									?string $websiteScope = null,
361
-									?string $address = null,
362
-									?string $addressScope = null,
363
-									?string $twitter = null,
364
-									?string $twitterScope = null
365
-	) {
366
-		$email = strtolower($email);
367
-		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
368
-			return new DataResponse(
369
-				[
370
-					'status' => 'error',
371
-					'data' => [
372
-						'message' => $this->l10n->t('Invalid mail address')
373
-					]
374
-				],
375
-				Http::STATUS_UNPROCESSABLE_ENTITY
376
-			);
377
-		}
378
-		$user = $this->userSession->getUser();
379
-		$data = $this->accountManager->getUser($user);
380
-		$data[IAccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
381
-		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
382
-			$data[IAccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
383
-			$data[IAccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
384
-		}
385
-		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
386
-			$shareProvider = \OC::$server->query(FederatedShareProvider::class);
387
-			if ($shareProvider->isLookupServerUploadEnabled()) {
388
-				$data[IAccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
389
-				$data[IAccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
390
-				$data[IAccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
391
-				$data[IAccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
392
-			}
393
-		}
394
-		try {
395
-			$data = $this->saveUserSettings($user, $data);
396
-			return new DataResponse(
397
-				[
398
-					'status' => 'success',
399
-					'data' => [
400
-						'userId' => $user->getUID(),
401
-						'avatarScope' => $data[IAccountManager::PROPERTY_AVATAR]['scope'],
402
-						'displayname' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['value'],
403
-						'displaynameScope' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'],
404
-						'phone' => $data[IAccountManager::PROPERTY_PHONE]['value'],
405
-						'phoneScope' => $data[IAccountManager::PROPERTY_PHONE]['scope'],
406
-						'email' => $data[IAccountManager::PROPERTY_EMAIL]['value'],
407
-						'emailScope' => $data[IAccountManager::PROPERTY_EMAIL]['scope'],
408
-						'website' => $data[IAccountManager::PROPERTY_WEBSITE]['value'],
409
-						'websiteScope' => $data[IAccountManager::PROPERTY_WEBSITE]['scope'],
410
-						'address' => $data[IAccountManager::PROPERTY_ADDRESS]['value'],
411
-						'addressScope' => $data[IAccountManager::PROPERTY_ADDRESS]['scope'],
412
-						'twitter' => $data[IAccountManager::PROPERTY_TWITTER]['value'],
413
-						'twitterScope' => $data[IAccountManager::PROPERTY_TWITTER]['scope'],
414
-						'message' => $this->l10n->t('Settings saved')
415
-					]
416
-				],
417
-				Http::STATUS_OK
418
-			);
419
-		} catch (ForbiddenException $e) {
420
-			return new DataResponse([
421
-				'status' => 'error',
422
-				'data' => [
423
-					'message' => $e->getMessage()
424
-				],
425
-			]);
426
-		} catch (\InvalidArgumentException $e) {
427
-			return new DataResponse([
428
-				'status' => 'error',
429
-				'data' => [
430
-					'message' => $e->getMessage()
431
-				],
432
-			]);
433
-		}
434
-	}
435
-	/**
436
-	 * update account manager with new user data
437
-	 *
438
-	 * @param IUser $user
439
-	 * @param array $data
440
-	 * @return array
441
-	 * @throws ForbiddenException
442
-	 * @throws \InvalidArgumentException
443
-	 */
444
-	protected function saveUserSettings(IUser $user, array $data): array {
445
-		// keep the user back-end up-to-date with the latest display name and email
446
-		// address
447
-		$oldDisplayName = $user->getDisplayName();
448
-		$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
449
-		if (isset($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
450
-			&& $oldDisplayName !== $data[IAccountManager::PROPERTY_DISPLAYNAME]['value']
451
-		) {
452
-			$result = $user->setDisplayName($data[IAccountManager::PROPERTY_DISPLAYNAME]['value']);
453
-			if ($result === false) {
454
-				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
455
-			}
456
-		}
457
-
458
-		$oldEmailAddress = $user->getEMailAddress();
459
-		$oldEmailAddress = is_null($oldEmailAddress) ? '' : strtolower($oldEmailAddress);
460
-		if (isset($data[IAccountManager::PROPERTY_EMAIL]['value'])
461
-			&& $oldEmailAddress !== $data[IAccountManager::PROPERTY_EMAIL]['value']
462
-		) {
463
-			// this is the only permission a backend provides and is also used
464
-			// for the permission of setting a email address
465
-			if (!$user->canChangeDisplayName()) {
466
-				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
467
-			}
468
-			$user->setEMailAddress($data[IAccountManager::PROPERTY_EMAIL]['value']);
469
-		}
470
-
471
-		try {
472
-			return $this->accountManager->updateUser($user, $data, true);
473
-		} catch (\InvalidArgumentException $e) {
474
-			if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
475
-				throw new \InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
476
-			}
477
-			throw new \InvalidArgumentException($this->l10n->t('Some account data was invalid'));
478
-		}
479
-	}
480
-
481
-	/**
482
-	 * Set the mail address of a user
483
-	 *
484
-	 * @NoAdminRequired
485
-	 * @NoSubAdminRequired
486
-	 * @PasswordConfirmationRequired
487
-	 *
488
-	 * @param string $account
489
-	 * @param bool $onlyVerificationCode only return verification code without updating the data
490
-	 * @return DataResponse
491
-	 */
492
-	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
493
-		$user = $this->userSession->getUser();
494
-
495
-		if ($user === null) {
496
-			return new DataResponse([], Http::STATUS_BAD_REQUEST);
497
-		}
498
-
499
-		$accountData = $this->accountManager->getUser($user);
500
-		$cloudId = $user->getCloudId();
501
-		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
502
-		$signature = $this->signMessage($user, $message);
503
-
504
-		$code = $message . ' ' . $signature;
505
-		$codeMd5 = $message . ' ' . md5($signature);
506
-
507
-		switch ($account) {
508
-			case 'verify-twitter':
509
-				$accountData[IAccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
510
-				$msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
511
-				$code = $codeMd5;
512
-				$type = IAccountManager::PROPERTY_TWITTER;
513
-				$accountData[IAccountManager::PROPERTY_TWITTER]['signature'] = $signature;
514
-				break;
515
-			case 'verify-website':
516
-				$accountData[IAccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
517
-				$msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
518
-				$type = IAccountManager::PROPERTY_WEBSITE;
519
-				$accountData[IAccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
520
-				break;
521
-			default:
522
-				return new DataResponse([], Http::STATUS_BAD_REQUEST);
523
-		}
524
-
525
-		if ($onlyVerificationCode === false) {
526
-			$accountData = $this->accountManager->updateUser($user, $accountData);
527
-			$data = $accountData[$type]['value'];
528
-
529
-			$this->jobList->add(VerifyUserData::class,
530
-				[
531
-					'verificationCode' => $code,
532
-					'data' => $data,
533
-					'type' => $type,
534
-					'uid' => $user->getUID(),
535
-					'try' => 0,
536
-					'lastRun' => $this->getCurrentTime()
537
-				]
538
-			);
539
-		}
540
-
541
-		return new DataResponse(['msg' => $msg, 'code' => $code]);
542
-	}
543
-
544
-	/**
545
-	 * get current timestamp
546
-	 *
547
-	 * @return int
548
-	 */
549
-	protected function getCurrentTime(): int {
550
-		return time();
551
-	}
552
-
553
-	/**
554
-	 * sign message with users private key
555
-	 *
556
-	 * @param IUser $user
557
-	 * @param string $message
558
-	 *
559
-	 * @return string base64 encoded signature
560
-	 */
561
-	protected function signMessage(IUser $user, string $message): string {
562
-		$privateKey = $this->keyManager->getKey($user)->getPrivate();
563
-		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
564
-		return base64_encode($signature);
565
-	}
73
+    /** @var UserManager */
74
+    private $userManager;
75
+    /** @var GroupManager */
76
+    private $groupManager;
77
+    /** @var IUserSession */
78
+    private $userSession;
79
+    /** @var IConfig */
80
+    private $config;
81
+    /** @var bool */
82
+    private $isAdmin;
83
+    /** @var IL10N */
84
+    private $l10n;
85
+    /** @var IMailer */
86
+    private $mailer;
87
+    /** @var Factory */
88
+    private $l10nFactory;
89
+    /** @var IAppManager */
90
+    private $appManager;
91
+    /** @var AccountManager */
92
+    private $accountManager;
93
+    /** @var Manager */
94
+    private $keyManager;
95
+    /** @var IJobList */
96
+    private $jobList;
97
+    /** @var IManager */
98
+    private $encryptionManager;
99
+    /** @var IEventDispatcher */
100
+    private $dispatcher;
101
+
102
+
103
+    public function __construct(
104
+        string $appName,
105
+        IRequest $request,
106
+        IUserManager $userManager,
107
+        IGroupManager $groupManager,
108
+        IUserSession $userSession,
109
+        IConfig $config,
110
+        bool $isAdmin,
111
+        IL10N $l10n,
112
+        IMailer $mailer,
113
+        IFactory $l10nFactory,
114
+        IAppManager $appManager,
115
+        AccountManager $accountManager,
116
+        Manager $keyManager,
117
+        IJobList $jobList,
118
+        IManager $encryptionManager,
119
+        IEventDispatcher $dispatcher
120
+    ) {
121
+        parent::__construct($appName, $request);
122
+        $this->userManager = $userManager;
123
+        $this->groupManager = $groupManager;
124
+        $this->userSession = $userSession;
125
+        $this->config = $config;
126
+        $this->isAdmin = $isAdmin;
127
+        $this->l10n = $l10n;
128
+        $this->mailer = $mailer;
129
+        $this->l10nFactory = $l10nFactory;
130
+        $this->appManager = $appManager;
131
+        $this->accountManager = $accountManager;
132
+        $this->keyManager = $keyManager;
133
+        $this->jobList = $jobList;
134
+        $this->encryptionManager = $encryptionManager;
135
+        $this->dispatcher = $dispatcher;
136
+    }
137
+
138
+
139
+    /**
140
+     * @NoCSRFRequired
141
+     * @NoAdminRequired
142
+     *
143
+     * Display users list template
144
+     *
145
+     * @return TemplateResponse
146
+     */
147
+    public function usersListByGroup(): TemplateResponse {
148
+        return $this->usersList();
149
+    }
150
+
151
+    /**
152
+     * @NoCSRFRequired
153
+     * @NoAdminRequired
154
+     *
155
+     * Display users list template
156
+     *
157
+     * @return TemplateResponse
158
+     */
159
+    public function usersList(): TemplateResponse {
160
+        $user = $this->userSession->getUser();
161
+        $uid = $user->getUID();
162
+
163
+        \OC::$server->getNavigationManager()->setActiveEntry('core_users');
164
+
165
+        /* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
166
+        $sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
167
+        $isLDAPUsed = false;
168
+        if ($this->config->getSystemValue('sort_groups_by_name', false)) {
169
+            $sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
170
+        } else {
171
+            if ($this->appManager->isEnabledForUser('user_ldap')) {
172
+                $isLDAPUsed =
173
+                    $this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
174
+                if ($isLDAPUsed) {
175
+                    // LDAP user count can be slow, so we sort by group name here
176
+                    $sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
177
+                }
178
+            }
179
+        }
180
+
181
+        $canChangePassword = $this->canAdminChangeUserPasswords();
182
+
183
+        /* GROUPS */
184
+        $groupsInfo = new \OC\Group\MetaData(
185
+            $uid,
186
+            $this->isAdmin,
187
+            $this->groupManager,
188
+            $this->userSession
189
+        );
190
+
191
+        $groupsInfo->setSorting($sortGroupsBy);
192
+        [$adminGroup, $groups] = $groupsInfo->get();
193
+
194
+        if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
195
+            $isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
196
+                return $ldapFound || $backend instanceof User_Proxy;
197
+            });
198
+        }
199
+
200
+        $disabledUsers = -1;
201
+        $userCount = 0;
202
+
203
+        if (!$isLDAPUsed) {
204
+            if ($this->isAdmin) {
205
+                $disabledUsers = $this->userManager->countDisabledUsers();
206
+                $userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
207
+                    return $v + (int)$w;
208
+                }, 0);
209
+            } else {
210
+                // User is subadmin !
211
+                // Map group list to names to retrieve the countDisabledUsersOfGroups
212
+                $userGroups = $this->groupManager->getUserGroups($user);
213
+                $groupsNames = [];
214
+
215
+                foreach ($groups as $key => $group) {
216
+                    // $userCount += (int)$group['usercount'];
217
+                    array_push($groupsNames, $group['name']);
218
+                    // we prevent subadmins from looking up themselves
219
+                    // so we lower the count of the groups he belongs to
220
+                    if (array_key_exists($group['id'], $userGroups)) {
221
+                        $groups[$key]['usercount']--;
222
+                        $userCount -= 1; // we also lower from one the total count
223
+                    }
224
+                }
225
+                $userCount += $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
226
+                $disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsNames);
227
+            }
228
+
229
+            $userCount -= $disabledUsers;
230
+        }
231
+
232
+        $disabledUsersGroup = [
233
+            'id' => 'disabled',
234
+            'name' => 'Disabled users',
235
+            'usercount' => $disabledUsers
236
+        ];
237
+
238
+        /* QUOTAS PRESETS */
239
+        $quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
240
+        $defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
241
+
242
+        $event = new BeforeTemplateRenderedEvent();
243
+        $this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
244
+        $this->dispatcher->dispatchTyped($event);
245
+
246
+        /* LANGUAGES */
247
+        $languages = $this->l10nFactory->getLanguages();
248
+
249
+        /* FINAL DATA */
250
+        $serverData = [];
251
+        // groups
252
+        $serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
253
+        // Various data
254
+        $serverData['isAdmin'] = $this->isAdmin;
255
+        $serverData['sortGroups'] = $sortGroupsBy;
256
+        $serverData['quotaPreset'] = $quotaPreset;
257
+        $serverData['userCount'] = $userCount;
258
+        $serverData['languages'] = $languages;
259
+        $serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
260
+        $serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
261
+        // Settings
262
+        $serverData['defaultQuota'] = $defaultQuota;
263
+        $serverData['canChangePassword'] = $canChangePassword;
264
+        $serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
265
+        $serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
266
+        $serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
267
+
268
+        return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
269
+    }
270
+
271
+    /**
272
+     * @param string $key
273
+     * @param string $value
274
+     *
275
+     * @return JSONResponse
276
+     */
277
+    public function setPreference(string $key, string $value): JSONResponse {
278
+        $allowed = ['newUser.sendEmail'];
279
+        if (!in_array($key, $allowed, true)) {
280
+            return new JSONResponse([], Http::STATUS_FORBIDDEN);
281
+        }
282
+
283
+        $this->config->setAppValue('core', $key, $value);
284
+
285
+        return new JSONResponse([]);
286
+    }
287
+
288
+    /**
289
+     * Parse the app value for quota_present
290
+     *
291
+     * @param string $quotaPreset
292
+     * @return array
293
+     */
294
+    protected function parseQuotaPreset(string $quotaPreset): array {
295
+        // 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
296
+        $presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
297
+        // Drop default and none, Make array indexes numerically
298
+        return array_values(array_diff($presets, ['default', 'none']));
299
+    }
300
+
301
+    /**
302
+     * check if the admin can change the users password
303
+     *
304
+     * The admin can change the passwords if:
305
+     *
306
+     *   - no encryption module is loaded and encryption is disabled
307
+     *   - encryption module is loaded but it doesn't require per user keys
308
+     *
309
+     * The admin can not change the passwords if:
310
+     *
311
+     *   - an encryption module is loaded and it uses per-user keys
312
+     *   - encryption is enabled but no encryption modules are loaded
313
+     *
314
+     * @return bool
315
+     */
316
+    protected function canAdminChangeUserPasswords(): bool {
317
+        $isEncryptionEnabled = $this->encryptionManager->isEnabled();
318
+        try {
319
+            $noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
320
+            $isEncryptionModuleLoaded = true;
321
+        } catch (ModuleDoesNotExistsException $e) {
322
+            $noUserSpecificEncryptionKeys = true;
323
+            $isEncryptionModuleLoaded = false;
324
+        }
325
+        $canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
326
+            || (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
327
+
328
+        return $canChangePassword;
329
+    }
330
+
331
+    /**
332
+     * @NoAdminRequired
333
+     * @NoSubAdminRequired
334
+     * @PasswordConfirmationRequired
335
+     *
336
+     * @param string|null $avatarScope
337
+     * @param string|null $displayname
338
+     * @param string|null $displaynameScope
339
+     * @param string|null $phone
340
+     * @param string|null $phoneScope
341
+     * @param string|null $email
342
+     * @param string|null $emailScope
343
+     * @param string|null $website
344
+     * @param string|null $websiteScope
345
+     * @param string|null $address
346
+     * @param string|null $addressScope
347
+     * @param string|null $twitter
348
+     * @param string|null $twitterScope
349
+     *
350
+     * @return DataResponse
351
+     */
352
+    public function setUserSettings(?string $avatarScope = null,
353
+                                    ?string $displayname = null,
354
+                                    ?string $displaynameScope = null,
355
+                                    ?string $phone = null,
356
+                                    ?string $phoneScope = null,
357
+                                    ?string $email = null,
358
+                                    ?string $emailScope = null,
359
+                                    ?string $website = null,
360
+                                    ?string $websiteScope = null,
361
+                                    ?string $address = null,
362
+                                    ?string $addressScope = null,
363
+                                    ?string $twitter = null,
364
+                                    ?string $twitterScope = null
365
+    ) {
366
+        $email = strtolower($email);
367
+        if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
368
+            return new DataResponse(
369
+                [
370
+                    'status' => 'error',
371
+                    'data' => [
372
+                        'message' => $this->l10n->t('Invalid mail address')
373
+                    ]
374
+                ],
375
+                Http::STATUS_UNPROCESSABLE_ENTITY
376
+            );
377
+        }
378
+        $user = $this->userSession->getUser();
379
+        $data = $this->accountManager->getUser($user);
380
+        $data[IAccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
381
+        if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
382
+            $data[IAccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
383
+            $data[IAccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
384
+        }
385
+        if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
386
+            $shareProvider = \OC::$server->query(FederatedShareProvider::class);
387
+            if ($shareProvider->isLookupServerUploadEnabled()) {
388
+                $data[IAccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
389
+                $data[IAccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
390
+                $data[IAccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
391
+                $data[IAccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
392
+            }
393
+        }
394
+        try {
395
+            $data = $this->saveUserSettings($user, $data);
396
+            return new DataResponse(
397
+                [
398
+                    'status' => 'success',
399
+                    'data' => [
400
+                        'userId' => $user->getUID(),
401
+                        'avatarScope' => $data[IAccountManager::PROPERTY_AVATAR]['scope'],
402
+                        'displayname' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['value'],
403
+                        'displaynameScope' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'],
404
+                        'phone' => $data[IAccountManager::PROPERTY_PHONE]['value'],
405
+                        'phoneScope' => $data[IAccountManager::PROPERTY_PHONE]['scope'],
406
+                        'email' => $data[IAccountManager::PROPERTY_EMAIL]['value'],
407
+                        'emailScope' => $data[IAccountManager::PROPERTY_EMAIL]['scope'],
408
+                        'website' => $data[IAccountManager::PROPERTY_WEBSITE]['value'],
409
+                        'websiteScope' => $data[IAccountManager::PROPERTY_WEBSITE]['scope'],
410
+                        'address' => $data[IAccountManager::PROPERTY_ADDRESS]['value'],
411
+                        'addressScope' => $data[IAccountManager::PROPERTY_ADDRESS]['scope'],
412
+                        'twitter' => $data[IAccountManager::PROPERTY_TWITTER]['value'],
413
+                        'twitterScope' => $data[IAccountManager::PROPERTY_TWITTER]['scope'],
414
+                        'message' => $this->l10n->t('Settings saved')
415
+                    ]
416
+                ],
417
+                Http::STATUS_OK
418
+            );
419
+        } catch (ForbiddenException $e) {
420
+            return new DataResponse([
421
+                'status' => 'error',
422
+                'data' => [
423
+                    'message' => $e->getMessage()
424
+                ],
425
+            ]);
426
+        } catch (\InvalidArgumentException $e) {
427
+            return new DataResponse([
428
+                'status' => 'error',
429
+                'data' => [
430
+                    'message' => $e->getMessage()
431
+                ],
432
+            ]);
433
+        }
434
+    }
435
+    /**
436
+     * update account manager with new user data
437
+     *
438
+     * @param IUser $user
439
+     * @param array $data
440
+     * @return array
441
+     * @throws ForbiddenException
442
+     * @throws \InvalidArgumentException
443
+     */
444
+    protected function saveUserSettings(IUser $user, array $data): array {
445
+        // keep the user back-end up-to-date with the latest display name and email
446
+        // address
447
+        $oldDisplayName = $user->getDisplayName();
448
+        $oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
449
+        if (isset($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
450
+            && $oldDisplayName !== $data[IAccountManager::PROPERTY_DISPLAYNAME]['value']
451
+        ) {
452
+            $result = $user->setDisplayName($data[IAccountManager::PROPERTY_DISPLAYNAME]['value']);
453
+            if ($result === false) {
454
+                throw new ForbiddenException($this->l10n->t('Unable to change full name'));
455
+            }
456
+        }
457
+
458
+        $oldEmailAddress = $user->getEMailAddress();
459
+        $oldEmailAddress = is_null($oldEmailAddress) ? '' : strtolower($oldEmailAddress);
460
+        if (isset($data[IAccountManager::PROPERTY_EMAIL]['value'])
461
+            && $oldEmailAddress !== $data[IAccountManager::PROPERTY_EMAIL]['value']
462
+        ) {
463
+            // this is the only permission a backend provides and is also used
464
+            // for the permission of setting a email address
465
+            if (!$user->canChangeDisplayName()) {
466
+                throw new ForbiddenException($this->l10n->t('Unable to change email address'));
467
+            }
468
+            $user->setEMailAddress($data[IAccountManager::PROPERTY_EMAIL]['value']);
469
+        }
470
+
471
+        try {
472
+            return $this->accountManager->updateUser($user, $data, true);
473
+        } catch (\InvalidArgumentException $e) {
474
+            if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
475
+                throw new \InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
476
+            }
477
+            throw new \InvalidArgumentException($this->l10n->t('Some account data was invalid'));
478
+        }
479
+    }
480
+
481
+    /**
482
+     * Set the mail address of a user
483
+     *
484
+     * @NoAdminRequired
485
+     * @NoSubAdminRequired
486
+     * @PasswordConfirmationRequired
487
+     *
488
+     * @param string $account
489
+     * @param bool $onlyVerificationCode only return verification code without updating the data
490
+     * @return DataResponse
491
+     */
492
+    public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
493
+        $user = $this->userSession->getUser();
494
+
495
+        if ($user === null) {
496
+            return new DataResponse([], Http::STATUS_BAD_REQUEST);
497
+        }
498
+
499
+        $accountData = $this->accountManager->getUser($user);
500
+        $cloudId = $user->getCloudId();
501
+        $message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
502
+        $signature = $this->signMessage($user, $message);
503
+
504
+        $code = $message . ' ' . $signature;
505
+        $codeMd5 = $message . ' ' . md5($signature);
506
+
507
+        switch ($account) {
508
+            case 'verify-twitter':
509
+                $accountData[IAccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
510
+                $msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
511
+                $code = $codeMd5;
512
+                $type = IAccountManager::PROPERTY_TWITTER;
513
+                $accountData[IAccountManager::PROPERTY_TWITTER]['signature'] = $signature;
514
+                break;
515
+            case 'verify-website':
516
+                $accountData[IAccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
517
+                $msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
518
+                $type = IAccountManager::PROPERTY_WEBSITE;
519
+                $accountData[IAccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
520
+                break;
521
+            default:
522
+                return new DataResponse([], Http::STATUS_BAD_REQUEST);
523
+        }
524
+
525
+        if ($onlyVerificationCode === false) {
526
+            $accountData = $this->accountManager->updateUser($user, $accountData);
527
+            $data = $accountData[$type]['value'];
528
+
529
+            $this->jobList->add(VerifyUserData::class,
530
+                [
531
+                    'verificationCode' => $code,
532
+                    'data' => $data,
533
+                    'type' => $type,
534
+                    'uid' => $user->getUID(),
535
+                    'try' => 0,
536
+                    'lastRun' => $this->getCurrentTime()
537
+                ]
538
+            );
539
+        }
540
+
541
+        return new DataResponse(['msg' => $msg, 'code' => $code]);
542
+    }
543
+
544
+    /**
545
+     * get current timestamp
546
+     *
547
+     * @return int
548
+     */
549
+    protected function getCurrentTime(): int {
550
+        return time();
551
+    }
552
+
553
+    /**
554
+     * sign message with users private key
555
+     *
556
+     * @param IUser $user
557
+     * @param string $message
558
+     *
559
+     * @return string base64 encoded signature
560
+     */
561
+    protected function signMessage(IUser $user, string $message): string {
562
+        $privateKey = $this->keyManager->getKey($user)->getPrivate();
563
+        openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
564
+        return base64_encode($signature);
565
+    }
566 566
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -192,7 +192,7 @@  discard block
 block discarded – undo
192 192
 		[$adminGroup, $groups] = $groupsInfo->get();
193 193
 
194 194
 		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
195
-			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
195
+			$isLDAPUsed = (bool) array_reduce($this->userManager->getBackends(), function($ldapFound, $backend) {
196 196
 				return $ldapFound || $backend instanceof User_Proxy;
197 197
 			});
198 198
 		}
@@ -203,8 +203,8 @@  discard block
 block discarded – undo
203 203
 		if (!$isLDAPUsed) {
204 204
 			if ($this->isAdmin) {
205 205
 				$disabledUsers = $this->userManager->countDisabledUsers();
206
-				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
207
-					return $v + (int)$w;
206
+				$userCount = array_reduce($this->userManager->countUsers(), function($v, $w) {
207
+					return $v + (int) $w;
208 208
 				}, 0);
209 209
 			} else {
210 210
 				// User is subadmin !
@@ -498,11 +498,11 @@  discard block
 block discarded – undo
498 498
 
499 499
 		$accountData = $this->accountManager->getUser($user);
500 500
 		$cloudId = $user->getCloudId();
501
-		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
501
+		$message = 'Use my Federated Cloud ID to share with me: '.$cloudId;
502 502
 		$signature = $this->signMessage($user, $message);
503 503
 
504
-		$code = $message . ' ' . $signature;
505
-		$codeMd5 = $message . ' ' . md5($signature);
504
+		$code = $message.' '.$signature;
505
+		$codeMd5 = $message.' '.md5($signature);
506 506
 
507 507
 		switch ($account) {
508 508
 			case 'verify-twitter':
Please login to merge, or discard this patch.
apps/files_versions/lib/Sabre/RootCollection.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -34,51 +34,51 @@
 block discarded – undo
34 34
 
35 35
 class RootCollection extends AbstractPrincipalCollection {
36 36
 
37
-	/** @var IRootFolder */
38
-	private $rootFolder;
37
+    /** @var IRootFolder */
38
+    private $rootFolder;
39 39
 
40
-	/** @var IUserManager */
41
-	private $userManager;
40
+    /** @var IUserManager */
41
+    private $userManager;
42 42
 
43
-	/** @var IVersionManager */
44
-	private $versionManager;
43
+    /** @var IVersionManager */
44
+    private $versionManager;
45 45
 
46
-	public function __construct(
47
-		PrincipalBackend\BackendInterface $principalBackend,
48
-		IRootFolder $rootFolder,
49
-		IConfig $config,
50
-		IUserManager $userManager,
51
-		IVersionManager $versionManager
52
-	) {
53
-		parent::__construct($principalBackend, 'principals/users');
46
+    public function __construct(
47
+        PrincipalBackend\BackendInterface $principalBackend,
48
+        IRootFolder $rootFolder,
49
+        IConfig $config,
50
+        IUserManager $userManager,
51
+        IVersionManager $versionManager
52
+    ) {
53
+        parent::__construct($principalBackend, 'principals/users');
54 54
 
55
-		$this->rootFolder = $rootFolder;
56
-		$this->userManager = $userManager;
57
-		$this->versionManager = $versionManager;
55
+        $this->rootFolder = $rootFolder;
56
+        $this->userManager = $userManager;
57
+        $this->versionManager = $versionManager;
58 58
 
59
-		$this->disableListing = !$config->getSystemValue('debug', false);
60
-	}
59
+        $this->disableListing = !$config->getSystemValue('debug', false);
60
+    }
61 61
 
62
-	/**
63
-	 * This method returns a node for a principal.
64
-	 *
65
-	 * The passed array contains principal information, and is guaranteed to
66
-	 * at least contain a uri item. Other properties may or may not be
67
-	 * supplied by the authentication backend.
68
-	 *
69
-	 * @param array $principalInfo
70
-	 * @return INode
71
-	 */
72
-	public function getChildForPrincipal(array $principalInfo) {
73
-		[, $name] = \Sabre\Uri\split($principalInfo['uri']);
74
-		$user = \OC::$server->getUserSession()->getUser();
75
-		if (is_null($user) || $name !== $user->getUID()) {
76
-			throw new \Sabre\DAV\Exception\Forbidden();
77
-		}
78
-		return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager);
79
-	}
62
+    /**
63
+     * This method returns a node for a principal.
64
+     *
65
+     * The passed array contains principal information, and is guaranteed to
66
+     * at least contain a uri item. Other properties may or may not be
67
+     * supplied by the authentication backend.
68
+     *
69
+     * @param array $principalInfo
70
+     * @return INode
71
+     */
72
+    public function getChildForPrincipal(array $principalInfo) {
73
+        [, $name] = \Sabre\Uri\split($principalInfo['uri']);
74
+        $user = \OC::$server->getUserSession()->getUser();
75
+        if (is_null($user) || $name !== $user->getUID()) {
76
+            throw new \Sabre\DAV\Exception\Forbidden();
77
+        }
78
+        return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager);
79
+    }
80 80
 
81
-	public function getName() {
82
-		return 'versions';
83
-	}
81
+    public function getName() {
82
+        return 'versions';
83
+    }
84 84
 }
Please login to merge, or discard this patch.
apps/files_versions/lib/Sabre/VersionHome.php 1 patch
Indentation   +75 added lines, -75 removed lines patch added patch discarded remove patch
@@ -33,79 +33,79 @@
 block discarded – undo
33 33
 
34 34
 class VersionHome implements ICollection {
35 35
 
36
-	/** @var array */
37
-	private $principalInfo;
38
-
39
-	/** @var IRootFolder */
40
-	private $rootFolder;
41
-
42
-	/** @var IUserManager */
43
-	private $userManager;
44
-
45
-	/** @var IVersionManager */
46
-	private $versionManager;
47
-
48
-	public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) {
49
-		$this->principalInfo = $principalInfo;
50
-		$this->rootFolder = $rootFolder;
51
-		$this->userManager = $userManager;
52
-		$this->versionManager = $versionManager;
53
-	}
54
-
55
-	private function getUser() {
56
-		[, $name] = \Sabre\Uri\split($this->principalInfo['uri']);
57
-		$user = $this->userManager->get($name);
58
-		if (!$user) {
59
-			throw new NoUserException();
60
-		}
61
-		return $user;
62
-	}
63
-
64
-	public function delete() {
65
-		throw new Forbidden();
66
-	}
67
-
68
-	public function getName(): string {
69
-		return $this->getUser()->getUID();
70
-	}
71
-
72
-	public function setName($name) {
73
-		throw new Forbidden();
74
-	}
75
-
76
-	public function createFile($name, $data = null) {
77
-		throw new Forbidden();
78
-	}
79
-
80
-	public function createDirectory($name) {
81
-		throw new Forbidden();
82
-	}
83
-
84
-	public function getChild($name) {
85
-		$user = $this->getUser();
86
-
87
-		if ($name === 'versions') {
88
-			return new VersionRoot($user, $this->rootFolder, $this->versionManager);
89
-		}
90
-		if ($name === 'restore') {
91
-			return new RestoreFolder();
92
-		}
93
-	}
94
-
95
-	public function getChildren() {
96
-		$user = $this->getUser();
97
-
98
-		return [
99
-			new VersionRoot($user, $this->rootFolder, $this->versionManager),
100
-			new RestoreFolder(),
101
-		];
102
-	}
103
-
104
-	public function childExists($name) {
105
-		return $name === 'versions' || $name === 'restore';
106
-	}
107
-
108
-	public function getLastModified() {
109
-		return 0;
110
-	}
36
+    /** @var array */
37
+    private $principalInfo;
38
+
39
+    /** @var IRootFolder */
40
+    private $rootFolder;
41
+
42
+    /** @var IUserManager */
43
+    private $userManager;
44
+
45
+    /** @var IVersionManager */
46
+    private $versionManager;
47
+
48
+    public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) {
49
+        $this->principalInfo = $principalInfo;
50
+        $this->rootFolder = $rootFolder;
51
+        $this->userManager = $userManager;
52
+        $this->versionManager = $versionManager;
53
+    }
54
+
55
+    private function getUser() {
56
+        [, $name] = \Sabre\Uri\split($this->principalInfo['uri']);
57
+        $user = $this->userManager->get($name);
58
+        if (!$user) {
59
+            throw new NoUserException();
60
+        }
61
+        return $user;
62
+    }
63
+
64
+    public function delete() {
65
+        throw new Forbidden();
66
+    }
67
+
68
+    public function getName(): string {
69
+        return $this->getUser()->getUID();
70
+    }
71
+
72
+    public function setName($name) {
73
+        throw new Forbidden();
74
+    }
75
+
76
+    public function createFile($name, $data = null) {
77
+        throw new Forbidden();
78
+    }
79
+
80
+    public function createDirectory($name) {
81
+        throw new Forbidden();
82
+    }
83
+
84
+    public function getChild($name) {
85
+        $user = $this->getUser();
86
+
87
+        if ($name === 'versions') {
88
+            return new VersionRoot($user, $this->rootFolder, $this->versionManager);
89
+        }
90
+        if ($name === 'restore') {
91
+            return new RestoreFolder();
92
+        }
93
+    }
94
+
95
+    public function getChildren() {
96
+        $user = $this->getUser();
97
+
98
+        return [
99
+            new VersionRoot($user, $this->rootFolder, $this->versionManager),
100
+            new RestoreFolder(),
101
+        ];
102
+    }
103
+
104
+    public function childExists($name) {
105
+        return $name === 'versions' || $name === 'restore';
106
+    }
107
+
108
+    public function getLastModified() {
109
+        return 0;
110
+    }
111 111
 }
Please login to merge, or discard this patch.
apps/files_versions/lib/Storage.php 2 patches
Indentation   +803 added lines, -803 removed lines patch added patch discarded remove patch
@@ -59,807 +59,807 @@
 block discarded – undo
59 59
 use OCP\User;
60 60
 
61 61
 class Storage {
62
-	public const DEFAULTENABLED = true;
63
-	public const DEFAULTMAXSIZE = 50; // unit: percentage; 50% of available disk space/quota
64
-	public const VERSIONS_ROOT = 'files_versions/';
65
-
66
-	public const DELETE_TRIGGER_MASTER_REMOVED = 0;
67
-	public const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
68
-	public const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
69
-
70
-	// files for which we can remove the versions after the delete operation was successful
71
-	private static $deletedFiles = [];
72
-
73
-	private static $sourcePathAndUser = [];
74
-
75
-	private static $max_versions_per_interval = [
76
-		//first 10sec, one version every 2sec
77
-		1 => ['intervalEndsAfter' => 10,      'step' => 2],
78
-		//next minute, one version every 10sec
79
-		2 => ['intervalEndsAfter' => 60,      'step' => 10],
80
-		//next hour, one version every minute
81
-		3 => ['intervalEndsAfter' => 3600,    'step' => 60],
82
-		//next 24h, one version every hour
83
-		4 => ['intervalEndsAfter' => 86400,   'step' => 3600],
84
-		//next 30days, one version per day
85
-		5 => ['intervalEndsAfter' => 2592000, 'step' => 86400],
86
-		//until the end one version per week
87
-		6 => ['intervalEndsAfter' => -1,      'step' => 604800],
88
-	];
89
-
90
-	/** @var \OCA\Files_Versions\AppInfo\Application */
91
-	private static $application;
92
-
93
-	/**
94
-	 * get the UID of the owner of the file and the path to the file relative to
95
-	 * owners files folder
96
-	 *
97
-	 * @param string $filename
98
-	 * @return array
99
-	 * @throws \OC\User\NoUserException
100
-	 */
101
-	public static function getUidAndFilename($filename) {
102
-		$uid = Filesystem::getOwner($filename);
103
-		$userManager = \OC::$server->getUserManager();
104
-		// if the user with the UID doesn't exists, e.g. because the UID points
105
-		// to a remote user with a federated cloud ID we use the current logged-in
106
-		// user. We need a valid local user to create the versions
107
-		if (!$userManager->userExists($uid)) {
108
-			$uid = User::getUser();
109
-		}
110
-		Filesystem::initMountPoints($uid);
111
-		if ($uid !== User::getUser()) {
112
-			$info = Filesystem::getFileInfo($filename);
113
-			$ownerView = new View('/'.$uid.'/files');
114
-			try {
115
-				$filename = $ownerView->getPath($info['fileid']);
116
-				// make sure that the file name doesn't end with a trailing slash
117
-				// can for example happen single files shared across servers
118
-				$filename = rtrim($filename, '/');
119
-			} catch (NotFoundException $e) {
120
-				$filename = null;
121
-			}
122
-		}
123
-		return [$uid, $filename];
124
-	}
125
-
126
-	/**
127
-	 * Remember the owner and the owner path of the source file
128
-	 *
129
-	 * @param string $source source path
130
-	 */
131
-	public static function setSourcePathAndUser($source) {
132
-		[$uid, $path] = self::getUidAndFilename($source);
133
-		self::$sourcePathAndUser[$source] = ['uid' => $uid, 'path' => $path];
134
-	}
135
-
136
-	/**
137
-	 * Gets the owner and the owner path from the source path
138
-	 *
139
-	 * @param string $source source path
140
-	 * @return array with user id and path
141
-	 */
142
-	public static function getSourcePathAndUser($source) {
143
-		if (isset(self::$sourcePathAndUser[$source])) {
144
-			$uid = self::$sourcePathAndUser[$source]['uid'];
145
-			$path = self::$sourcePathAndUser[$source]['path'];
146
-			unset(self::$sourcePathAndUser[$source]);
147
-		} else {
148
-			$uid = $path = false;
149
-		}
150
-		return [$uid, $path];
151
-	}
152
-
153
-	/**
154
-	 * get current size of all versions from a given user
155
-	 *
156
-	 * @param string $user user who owns the versions
157
-	 * @return int versions size
158
-	 */
159
-	private static function getVersionsSize($user) {
160
-		$view = new View('/' . $user);
161
-		$fileInfo = $view->getFileInfo('/files_versions');
162
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
163
-	}
164
-
165
-	/**
166
-	 * store a new version of a file.
167
-	 */
168
-	public static function store($filename) {
169
-
170
-		// if the file gets streamed we need to remove the .part extension
171
-		// to get the right target
172
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
173
-		if ($ext === 'part') {
174
-			$filename = substr($filename, 0, -5);
175
-		}
176
-
177
-		// we only handle existing files
178
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
179
-			return false;
180
-		}
181
-
182
-		[$uid, $filename] = self::getUidAndFilename($filename);
183
-
184
-		$files_view = new View('/'.$uid .'/files');
185
-
186
-		$eventDispatcher = \OC::$server->getEventDispatcher();
187
-		$fileInfo = $files_view->getFileInfo($filename);
188
-		$id = $fileInfo->getId();
189
-		$nodes = \OC::$server->getRootFolder()->getUserFolder($uid)->getById($id);
190
-		foreach ($nodes as $node) {
191
-			$event = new CreateVersionEvent($node);
192
-			$eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
193
-			if ($event->shouldCreateVersion() === false) {
194
-				return false;
195
-			}
196
-		}
197
-
198
-		// no use making versions for empty files
199
-		if ($fileInfo->getSize() === 0) {
200
-			return false;
201
-		}
202
-
203
-		/** @var IVersionManager $versionManager */
204
-		$versionManager = \OC::$server->query(IVersionManager::class);
205
-		$userManager = \OC::$server->getUserManager();
206
-		$user = $userManager->get($uid);
207
-
208
-		$versionManager->createVersion($user, $fileInfo);
209
-	}
210
-
211
-
212
-	/**
213
-	 * mark file as deleted so that we can remove the versions if the file is gone
214
-	 * @param string $path
215
-	 */
216
-	public static function markDeletedFile($path) {
217
-		[$uid, $filename] = self::getUidAndFilename($path);
218
-		self::$deletedFiles[$path] = [
219
-			'uid' => $uid,
220
-			'filename' => $filename];
221
-	}
222
-
223
-	/**
224
-	 * delete the version from the storage and cache
225
-	 *
226
-	 * @param View $view
227
-	 * @param string $path
228
-	 */
229
-	protected static function deleteVersion($view, $path) {
230
-		$view->unlink($path);
231
-		/**
232
-		 * @var \OC\Files\Storage\Storage $storage
233
-		 * @var string $internalPath
234
-		 */
235
-		[$storage, $internalPath] = $view->resolvePath($path);
236
-		$cache = $storage->getCache($internalPath);
237
-		$cache->remove($internalPath);
238
-	}
239
-
240
-	/**
241
-	 * Delete versions of a file
242
-	 */
243
-	public static function delete($path) {
244
-		$deletedFile = self::$deletedFiles[$path];
245
-		$uid = $deletedFile['uid'];
246
-		$filename = $deletedFile['filename'];
247
-
248
-		if (!Filesystem::file_exists($path)) {
249
-			$view = new View('/' . $uid . '/files_versions');
250
-
251
-			$versions = self::getVersions($uid, $filename);
252
-			if (!empty($versions)) {
253
-				foreach ($versions as $v) {
254
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
255
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
256
-					\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
257
-				}
258
-			}
259
-		}
260
-		unset(self::$deletedFiles[$path]);
261
-	}
262
-
263
-	/**
264
-	 * Rename or copy versions of a file of the given paths
265
-	 *
266
-	 * @param string $sourcePath source path of the file to move, relative to
267
-	 * the currently logged in user's "files" folder
268
-	 * @param string $targetPath target path of the file to move, relative to
269
-	 * the currently logged in user's "files" folder
270
-	 * @param string $operation can be 'copy' or 'rename'
271
-	 */
272
-	public static function renameOrCopy($sourcePath, $targetPath, $operation) {
273
-		[$sourceOwner, $sourcePath] = self::getSourcePathAndUser($sourcePath);
274
-
275
-		// it was a upload of a existing file if no old path exists
276
-		// in this case the pre-hook already called the store method and we can
277
-		// stop here
278
-		if ($sourcePath === false) {
279
-			return true;
280
-		}
281
-
282
-		[$targetOwner, $targetPath] = self::getUidAndFilename($targetPath);
283
-
284
-		$sourcePath = ltrim($sourcePath, '/');
285
-		$targetPath = ltrim($targetPath, '/');
286
-
287
-		$rootView = new View('');
288
-
289
-		// did we move a directory ?
290
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
291
-			// does the directory exists for versions too ?
292
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
293
-				// create missing dirs if necessary
294
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
295
-
296
-				// move the directory containing the versions
297
-				$rootView->$operation(
298
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
299
-					'/' . $targetOwner . '/files_versions/' . $targetPath
300
-				);
301
-			}
302
-		} elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
303
-			// create missing dirs if necessary
304
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
305
-
306
-			foreach ($versions as $v) {
307
-				// move each version one by one to the target directory
308
-				$rootView->$operation(
309
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
310
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
311
-				);
312
-			}
313
-		}
314
-
315
-		// if we moved versions directly for a file, schedule expiration check for that file
316
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
317
-			self::scheduleExpire($targetOwner, $targetPath);
318
-		}
319
-	}
320
-
321
-	/**
322
-	 * Rollback to an old version of a file.
323
-	 *
324
-	 * @param string $file file name
325
-	 * @param int $revision revision timestamp
326
-	 * @return bool
327
-	 */
328
-	public static function rollback(string $file, int $revision, IUser $user) {
329
-
330
-		// add expected leading slash
331
-		$filename = '/' . ltrim($file, '/');
332
-
333
-		// Fetch the userfolder to trigger view hooks
334
-		$userFolder = \OC::$server->getUserFolder($user->getUID());
335
-
336
-		$users_view = new View('/'.$user->getUID());
337
-		$files_view = new View('/'. $user->getUID().'/files');
338
-
339
-		$versionCreated = false;
340
-
341
-		$fileInfo = $files_view->getFileInfo($file);
342
-
343
-		// check if user has the permissions to revert a version
344
-		if (!$fileInfo->isUpdateable()) {
345
-			return false;
346
-		}
347
-
348
-		//first create a new version
349
-		$version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
350
-		if (!$users_view->file_exists($version)) {
351
-			$users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
352
-			$versionCreated = true;
353
-		}
354
-
355
-		$fileToRestore = 'files_versions' . $filename . '.v' . $revision;
356
-
357
-		// Restore encrypted version of the old file for the newly restored file
358
-		// This has to happen manually here since the file is manually copied below
359
-		$oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
360
-		$oldFileInfo = $users_view->getFileInfo($fileToRestore);
361
-		$cache = $fileInfo->getStorage()->getCache();
362
-		$cache->update(
363
-			$fileInfo->getId(), [
364
-				'encrypted' => $oldVersion,
365
-				'encryptedVersion' => $oldVersion,
366
-				'size' => $oldFileInfo->getSize()
367
-			]
368
-		);
369
-
370
-		// rollback
371
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
372
-			$files_view->touch($file, $revision);
373
-			Storage::scheduleExpire($user->getUID(), $file);
374
-
375
-			$node = $userFolder->get($file);
376
-
377
-			// TODO: move away from those legacy hooks!
378
-			\OC_Hook::emit('\OCP\Versions', 'rollback', [
379
-				'path' => $filename,
380
-				'revision' => $revision,
381
-				'node' => $node,
382
-			]);
383
-			return true;
384
-		} elseif ($versionCreated) {
385
-			self::deleteVersion($users_view, $version);
386
-		}
387
-
388
-		return false;
389
-	}
390
-
391
-	/**
392
-	 * Stream copy file contents from $path1 to $path2
393
-	 *
394
-	 * @param View $view view to use for copying
395
-	 * @param string $path1 source file to copy
396
-	 * @param string $path2 target file
397
-	 *
398
-	 * @return bool true for success, false otherwise
399
-	 */
400
-	private static function copyFileContents($view, $path1, $path2) {
401
-		/** @var \OC\Files\Storage\Storage $storage1 */
402
-		[$storage1, $internalPath1] = $view->resolvePath($path1);
403
-		/** @var \OC\Files\Storage\Storage $storage2 */
404
-		[$storage2, $internalPath2] = $view->resolvePath($path2);
405
-
406
-		$view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
-		$view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
-
409
-		// TODO add a proper way of overwriting a file while maintaining file ids
410
-		if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
-			$source = $storage1->fopen($internalPath1, 'r');
412
-			$target = $storage2->fopen($internalPath2, 'w');
413
-			[, $result] = \OC_Helper::streamCopy($source, $target);
414
-			fclose($source);
415
-			fclose($target);
416
-
417
-			if ($result !== false) {
418
-				$storage1->unlink($internalPath1);
419
-			}
420
-		} else {
421
-			$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
-		}
423
-
424
-		$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
-		$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
-
427
-		return ($result !== false);
428
-	}
429
-
430
-	/**
431
-	 * get a list of all available versions of a file in descending chronological order
432
-	 * @param string $uid user id from the owner of the file
433
-	 * @param string $filename file to find versions of, relative to the user files dir
434
-	 * @param string $userFullPath
435
-	 * @return array versions newest version first
436
-	 */
437
-	public static function getVersions($uid, $filename, $userFullPath = '') {
438
-		$versions = [];
439
-		if (empty($filename)) {
440
-			return $versions;
441
-		}
442
-		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
444
-
445
-		$pathinfo = pathinfo($filename);
446
-		$versionedFile = $pathinfo['basename'];
447
-
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
-
450
-		$dirContent = false;
451
-		if ($view->is_dir($dir)) {
452
-			$dirContent = $view->opendir($dir);
453
-		}
454
-
455
-		if ($dirContent === false) {
456
-			return $versions;
457
-		}
458
-
459
-		if (is_resource($dirContent)) {
460
-			while (($entryName = readdir($dirContent)) !== false) {
461
-				if (!Filesystem::isIgnoredDir($entryName)) {
462
-					$pathparts = pathinfo($entryName);
463
-					$filename = $pathparts['filename'];
464
-					if ($filename === $versionedFile) {
465
-						$pathparts = pathinfo($entryName);
466
-						$timestamp = substr($pathparts['extension'], 1);
467
-						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
469
-						$versions[$key]['version'] = $timestamp;
470
-						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
-						if (empty($userFullPath)) {
472
-							$versions[$key]['preview'] = '';
473
-						} else {
474
-							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
-						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
-						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
-						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
-					}
481
-				}
482
-			}
483
-			closedir($dirContent);
484
-		}
485
-
486
-		// sort with newest version first
487
-		krsort($versions);
488
-
489
-		return $versions;
490
-	}
491
-
492
-	/**
493
-	 * Expire versions that older than max version retention time
494
-	 * @param string $uid
495
-	 */
496
-	public static function expireOlderThanMaxForUser($uid) {
497
-		$expiration = self::getExpiration();
498
-		$threshold = $expiration->getMaxAgeAsTimestamp();
499
-		$versions = self::getAllVersions($uid);
500
-		if (!$threshold || empty($versions['all'])) {
501
-			return;
502
-		}
503
-
504
-		$toDelete = [];
505
-		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] < $threshold) {
507
-				$toDelete[$key] = $version;
508
-			} else {
509
-				//Versions are sorted by time - nothing mo to iterate.
510
-				break;
511
-			}
512
-		}
513
-
514
-		$view = new View('/' . $uid . '/files_versions');
515
-		if (!empty($toDelete)) {
516
-			foreach ($toDelete as $version) {
517
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
-				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
520
-			}
521
-		}
522
-	}
523
-
524
-	/**
525
-	 * translate a timestamp into a string like "5 days ago"
526
-	 * @param int $timestamp
527
-	 * @return string for example "5 days ago"
528
-	 */
529
-	private static function getHumanReadableTimestamp($timestamp) {
530
-		$diff = time() - $timestamp;
531
-
532
-		if ($diff < 60) { // first minute
533
-			return  $diff . " seconds ago";
534
-		} elseif ($diff < 3600) { //first hour
535
-			return round($diff / 60) . " minutes ago";
536
-		} elseif ($diff < 86400) { // first day
537
-			return round($diff / 3600) . " hours ago";
538
-		} elseif ($diff < 604800) { //first week
539
-			return round($diff / 86400) . " days ago";
540
-		} elseif ($diff < 2419200) { //first month
541
-			return round($diff / 604800) . " weeks ago";
542
-		} elseif ($diff < 29030400) { // first year
543
-			return round($diff / 2419200) . " months ago";
544
-		} else {
545
-			return round($diff / 29030400) . " years ago";
546
-		}
547
-	}
548
-
549
-	/**
550
-	 * returns all stored file versions from a given user
551
-	 * @param string $uid id of the user
552
-	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
553
-	 */
554
-	private static function getAllVersions($uid) {
555
-		$view = new View('/' . $uid . '/');
556
-		$dirs = [self::VERSIONS_ROOT];
557
-		$versions = [];
558
-
559
-		while (!empty($dirs)) {
560
-			$dir = array_pop($dirs);
561
-			$files = $view->getDirectoryContent($dir);
562
-
563
-			foreach ($files as $file) {
564
-				$fileData = $file->getData();
565
-				$filePath = $dir . '/' . $fileData['name'];
566
-				if ($file['type'] === 'dir') {
567
-					$dirs[] = $filePath;
568
-				} else {
569
-					$versionsBegin = strrpos($filePath, '.v');
570
-					$relPathStart = strlen(self::VERSIONS_ROOT);
571
-					$version = substr($filePath, $versionsBegin + 2);
572
-					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
573
-					$key = $version . '#' . $relpath;
574
-					$versions[$key] = ['path' => $relpath, 'timestamp' => $version];
575
-				}
576
-			}
577
-		}
578
-
579
-		// newest version first
580
-		krsort($versions);
581
-
582
-		$result = [
583
-			'all' => [],
584
-			'by_file' => [],
585
-		];
586
-
587
-		foreach ($versions as $key => $value) {
588
-			$size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
589
-			$filename = $value['path'];
590
-
591
-			$result['all'][$key]['version'] = $value['timestamp'];
592
-			$result['all'][$key]['path'] = $filename;
593
-			$result['all'][$key]['size'] = $size;
594
-
595
-			$result['by_file'][$filename][$key]['version'] = $value['timestamp'];
596
-			$result['by_file'][$filename][$key]['path'] = $filename;
597
-			$result['by_file'][$filename][$key]['size'] = $size;
598
-		}
599
-
600
-		return $result;
601
-	}
602
-
603
-	/**
604
-	 * get list of files we want to expire
605
-	 * @param array $versions list of versions
606
-	 * @param integer $time
607
-	 * @param bool $quotaExceeded is versions storage limit reached
608
-	 * @return array containing the list of to deleted versions and the size of them
609
-	 */
610
-	protected static function getExpireList($time, $versions, $quotaExceeded = false) {
611
-		$expiration = self::getExpiration();
612
-
613
-		if ($expiration->shouldAutoExpire()) {
614
-			[$toDelete, $size] = self::getAutoExpireList($time, $versions);
615
-		} else {
616
-			$size = 0;
617
-			$toDelete = [];  // versions we want to delete
618
-		}
619
-
620
-		foreach ($versions as $key => $version) {
621
-			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
622
-				$size += $version['size'];
623
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
624
-			}
625
-		}
626
-
627
-		return [$toDelete, $size];
628
-	}
629
-
630
-	/**
631
-	 * get list of files we want to expire
632
-	 * @param array $versions list of versions
633
-	 * @param integer $time
634
-	 * @return array containing the list of to deleted versions and the size of them
635
-	 */
636
-	protected static function getAutoExpireList($time, $versions) {
637
-		$size = 0;
638
-		$toDelete = [];  // versions we want to delete
639
-
640
-		$interval = 1;
641
-		$step = Storage::$max_versions_per_interval[$interval]['step'];
642
-		if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
643
-			$nextInterval = -1;
644
-		} else {
645
-			$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
646
-		}
647
-
648
-		$firstVersion = reset($versions);
649
-
650
-		if ($firstVersion === false) {
651
-			return [$toDelete, $size];
652
-		}
653
-
654
-		$firstKey = key($versions);
655
-		$prevTimestamp = $firstVersion['version'];
656
-		$nextVersion = $firstVersion['version'] - $step;
657
-		unset($versions[$firstKey]);
658
-
659
-		foreach ($versions as $key => $version) {
660
-			$newInterval = true;
661
-			while ($newInterval) {
662
-				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
663
-					if ($version['version'] > $nextVersion) {
664
-						//distance between two version too small, mark to delete
665
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
666
-						$size += $version['size'];
667
-						\OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
668
-					} else {
669
-						$nextVersion = $version['version'] - $step;
670
-						$prevTimestamp = $version['version'];
671
-					}
672
-					$newInterval = false; // version checked so we can move to the next one
673
-				} else { // time to move on to the next interval
674
-					$interval++;
675
-					$step = Storage::$max_versions_per_interval[$interval]['step'];
676
-					$nextVersion = $prevTimestamp - $step;
677
-					if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
678
-						$nextInterval = -1;
679
-					} else {
680
-						$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
681
-					}
682
-					$newInterval = true; // we changed the interval -> check same version with new interval
683
-				}
684
-			}
685
-		}
686
-
687
-		return [$toDelete, $size];
688
-	}
689
-
690
-	/**
691
-	 * Schedule versions expiration for the given file
692
-	 *
693
-	 * @param string $uid owner of the file
694
-	 * @param string $fileName file/folder for which to schedule expiration
695
-	 */
696
-	public static function scheduleExpire($uid, $fileName) {
697
-		// let the admin disable auto expire
698
-		$expiration = self::getExpiration();
699
-		if ($expiration->isEnabled()) {
700
-			$command = new Expire($uid, $fileName);
701
-			\OC::$server->getCommandBus()->push($command);
702
-		}
703
-	}
704
-
705
-	/**
706
-	 * Expire versions which exceed the quota.
707
-	 *
708
-	 * This will setup the filesystem for the given user but will not
709
-	 * tear it down afterwards.
710
-	 *
711
-	 * @param string $filename path to file to expire
712
-	 * @param string $uid user for which to expire the version
713
-	 * @return bool|int|null
714
-	 */
715
-	public static function expire($filename, $uid) {
716
-		$expiration = self::getExpiration();
717
-
718
-		if ($expiration->isEnabled()) {
719
-			// get available disk space for user
720
-			$user = \OC::$server->getUserManager()->get($uid);
721
-			if (is_null($user)) {
722
-				\OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
723
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
724
-			}
725
-
726
-			\OC_Util::setupFS($uid);
727
-
728
-			try {
729
-				if (!Filesystem::file_exists($filename)) {
730
-					return false;
731
-				}
732
-			} catch (StorageNotAvailableException $e) {
733
-				// if we can't check that the file hasn't been deleted we can only assume that it hasn't
734
-				// note that this `StorageNotAvailableException` is about the file the versions originate from,
735
-				// not the storage that the versions are stored on
736
-			}
737
-
738
-			if (empty($filename)) {
739
-				// file maybe renamed or deleted
740
-				return false;
741
-			}
742
-			$versionsFileview = new View('/'.$uid.'/files_versions');
743
-
744
-			$softQuota = true;
745
-			$quota = $user->getQuota();
746
-			if ($quota === null || $quota === 'none') {
747
-				$quota = Filesystem::free_space('/');
748
-				$softQuota = false;
749
-			} else {
750
-				$quota = \OCP\Util::computerFileSize($quota);
751
-			}
752
-
753
-			// make sure that we have the current size of the version history
754
-			$versionsSize = self::getVersionsSize($uid);
755
-
756
-			// calculate available space for version history
757
-			// subtract size of files and current versions size from quota
758
-			if ($quota >= 0) {
759
-				if ($softQuota) {
760
-					$userFolder = \OC::$server->getUserFolder($uid);
761
-					if (is_null($userFolder)) {
762
-						$availableSpace = 0;
763
-					} else {
764
-						$free = $quota - $userFolder->getSize(false); // remaining free space for user
765
-						if ($free > 0) {
766
-							$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
767
-						} else {
768
-							$availableSpace = $free - $versionsSize;
769
-						}
770
-					}
771
-				} else {
772
-					$availableSpace = $quota;
773
-				}
774
-			} else {
775
-				$availableSpace = PHP_INT_MAX;
776
-			}
777
-
778
-			$allVersions = Storage::getVersions($uid, $filename);
779
-
780
-			$time = time();
781
-			[$toDelete, $sizeOfDeletedVersions] = self::getExpireList($time, $allVersions, $availableSpace <= 0);
782
-
783
-			$availableSpace = $availableSpace + $sizeOfDeletedVersions;
784
-			$versionsSize = $versionsSize - $sizeOfDeletedVersions;
785
-
786
-			// if still not enough free space we rearrange the versions from all files
787
-			if ($availableSpace <= 0) {
788
-				$result = self::getAllVersions($uid);
789
-				$allVersions = $result['all'];
790
-
791
-				foreach ($result['by_file'] as $versions) {
792
-					[$toDeleteNew, $size] = self::getExpireList($time, $versions, $availableSpace <= 0);
793
-					$toDelete = array_merge($toDelete, $toDeleteNew);
794
-					$sizeOfDeletedVersions += $size;
795
-				}
796
-				$availableSpace = $availableSpace + $sizeOfDeletedVersions;
797
-				$versionsSize = $versionsSize - $sizeOfDeletedVersions;
798
-			}
799
-
800
-			$logger = \OC::$server->getLogger();
801
-			foreach ($toDelete as $key => $path) {
802
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
803
-				self::deleteVersion($versionsFileview, $path);
804
-				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
805
-				unset($allVersions[$key]); // update array with the versions we keep
806
-				$logger->info('Expire: ' . $path, ['app' => 'files_versions']);
807
-			}
808
-
809
-			// Check if enough space is available after versions are rearranged.
810
-			// If not we delete the oldest versions until we meet the size limit for versions,
811
-			// but always keep the two latest versions
812
-			$numOfVersions = count($allVersions) - 2 ;
813
-			$i = 0;
814
-			// sort oldest first and make sure that we start at the first element
815
-			ksort($allVersions);
816
-			reset($allVersions);
817
-			while ($availableSpace < 0 && $i < $numOfVersions) {
818
-				$version = current($allVersions);
819
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
820
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
821
-				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
822
-				\OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
823
-				$versionsSize -= $version['size'];
824
-				$availableSpace += $version['size'];
825
-				next($allVersions);
826
-				$i++;
827
-			}
828
-
829
-			return $versionsSize; // finally return the new size of the version history
830
-		}
831
-
832
-		return false;
833
-	}
834
-
835
-	/**
836
-	 * Create recursively missing directories inside of files_versions
837
-	 * that match the given path to a file.
838
-	 *
839
-	 * @param string $filename $path to a file, relative to the user's
840
-	 * "files" folder
841
-	 * @param View $view view on data/user/
842
-	 */
843
-	public static function createMissingDirectories($filename, $view) {
844
-		$dirname = Filesystem::normalizePath(dirname($filename));
845
-		$dirParts = explode('/', $dirname);
846
-		$dir = "/files_versions";
847
-		foreach ($dirParts as $part) {
848
-			$dir = $dir . '/' . $part;
849
-			if (!$view->file_exists($dir)) {
850
-				$view->mkdir($dir);
851
-			}
852
-		}
853
-	}
854
-
855
-	/**
856
-	 * Static workaround
857
-	 * @return Expiration
858
-	 */
859
-	protected static function getExpiration() {
860
-		if (self::$application === null) {
861
-			self::$application = \OC::$server->query(Application::class);
862
-		}
863
-		return self::$application->getContainer()->query(Expiration::class);
864
-	}
62
+    public const DEFAULTENABLED = true;
63
+    public const DEFAULTMAXSIZE = 50; // unit: percentage; 50% of available disk space/quota
64
+    public const VERSIONS_ROOT = 'files_versions/';
65
+
66
+    public const DELETE_TRIGGER_MASTER_REMOVED = 0;
67
+    public const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
68
+    public const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
69
+
70
+    // files for which we can remove the versions after the delete operation was successful
71
+    private static $deletedFiles = [];
72
+
73
+    private static $sourcePathAndUser = [];
74
+
75
+    private static $max_versions_per_interval = [
76
+        //first 10sec, one version every 2sec
77
+        1 => ['intervalEndsAfter' => 10,      'step' => 2],
78
+        //next minute, one version every 10sec
79
+        2 => ['intervalEndsAfter' => 60,      'step' => 10],
80
+        //next hour, one version every minute
81
+        3 => ['intervalEndsAfter' => 3600,    'step' => 60],
82
+        //next 24h, one version every hour
83
+        4 => ['intervalEndsAfter' => 86400,   'step' => 3600],
84
+        //next 30days, one version per day
85
+        5 => ['intervalEndsAfter' => 2592000, 'step' => 86400],
86
+        //until the end one version per week
87
+        6 => ['intervalEndsAfter' => -1,      'step' => 604800],
88
+    ];
89
+
90
+    /** @var \OCA\Files_Versions\AppInfo\Application */
91
+    private static $application;
92
+
93
+    /**
94
+     * get the UID of the owner of the file and the path to the file relative to
95
+     * owners files folder
96
+     *
97
+     * @param string $filename
98
+     * @return array
99
+     * @throws \OC\User\NoUserException
100
+     */
101
+    public static function getUidAndFilename($filename) {
102
+        $uid = Filesystem::getOwner($filename);
103
+        $userManager = \OC::$server->getUserManager();
104
+        // if the user with the UID doesn't exists, e.g. because the UID points
105
+        // to a remote user with a federated cloud ID we use the current logged-in
106
+        // user. We need a valid local user to create the versions
107
+        if (!$userManager->userExists($uid)) {
108
+            $uid = User::getUser();
109
+        }
110
+        Filesystem::initMountPoints($uid);
111
+        if ($uid !== User::getUser()) {
112
+            $info = Filesystem::getFileInfo($filename);
113
+            $ownerView = new View('/'.$uid.'/files');
114
+            try {
115
+                $filename = $ownerView->getPath($info['fileid']);
116
+                // make sure that the file name doesn't end with a trailing slash
117
+                // can for example happen single files shared across servers
118
+                $filename = rtrim($filename, '/');
119
+            } catch (NotFoundException $e) {
120
+                $filename = null;
121
+            }
122
+        }
123
+        return [$uid, $filename];
124
+    }
125
+
126
+    /**
127
+     * Remember the owner and the owner path of the source file
128
+     *
129
+     * @param string $source source path
130
+     */
131
+    public static function setSourcePathAndUser($source) {
132
+        [$uid, $path] = self::getUidAndFilename($source);
133
+        self::$sourcePathAndUser[$source] = ['uid' => $uid, 'path' => $path];
134
+    }
135
+
136
+    /**
137
+     * Gets the owner and the owner path from the source path
138
+     *
139
+     * @param string $source source path
140
+     * @return array with user id and path
141
+     */
142
+    public static function getSourcePathAndUser($source) {
143
+        if (isset(self::$sourcePathAndUser[$source])) {
144
+            $uid = self::$sourcePathAndUser[$source]['uid'];
145
+            $path = self::$sourcePathAndUser[$source]['path'];
146
+            unset(self::$sourcePathAndUser[$source]);
147
+        } else {
148
+            $uid = $path = false;
149
+        }
150
+        return [$uid, $path];
151
+    }
152
+
153
+    /**
154
+     * get current size of all versions from a given user
155
+     *
156
+     * @param string $user user who owns the versions
157
+     * @return int versions size
158
+     */
159
+    private static function getVersionsSize($user) {
160
+        $view = new View('/' . $user);
161
+        $fileInfo = $view->getFileInfo('/files_versions');
162
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
163
+    }
164
+
165
+    /**
166
+     * store a new version of a file.
167
+     */
168
+    public static function store($filename) {
169
+
170
+        // if the file gets streamed we need to remove the .part extension
171
+        // to get the right target
172
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
173
+        if ($ext === 'part') {
174
+            $filename = substr($filename, 0, -5);
175
+        }
176
+
177
+        // we only handle existing files
178
+        if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
179
+            return false;
180
+        }
181
+
182
+        [$uid, $filename] = self::getUidAndFilename($filename);
183
+
184
+        $files_view = new View('/'.$uid .'/files');
185
+
186
+        $eventDispatcher = \OC::$server->getEventDispatcher();
187
+        $fileInfo = $files_view->getFileInfo($filename);
188
+        $id = $fileInfo->getId();
189
+        $nodes = \OC::$server->getRootFolder()->getUserFolder($uid)->getById($id);
190
+        foreach ($nodes as $node) {
191
+            $event = new CreateVersionEvent($node);
192
+            $eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
193
+            if ($event->shouldCreateVersion() === false) {
194
+                return false;
195
+            }
196
+        }
197
+
198
+        // no use making versions for empty files
199
+        if ($fileInfo->getSize() === 0) {
200
+            return false;
201
+        }
202
+
203
+        /** @var IVersionManager $versionManager */
204
+        $versionManager = \OC::$server->query(IVersionManager::class);
205
+        $userManager = \OC::$server->getUserManager();
206
+        $user = $userManager->get($uid);
207
+
208
+        $versionManager->createVersion($user, $fileInfo);
209
+    }
210
+
211
+
212
+    /**
213
+     * mark file as deleted so that we can remove the versions if the file is gone
214
+     * @param string $path
215
+     */
216
+    public static function markDeletedFile($path) {
217
+        [$uid, $filename] = self::getUidAndFilename($path);
218
+        self::$deletedFiles[$path] = [
219
+            'uid' => $uid,
220
+            'filename' => $filename];
221
+    }
222
+
223
+    /**
224
+     * delete the version from the storage and cache
225
+     *
226
+     * @param View $view
227
+     * @param string $path
228
+     */
229
+    protected static function deleteVersion($view, $path) {
230
+        $view->unlink($path);
231
+        /**
232
+         * @var \OC\Files\Storage\Storage $storage
233
+         * @var string $internalPath
234
+         */
235
+        [$storage, $internalPath] = $view->resolvePath($path);
236
+        $cache = $storage->getCache($internalPath);
237
+        $cache->remove($internalPath);
238
+    }
239
+
240
+    /**
241
+     * Delete versions of a file
242
+     */
243
+    public static function delete($path) {
244
+        $deletedFile = self::$deletedFiles[$path];
245
+        $uid = $deletedFile['uid'];
246
+        $filename = $deletedFile['filename'];
247
+
248
+        if (!Filesystem::file_exists($path)) {
249
+            $view = new View('/' . $uid . '/files_versions');
250
+
251
+            $versions = self::getVersions($uid, $filename);
252
+            if (!empty($versions)) {
253
+                foreach ($versions as $v) {
254
+                    \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
255
+                    self::deleteVersion($view, $filename . '.v' . $v['version']);
256
+                    \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
257
+                }
258
+            }
259
+        }
260
+        unset(self::$deletedFiles[$path]);
261
+    }
262
+
263
+    /**
264
+     * Rename or copy versions of a file of the given paths
265
+     *
266
+     * @param string $sourcePath source path of the file to move, relative to
267
+     * the currently logged in user's "files" folder
268
+     * @param string $targetPath target path of the file to move, relative to
269
+     * the currently logged in user's "files" folder
270
+     * @param string $operation can be 'copy' or 'rename'
271
+     */
272
+    public static function renameOrCopy($sourcePath, $targetPath, $operation) {
273
+        [$sourceOwner, $sourcePath] = self::getSourcePathAndUser($sourcePath);
274
+
275
+        // it was a upload of a existing file if no old path exists
276
+        // in this case the pre-hook already called the store method and we can
277
+        // stop here
278
+        if ($sourcePath === false) {
279
+            return true;
280
+        }
281
+
282
+        [$targetOwner, $targetPath] = self::getUidAndFilename($targetPath);
283
+
284
+        $sourcePath = ltrim($sourcePath, '/');
285
+        $targetPath = ltrim($targetPath, '/');
286
+
287
+        $rootView = new View('');
288
+
289
+        // did we move a directory ?
290
+        if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
291
+            // does the directory exists for versions too ?
292
+            if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
293
+                // create missing dirs if necessary
294
+                self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
295
+
296
+                // move the directory containing the versions
297
+                $rootView->$operation(
298
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath,
299
+                    '/' . $targetOwner . '/files_versions/' . $targetPath
300
+                );
301
+            }
302
+        } elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
303
+            // create missing dirs if necessary
304
+            self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
305
+
306
+            foreach ($versions as $v) {
307
+                // move each version one by one to the target directory
308
+                $rootView->$operation(
309
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
310
+                    '/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
311
+                );
312
+            }
313
+        }
314
+
315
+        // if we moved versions directly for a file, schedule expiration check for that file
316
+        if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
317
+            self::scheduleExpire($targetOwner, $targetPath);
318
+        }
319
+    }
320
+
321
+    /**
322
+     * Rollback to an old version of a file.
323
+     *
324
+     * @param string $file file name
325
+     * @param int $revision revision timestamp
326
+     * @return bool
327
+     */
328
+    public static function rollback(string $file, int $revision, IUser $user) {
329
+
330
+        // add expected leading slash
331
+        $filename = '/' . ltrim($file, '/');
332
+
333
+        // Fetch the userfolder to trigger view hooks
334
+        $userFolder = \OC::$server->getUserFolder($user->getUID());
335
+
336
+        $users_view = new View('/'.$user->getUID());
337
+        $files_view = new View('/'. $user->getUID().'/files');
338
+
339
+        $versionCreated = false;
340
+
341
+        $fileInfo = $files_view->getFileInfo($file);
342
+
343
+        // check if user has the permissions to revert a version
344
+        if (!$fileInfo->isUpdateable()) {
345
+            return false;
346
+        }
347
+
348
+        //first create a new version
349
+        $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
350
+        if (!$users_view->file_exists($version)) {
351
+            $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
352
+            $versionCreated = true;
353
+        }
354
+
355
+        $fileToRestore = 'files_versions' . $filename . '.v' . $revision;
356
+
357
+        // Restore encrypted version of the old file for the newly restored file
358
+        // This has to happen manually here since the file is manually copied below
359
+        $oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
360
+        $oldFileInfo = $users_view->getFileInfo($fileToRestore);
361
+        $cache = $fileInfo->getStorage()->getCache();
362
+        $cache->update(
363
+            $fileInfo->getId(), [
364
+                'encrypted' => $oldVersion,
365
+                'encryptedVersion' => $oldVersion,
366
+                'size' => $oldFileInfo->getSize()
367
+            ]
368
+        );
369
+
370
+        // rollback
371
+        if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
372
+            $files_view->touch($file, $revision);
373
+            Storage::scheduleExpire($user->getUID(), $file);
374
+
375
+            $node = $userFolder->get($file);
376
+
377
+            // TODO: move away from those legacy hooks!
378
+            \OC_Hook::emit('\OCP\Versions', 'rollback', [
379
+                'path' => $filename,
380
+                'revision' => $revision,
381
+                'node' => $node,
382
+            ]);
383
+            return true;
384
+        } elseif ($versionCreated) {
385
+            self::deleteVersion($users_view, $version);
386
+        }
387
+
388
+        return false;
389
+    }
390
+
391
+    /**
392
+     * Stream copy file contents from $path1 to $path2
393
+     *
394
+     * @param View $view view to use for copying
395
+     * @param string $path1 source file to copy
396
+     * @param string $path2 target file
397
+     *
398
+     * @return bool true for success, false otherwise
399
+     */
400
+    private static function copyFileContents($view, $path1, $path2) {
401
+        /** @var \OC\Files\Storage\Storage $storage1 */
402
+        [$storage1, $internalPath1] = $view->resolvePath($path1);
403
+        /** @var \OC\Files\Storage\Storage $storage2 */
404
+        [$storage2, $internalPath2] = $view->resolvePath($path2);
405
+
406
+        $view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
+        $view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
+
409
+        // TODO add a proper way of overwriting a file while maintaining file ids
410
+        if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
+            $source = $storage1->fopen($internalPath1, 'r');
412
+            $target = $storage2->fopen($internalPath2, 'w');
413
+            [, $result] = \OC_Helper::streamCopy($source, $target);
414
+            fclose($source);
415
+            fclose($target);
416
+
417
+            if ($result !== false) {
418
+                $storage1->unlink($internalPath1);
419
+            }
420
+        } else {
421
+            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
+        }
423
+
424
+        $view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
+        $view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
+
427
+        return ($result !== false);
428
+    }
429
+
430
+    /**
431
+     * get a list of all available versions of a file in descending chronological order
432
+     * @param string $uid user id from the owner of the file
433
+     * @param string $filename file to find versions of, relative to the user files dir
434
+     * @param string $userFullPath
435
+     * @return array versions newest version first
436
+     */
437
+    public static function getVersions($uid, $filename, $userFullPath = '') {
438
+        $versions = [];
439
+        if (empty($filename)) {
440
+            return $versions;
441
+        }
442
+        // fetch for old versions
443
+        $view = new View('/' . $uid . '/');
444
+
445
+        $pathinfo = pathinfo($filename);
446
+        $versionedFile = $pathinfo['basename'];
447
+
448
+        $dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
+
450
+        $dirContent = false;
451
+        if ($view->is_dir($dir)) {
452
+            $dirContent = $view->opendir($dir);
453
+        }
454
+
455
+        if ($dirContent === false) {
456
+            return $versions;
457
+        }
458
+
459
+        if (is_resource($dirContent)) {
460
+            while (($entryName = readdir($dirContent)) !== false) {
461
+                if (!Filesystem::isIgnoredDir($entryName)) {
462
+                    $pathparts = pathinfo($entryName);
463
+                    $filename = $pathparts['filename'];
464
+                    if ($filename === $versionedFile) {
465
+                        $pathparts = pathinfo($entryName);
466
+                        $timestamp = substr($pathparts['extension'], 1);
467
+                        $filename = $pathparts['filename'];
468
+                        $key = $timestamp . '#' . $filename;
469
+                        $versions[$key]['version'] = $timestamp;
470
+                        $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
+                        if (empty($userFullPath)) {
472
+                            $versions[$key]['preview'] = '';
473
+                        } else {
474
+                            $versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
+                        }
476
+                        $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
+                        $versions[$key]['name'] = $versionedFile;
478
+                        $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
+                        $versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
+                    }
481
+                }
482
+            }
483
+            closedir($dirContent);
484
+        }
485
+
486
+        // sort with newest version first
487
+        krsort($versions);
488
+
489
+        return $versions;
490
+    }
491
+
492
+    /**
493
+     * Expire versions that older than max version retention time
494
+     * @param string $uid
495
+     */
496
+    public static function expireOlderThanMaxForUser($uid) {
497
+        $expiration = self::getExpiration();
498
+        $threshold = $expiration->getMaxAgeAsTimestamp();
499
+        $versions = self::getAllVersions($uid);
500
+        if (!$threshold || empty($versions['all'])) {
501
+            return;
502
+        }
503
+
504
+        $toDelete = [];
505
+        foreach (array_reverse($versions['all']) as $key => $version) {
506
+            if ((int)$version['version'] < $threshold) {
507
+                $toDelete[$key] = $version;
508
+            } else {
509
+                //Versions are sorted by time - nothing mo to iterate.
510
+                break;
511
+            }
512
+        }
513
+
514
+        $view = new View('/' . $uid . '/files_versions');
515
+        if (!empty($toDelete)) {
516
+            foreach ($toDelete as $version) {
517
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
518
+                self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
+                \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
520
+            }
521
+        }
522
+    }
523
+
524
+    /**
525
+     * translate a timestamp into a string like "5 days ago"
526
+     * @param int $timestamp
527
+     * @return string for example "5 days ago"
528
+     */
529
+    private static function getHumanReadableTimestamp($timestamp) {
530
+        $diff = time() - $timestamp;
531
+
532
+        if ($diff < 60) { // first minute
533
+            return  $diff . " seconds ago";
534
+        } elseif ($diff < 3600) { //first hour
535
+            return round($diff / 60) . " minutes ago";
536
+        } elseif ($diff < 86400) { // first day
537
+            return round($diff / 3600) . " hours ago";
538
+        } elseif ($diff < 604800) { //first week
539
+            return round($diff / 86400) . " days ago";
540
+        } elseif ($diff < 2419200) { //first month
541
+            return round($diff / 604800) . " weeks ago";
542
+        } elseif ($diff < 29030400) { // first year
543
+            return round($diff / 2419200) . " months ago";
544
+        } else {
545
+            return round($diff / 29030400) . " years ago";
546
+        }
547
+    }
548
+
549
+    /**
550
+     * returns all stored file versions from a given user
551
+     * @param string $uid id of the user
552
+     * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
553
+     */
554
+    private static function getAllVersions($uid) {
555
+        $view = new View('/' . $uid . '/');
556
+        $dirs = [self::VERSIONS_ROOT];
557
+        $versions = [];
558
+
559
+        while (!empty($dirs)) {
560
+            $dir = array_pop($dirs);
561
+            $files = $view->getDirectoryContent($dir);
562
+
563
+            foreach ($files as $file) {
564
+                $fileData = $file->getData();
565
+                $filePath = $dir . '/' . $fileData['name'];
566
+                if ($file['type'] === 'dir') {
567
+                    $dirs[] = $filePath;
568
+                } else {
569
+                    $versionsBegin = strrpos($filePath, '.v');
570
+                    $relPathStart = strlen(self::VERSIONS_ROOT);
571
+                    $version = substr($filePath, $versionsBegin + 2);
572
+                    $relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
573
+                    $key = $version . '#' . $relpath;
574
+                    $versions[$key] = ['path' => $relpath, 'timestamp' => $version];
575
+                }
576
+            }
577
+        }
578
+
579
+        // newest version first
580
+        krsort($versions);
581
+
582
+        $result = [
583
+            'all' => [],
584
+            'by_file' => [],
585
+        ];
586
+
587
+        foreach ($versions as $key => $value) {
588
+            $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
589
+            $filename = $value['path'];
590
+
591
+            $result['all'][$key]['version'] = $value['timestamp'];
592
+            $result['all'][$key]['path'] = $filename;
593
+            $result['all'][$key]['size'] = $size;
594
+
595
+            $result['by_file'][$filename][$key]['version'] = $value['timestamp'];
596
+            $result['by_file'][$filename][$key]['path'] = $filename;
597
+            $result['by_file'][$filename][$key]['size'] = $size;
598
+        }
599
+
600
+        return $result;
601
+    }
602
+
603
+    /**
604
+     * get list of files we want to expire
605
+     * @param array $versions list of versions
606
+     * @param integer $time
607
+     * @param bool $quotaExceeded is versions storage limit reached
608
+     * @return array containing the list of to deleted versions and the size of them
609
+     */
610
+    protected static function getExpireList($time, $versions, $quotaExceeded = false) {
611
+        $expiration = self::getExpiration();
612
+
613
+        if ($expiration->shouldAutoExpire()) {
614
+            [$toDelete, $size] = self::getAutoExpireList($time, $versions);
615
+        } else {
616
+            $size = 0;
617
+            $toDelete = [];  // versions we want to delete
618
+        }
619
+
620
+        foreach ($versions as $key => $version) {
621
+            if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
622
+                $size += $version['size'];
623
+                $toDelete[$key] = $version['path'] . '.v' . $version['version'];
624
+            }
625
+        }
626
+
627
+        return [$toDelete, $size];
628
+    }
629
+
630
+    /**
631
+     * get list of files we want to expire
632
+     * @param array $versions list of versions
633
+     * @param integer $time
634
+     * @return array containing the list of to deleted versions and the size of them
635
+     */
636
+    protected static function getAutoExpireList($time, $versions) {
637
+        $size = 0;
638
+        $toDelete = [];  // versions we want to delete
639
+
640
+        $interval = 1;
641
+        $step = Storage::$max_versions_per_interval[$interval]['step'];
642
+        if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
643
+            $nextInterval = -1;
644
+        } else {
645
+            $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
646
+        }
647
+
648
+        $firstVersion = reset($versions);
649
+
650
+        if ($firstVersion === false) {
651
+            return [$toDelete, $size];
652
+        }
653
+
654
+        $firstKey = key($versions);
655
+        $prevTimestamp = $firstVersion['version'];
656
+        $nextVersion = $firstVersion['version'] - $step;
657
+        unset($versions[$firstKey]);
658
+
659
+        foreach ($versions as $key => $version) {
660
+            $newInterval = true;
661
+            while ($newInterval) {
662
+                if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
663
+                    if ($version['version'] > $nextVersion) {
664
+                        //distance between two version too small, mark to delete
665
+                        $toDelete[$key] = $version['path'] . '.v' . $version['version'];
666
+                        $size += $version['size'];
667
+                        \OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
668
+                    } else {
669
+                        $nextVersion = $version['version'] - $step;
670
+                        $prevTimestamp = $version['version'];
671
+                    }
672
+                    $newInterval = false; // version checked so we can move to the next one
673
+                } else { // time to move on to the next interval
674
+                    $interval++;
675
+                    $step = Storage::$max_versions_per_interval[$interval]['step'];
676
+                    $nextVersion = $prevTimestamp - $step;
677
+                    if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
678
+                        $nextInterval = -1;
679
+                    } else {
680
+                        $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
681
+                    }
682
+                    $newInterval = true; // we changed the interval -> check same version with new interval
683
+                }
684
+            }
685
+        }
686
+
687
+        return [$toDelete, $size];
688
+    }
689
+
690
+    /**
691
+     * Schedule versions expiration for the given file
692
+     *
693
+     * @param string $uid owner of the file
694
+     * @param string $fileName file/folder for which to schedule expiration
695
+     */
696
+    public static function scheduleExpire($uid, $fileName) {
697
+        // let the admin disable auto expire
698
+        $expiration = self::getExpiration();
699
+        if ($expiration->isEnabled()) {
700
+            $command = new Expire($uid, $fileName);
701
+            \OC::$server->getCommandBus()->push($command);
702
+        }
703
+    }
704
+
705
+    /**
706
+     * Expire versions which exceed the quota.
707
+     *
708
+     * This will setup the filesystem for the given user but will not
709
+     * tear it down afterwards.
710
+     *
711
+     * @param string $filename path to file to expire
712
+     * @param string $uid user for which to expire the version
713
+     * @return bool|int|null
714
+     */
715
+    public static function expire($filename, $uid) {
716
+        $expiration = self::getExpiration();
717
+
718
+        if ($expiration->isEnabled()) {
719
+            // get available disk space for user
720
+            $user = \OC::$server->getUserManager()->get($uid);
721
+            if (is_null($user)) {
722
+                \OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
723
+                throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
724
+            }
725
+
726
+            \OC_Util::setupFS($uid);
727
+
728
+            try {
729
+                if (!Filesystem::file_exists($filename)) {
730
+                    return false;
731
+                }
732
+            } catch (StorageNotAvailableException $e) {
733
+                // if we can't check that the file hasn't been deleted we can only assume that it hasn't
734
+                // note that this `StorageNotAvailableException` is about the file the versions originate from,
735
+                // not the storage that the versions are stored on
736
+            }
737
+
738
+            if (empty($filename)) {
739
+                // file maybe renamed or deleted
740
+                return false;
741
+            }
742
+            $versionsFileview = new View('/'.$uid.'/files_versions');
743
+
744
+            $softQuota = true;
745
+            $quota = $user->getQuota();
746
+            if ($quota === null || $quota === 'none') {
747
+                $quota = Filesystem::free_space('/');
748
+                $softQuota = false;
749
+            } else {
750
+                $quota = \OCP\Util::computerFileSize($quota);
751
+            }
752
+
753
+            // make sure that we have the current size of the version history
754
+            $versionsSize = self::getVersionsSize($uid);
755
+
756
+            // calculate available space for version history
757
+            // subtract size of files and current versions size from quota
758
+            if ($quota >= 0) {
759
+                if ($softQuota) {
760
+                    $userFolder = \OC::$server->getUserFolder($uid);
761
+                    if (is_null($userFolder)) {
762
+                        $availableSpace = 0;
763
+                    } else {
764
+                        $free = $quota - $userFolder->getSize(false); // remaining free space for user
765
+                        if ($free > 0) {
766
+                            $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
767
+                        } else {
768
+                            $availableSpace = $free - $versionsSize;
769
+                        }
770
+                    }
771
+                } else {
772
+                    $availableSpace = $quota;
773
+                }
774
+            } else {
775
+                $availableSpace = PHP_INT_MAX;
776
+            }
777
+
778
+            $allVersions = Storage::getVersions($uid, $filename);
779
+
780
+            $time = time();
781
+            [$toDelete, $sizeOfDeletedVersions] = self::getExpireList($time, $allVersions, $availableSpace <= 0);
782
+
783
+            $availableSpace = $availableSpace + $sizeOfDeletedVersions;
784
+            $versionsSize = $versionsSize - $sizeOfDeletedVersions;
785
+
786
+            // if still not enough free space we rearrange the versions from all files
787
+            if ($availableSpace <= 0) {
788
+                $result = self::getAllVersions($uid);
789
+                $allVersions = $result['all'];
790
+
791
+                foreach ($result['by_file'] as $versions) {
792
+                    [$toDeleteNew, $size] = self::getExpireList($time, $versions, $availableSpace <= 0);
793
+                    $toDelete = array_merge($toDelete, $toDeleteNew);
794
+                    $sizeOfDeletedVersions += $size;
795
+                }
796
+                $availableSpace = $availableSpace + $sizeOfDeletedVersions;
797
+                $versionsSize = $versionsSize - $sizeOfDeletedVersions;
798
+            }
799
+
800
+            $logger = \OC::$server->getLogger();
801
+            foreach ($toDelete as $key => $path) {
802
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
803
+                self::deleteVersion($versionsFileview, $path);
804
+                \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
805
+                unset($allVersions[$key]); // update array with the versions we keep
806
+                $logger->info('Expire: ' . $path, ['app' => 'files_versions']);
807
+            }
808
+
809
+            // Check if enough space is available after versions are rearranged.
810
+            // If not we delete the oldest versions until we meet the size limit for versions,
811
+            // but always keep the two latest versions
812
+            $numOfVersions = count($allVersions) - 2 ;
813
+            $i = 0;
814
+            // sort oldest first and make sure that we start at the first element
815
+            ksort($allVersions);
816
+            reset($allVersions);
817
+            while ($availableSpace < 0 && $i < $numOfVersions) {
818
+                $version = current($allVersions);
819
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
820
+                self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
821
+                \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
822
+                \OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
823
+                $versionsSize -= $version['size'];
824
+                $availableSpace += $version['size'];
825
+                next($allVersions);
826
+                $i++;
827
+            }
828
+
829
+            return $versionsSize; // finally return the new size of the version history
830
+        }
831
+
832
+        return false;
833
+    }
834
+
835
+    /**
836
+     * Create recursively missing directories inside of files_versions
837
+     * that match the given path to a file.
838
+     *
839
+     * @param string $filename $path to a file, relative to the user's
840
+     * "files" folder
841
+     * @param View $view view on data/user/
842
+     */
843
+    public static function createMissingDirectories($filename, $view) {
844
+        $dirname = Filesystem::normalizePath(dirname($filename));
845
+        $dirParts = explode('/', $dirname);
846
+        $dir = "/files_versions";
847
+        foreach ($dirParts as $part) {
848
+            $dir = $dir . '/' . $part;
849
+            if (!$view->file_exists($dir)) {
850
+                $view->mkdir($dir);
851
+            }
852
+        }
853
+    }
854
+
855
+    /**
856
+     * Static workaround
857
+     * @return Expiration
858
+     */
859
+    protected static function getExpiration() {
860
+        if (self::$application === null) {
861
+            self::$application = \OC::$server->query(Application::class);
862
+        }
863
+        return self::$application->getContainer()->query(Expiration::class);
864
+    }
865 865
 }
Please login to merge, or discard this patch.
Spacing   +56 added lines, -56 removed lines patch added patch discarded remove patch
@@ -74,17 +74,17 @@  discard block
 block discarded – undo
74 74
 
75 75
 	private static $max_versions_per_interval = [
76 76
 		//first 10sec, one version every 2sec
77
-		1 => ['intervalEndsAfter' => 10,      'step' => 2],
77
+		1 => ['intervalEndsAfter' => 10, 'step' => 2],
78 78
 		//next minute, one version every 10sec
79
-		2 => ['intervalEndsAfter' => 60,      'step' => 10],
79
+		2 => ['intervalEndsAfter' => 60, 'step' => 10],
80 80
 		//next hour, one version every minute
81
-		3 => ['intervalEndsAfter' => 3600,    'step' => 60],
81
+		3 => ['intervalEndsAfter' => 3600, 'step' => 60],
82 82
 		//next 24h, one version every hour
83
-		4 => ['intervalEndsAfter' => 86400,   'step' => 3600],
83
+		4 => ['intervalEndsAfter' => 86400, 'step' => 3600],
84 84
 		//next 30days, one version per day
85 85
 		5 => ['intervalEndsAfter' => 2592000, 'step' => 86400],
86 86
 		//until the end one version per week
87
-		6 => ['intervalEndsAfter' => -1,      'step' => 604800],
87
+		6 => ['intervalEndsAfter' => -1, 'step' => 604800],
88 88
 	];
89 89
 
90 90
 	/** @var \OCA\Files_Versions\AppInfo\Application */
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
 	 * @return int versions size
158 158
 	 */
159 159
 	private static function getVersionsSize($user) {
160
-		$view = new View('/' . $user);
160
+		$view = new View('/'.$user);
161 161
 		$fileInfo = $view->getFileInfo('/files_versions');
162 162
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
163 163
 	}
@@ -175,13 +175,13 @@  discard block
 block discarded – undo
175 175
 		}
176 176
 
177 177
 		// we only handle existing files
178
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
178
+		if (!Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
179 179
 			return false;
180 180
 		}
181 181
 
182 182
 		[$uid, $filename] = self::getUidAndFilename($filename);
183 183
 
184
-		$files_view = new View('/'.$uid .'/files');
184
+		$files_view = new View('/'.$uid.'/files');
185 185
 
186 186
 		$eventDispatcher = \OC::$server->getEventDispatcher();
187 187
 		$fileInfo = $files_view->getFileInfo($filename);
@@ -246,14 +246,14 @@  discard block
 block discarded – undo
246 246
 		$filename = $deletedFile['filename'];
247 247
 
248 248
 		if (!Filesystem::file_exists($path)) {
249
-			$view = new View('/' . $uid . '/files_versions');
249
+			$view = new View('/'.$uid.'/files_versions');
250 250
 
251 251
 			$versions = self::getVersions($uid, $filename);
252 252
 			if (!empty($versions)) {
253 253
 				foreach ($versions as $v) {
254
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
255
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
256
-					\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
254
+					\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $path.$v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
255
+					self::deleteVersion($view, $filename.'.v'.$v['version']);
256
+					\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path.$v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED]);
257 257
 				}
258 258
 			}
259 259
 		}
@@ -287,33 +287,33 @@  discard block
 block discarded – undo
287 287
 		$rootView = new View('');
288 288
 
289 289
 		// did we move a directory ?
290
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
290
+		if ($rootView->is_dir('/'.$targetOwner.'/files/'.$targetPath)) {
291 291
 			// does the directory exists for versions too ?
292
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
292
+			if ($rootView->is_dir('/'.$sourceOwner.'/files_versions/'.$sourcePath)) {
293 293
 				// create missing dirs if necessary
294
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
294
+				self::createMissingDirectories($targetPath, new View('/'.$targetOwner));
295 295
 
296 296
 				// move the directory containing the versions
297 297
 				$rootView->$operation(
298
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
299
-					'/' . $targetOwner . '/files_versions/' . $targetPath
298
+					'/'.$sourceOwner.'/files_versions/'.$sourcePath,
299
+					'/'.$targetOwner.'/files_versions/'.$targetPath
300 300
 				);
301 301
 			}
302
-		} elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
302
+		} elseif ($versions = Storage::getVersions($sourceOwner, '/'.$sourcePath)) {
303 303
 			// create missing dirs if necessary
304
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
304
+			self::createMissingDirectories($targetPath, new View('/'.$targetOwner));
305 305
 
306 306
 			foreach ($versions as $v) {
307 307
 				// move each version one by one to the target directory
308 308
 				$rootView->$operation(
309
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
310
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
309
+					'/'.$sourceOwner.'/files_versions/'.$sourcePath.'.v'.$v['version'],
310
+					'/'.$targetOwner.'/files_versions/'.$targetPath.'.v'.$v['version']
311 311
 				);
312 312
 			}
313 313
 		}
314 314
 
315 315
 		// if we moved versions directly for a file, schedule expiration check for that file
316
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
316
+		if (!$rootView->is_dir('/'.$targetOwner.'/files/'.$targetPath)) {
317 317
 			self::scheduleExpire($targetOwner, $targetPath);
318 318
 		}
319 319
 	}
@@ -328,13 +328,13 @@  discard block
 block discarded – undo
328 328
 	public static function rollback(string $file, int $revision, IUser $user) {
329 329
 
330 330
 		// add expected leading slash
331
-		$filename = '/' . ltrim($file, '/');
331
+		$filename = '/'.ltrim($file, '/');
332 332
 
333 333
 		// Fetch the userfolder to trigger view hooks
334 334
 		$userFolder = \OC::$server->getUserFolder($user->getUID());
335 335
 
336 336
 		$users_view = new View('/'.$user->getUID());
337
-		$files_view = new View('/'. $user->getUID().'/files');
337
+		$files_view = new View('/'.$user->getUID().'/files');
338 338
 
339 339
 		$versionCreated = false;
340 340
 
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
 			$versionCreated = true;
353 353
 		}
354 354
 
355
-		$fileToRestore = 'files_versions' . $filename . '.v' . $revision;
355
+		$fileToRestore = 'files_versions'.$filename.'.v'.$revision;
356 356
 
357 357
 		// Restore encrypted version of the old file for the newly restored file
358 358
 		// This has to happen manually here since the file is manually copied below
@@ -368,7 +368,7 @@  discard block
 block discarded – undo
368 368
 		);
369 369
 
370 370
 		// rollback
371
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
371
+		if (self::copyFileContents($users_view, $fileToRestore, 'files'.$filename)) {
372 372
 			$files_view->touch($file, $revision);
373 373
 			Storage::scheduleExpire($user->getUID(), $file);
374 374
 
@@ -440,12 +440,12 @@  discard block
 block discarded – undo
440 440
 			return $versions;
441 441
 		}
442 442
 		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
443
+		$view = new View('/'.$uid.'/');
444 444
 
445 445
 		$pathinfo = pathinfo($filename);
446 446
 		$versionedFile = $pathinfo['basename'];
447 447
 
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
448
+		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT.'/'.$pathinfo['dirname']);
449 449
 
450 450
 		$dirContent = false;
451 451
 		if ($view->is_dir($dir)) {
@@ -465,7 +465,7 @@  discard block
 block discarded – undo
465 465
 						$pathparts = pathinfo($entryName);
466 466
 						$timestamp = substr($pathparts['extension'], 1);
467 467
 						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
468
+						$key = $timestamp.'#'.$filename;
469 469
 						$versions[$key]['version'] = $timestamp;
470 470
 						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471 471
 						if (empty($userFullPath)) {
@@ -473,9 +473,9 @@  discard block
 block discarded – undo
473 473
 						} else {
474 474
 							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475 475
 						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
476
+						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'].'/'.$filename);
477 477
 						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
478
+						$versions[$key]['size'] = $view->filesize($dir.'/'.$entryName);
479 479
 						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480 480
 					}
481 481
 				}
@@ -503,7 +503,7 @@  discard block
 block discarded – undo
503 503
 
504 504
 		$toDelete = [];
505 505
 		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] < $threshold) {
506
+			if ((int) $version['version'] < $threshold) {
507 507
 				$toDelete[$key] = $version;
508 508
 			} else {
509 509
 				//Versions are sorted by time - nothing mo to iterate.
@@ -511,11 +511,11 @@  discard block
 block discarded – undo
511 511
 			}
512 512
 		}
513 513
 
514
-		$view = new View('/' . $uid . '/files_versions');
514
+		$view = new View('/'.$uid.'/files_versions');
515 515
 		if (!empty($toDelete)) {
516 516
 			foreach ($toDelete as $version) {
517 517
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
518
+				self::deleteVersion($view, $version['path'].'.v'.$version['version']);
519 519
 				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]);
520 520
 			}
521 521
 		}
@@ -530,19 +530,19 @@  discard block
 block discarded – undo
530 530
 		$diff = time() - $timestamp;
531 531
 
532 532
 		if ($diff < 60) { // first minute
533
-			return  $diff . " seconds ago";
533
+			return  $diff." seconds ago";
534 534
 		} elseif ($diff < 3600) { //first hour
535
-			return round($diff / 60) . " minutes ago";
535
+			return round($diff / 60)." minutes ago";
536 536
 		} elseif ($diff < 86400) { // first day
537
-			return round($diff / 3600) . " hours ago";
537
+			return round($diff / 3600)." hours ago";
538 538
 		} elseif ($diff < 604800) { //first week
539
-			return round($diff / 86400) . " days ago";
539
+			return round($diff / 86400)." days ago";
540 540
 		} elseif ($diff < 2419200) { //first month
541
-			return round($diff / 604800) . " weeks ago";
541
+			return round($diff / 604800)." weeks ago";
542 542
 		} elseif ($diff < 29030400) { // first year
543
-			return round($diff / 2419200) . " months ago";
543
+			return round($diff / 2419200)." months ago";
544 544
 		} else {
545
-			return round($diff / 29030400) . " years ago";
545
+			return round($diff / 29030400)." years ago";
546 546
 		}
547 547
 	}
548 548
 
@@ -552,7 +552,7 @@  discard block
 block discarded – undo
552 552
 	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
553 553
 	 */
554 554
 	private static function getAllVersions($uid) {
555
-		$view = new View('/' . $uid . '/');
555
+		$view = new View('/'.$uid.'/');
556 556
 		$dirs = [self::VERSIONS_ROOT];
557 557
 		$versions = [];
558 558
 
@@ -562,7 +562,7 @@  discard block
 block discarded – undo
562 562
 
563 563
 			foreach ($files as $file) {
564 564
 				$fileData = $file->getData();
565
-				$filePath = $dir . '/' . $fileData['name'];
565
+				$filePath = $dir.'/'.$fileData['name'];
566 566
 				if ($file['type'] === 'dir') {
567 567
 					$dirs[] = $filePath;
568 568
 				} else {
@@ -570,7 +570,7 @@  discard block
 block discarded – undo
570 570
 					$relPathStart = strlen(self::VERSIONS_ROOT);
571 571
 					$version = substr($filePath, $versionsBegin + 2);
572 572
 					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
573
-					$key = $version . '#' . $relpath;
573
+					$key = $version.'#'.$relpath;
574 574
 					$versions[$key] = ['path' => $relpath, 'timestamp' => $version];
575 575
 				}
576 576
 			}
@@ -614,13 +614,13 @@  discard block
 block discarded – undo
614 614
 			[$toDelete, $size] = self::getAutoExpireList($time, $versions);
615 615
 		} else {
616 616
 			$size = 0;
617
-			$toDelete = [];  // versions we want to delete
617
+			$toDelete = []; // versions we want to delete
618 618
 		}
619 619
 
620 620
 		foreach ($versions as $key => $version) {
621 621
 			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
622 622
 				$size += $version['size'];
623
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
+				$toDelete[$key] = $version['path'].'.v'.$version['version'];
624 624
 			}
625 625
 		}
626 626
 
@@ -635,7 +635,7 @@  discard block
 block discarded – undo
635 635
 	 */
636 636
 	protected static function getAutoExpireList($time, $versions) {
637 637
 		$size = 0;
638
-		$toDelete = [];  // versions we want to delete
638
+		$toDelete = []; // versions we want to delete
639 639
 
640 640
 		$interval = 1;
641 641
 		$step = Storage::$max_versions_per_interval[$interval]['step'];
@@ -662,9 +662,9 @@  discard block
 block discarded – undo
662 662
 				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
663 663
 					if ($version['version'] > $nextVersion) {
664 664
 						//distance between two version too small, mark to delete
665
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
665
+						$toDelete[$key] = $version['path'].'.v'.$version['version'];
666 666
 						$size += $version['size'];
667
-						\OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
667
+						\OC::$server->getLogger()->info('Mark to expire '.$version['path'].' next version should be '.$nextVersion." or smaller. (prevTimestamp: ".$prevTimestamp."; step: ".$step, ['app' => 'files_versions']);
668 668
 					} else {
669 669
 						$nextVersion = $version['version'] - $step;
670 670
 						$prevTimestamp = $version['version'];
@@ -719,8 +719,8 @@  discard block
 block discarded – undo
719 719
 			// get available disk space for user
720 720
 			$user = \OC::$server->getUserManager()->get($uid);
721 721
 			if (is_null($user)) {
722
-				\OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
723
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
722
+				\OC::$server->getLogger()->error('Backends provided no user object for '.$uid, ['app' => 'files_versions']);
723
+				throw new \OC\User\NoUserException('Backends provided no user object for '.$uid);
724 724
 			}
725 725
 
726 726
 			\OC_Util::setupFS($uid);
@@ -803,13 +803,13 @@  discard block
 block discarded – undo
803 803
 				self::deleteVersion($versionsFileview, $path);
804 804
 				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
805 805
 				unset($allVersions[$key]); // update array with the versions we keep
806
-				$logger->info('Expire: ' . $path, ['app' => 'files_versions']);
806
+				$logger->info('Expire: '.$path, ['app' => 'files_versions']);
807 807
 			}
808 808
 
809 809
 			// Check if enough space is available after versions are rearranged.
810 810
 			// If not we delete the oldest versions until we meet the size limit for versions,
811 811
 			// but always keep the two latest versions
812
-			$numOfVersions = count($allVersions) - 2 ;
812
+			$numOfVersions = count($allVersions) - 2;
813 813
 			$i = 0;
814 814
 			// sort oldest first and make sure that we start at the first element
815 815
 			ksort($allVersions);
@@ -817,9 +817,9 @@  discard block
 block discarded – undo
817 817
 			while ($availableSpace < 0 && $i < $numOfVersions) {
818 818
 				$version = current($allVersions);
819 819
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
820
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
820
+				self::deleteVersion($versionsFileview, $version['path'].'.v'.$version['version']);
821 821
 				\OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]);
822
-				\OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
822
+				\OC::$server->getLogger()->info('running out of space! Delete oldest version: '.$version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
823 823
 				$versionsSize -= $version['size'];
824 824
 				$availableSpace += $version['size'];
825 825
 				next($allVersions);
@@ -845,7 +845,7 @@  discard block
 block discarded – undo
845 845
 		$dirParts = explode('/', $dirname);
846 846
 		$dir = "/files_versions";
847 847
 		foreach ($dirParts as $part) {
848
-			$dir = $dir . '/' . $part;
848
+			$dir = $dir.'/'.$part;
849 849
 			if (!$view->file_exists($dir)) {
850 850
 				$view->mkdir($dir);
851 851
 			}
Please login to merge, or discard this patch.
apps/theming/lib/Util.php 1 patch
Indentation   +192 added lines, -192 removed lines patch added patch discarded remove patch
@@ -39,218 +39,218 @@
 block discarded – undo
39 39
 
40 40
 class Util {
41 41
 
42
-	/** @var IConfig */
43
-	private $config;
42
+    /** @var IConfig */
43
+    private $config;
44 44
 
45
-	/** @var IAppManager */
46
-	private $appManager;
45
+    /** @var IAppManager */
46
+    private $appManager;
47 47
 
48
-	/** @var IAppData */
49
-	private $appData;
48
+    /** @var IAppData */
49
+    private $appData;
50 50
 
51
-	/**
52
-	 * Util constructor.
53
-	 *
54
-	 * @param IConfig $config
55
-	 * @param IAppManager $appManager
56
-	 * @param IAppData $appData
57
-	 */
58
-	public function __construct(IConfig $config, IAppManager $appManager, IAppData $appData) {
59
-		$this->config = $config;
60
-		$this->appManager = $appManager;
61
-		$this->appData = $appData;
62
-	}
51
+    /**
52
+     * Util constructor.
53
+     *
54
+     * @param IConfig $config
55
+     * @param IAppManager $appManager
56
+     * @param IAppData $appData
57
+     */
58
+    public function __construct(IConfig $config, IAppManager $appManager, IAppData $appData) {
59
+        $this->config = $config;
60
+        $this->appManager = $appManager;
61
+        $this->appData = $appData;
62
+    }
63 63
 
64
-	/**
65
-	 * @param string $color rgb color value
66
-	 * @return bool
67
-	 */
68
-	public function invertTextColor($color) {
69
-		$l = $this->calculateLuma($color);
70
-		if ($l > 0.6) {
71
-			return true;
72
-		} else {
73
-			return false;
74
-		}
75
-	}
64
+    /**
65
+     * @param string $color rgb color value
66
+     * @return bool
67
+     */
68
+    public function invertTextColor($color) {
69
+        $l = $this->calculateLuma($color);
70
+        if ($l > 0.6) {
71
+            return true;
72
+        } else {
73
+            return false;
74
+        }
75
+    }
76 76
 
77
-	/**
78
-	 * get color for on-page elements:
79
-	 * theme color by default, grey if theme color is to bright
80
-	 * @param string $color
81
-	 * @param bool $brightBackground
82
-	 * @return string
83
-	 */
84
-	public function elementColor($color, bool $brightBackground = true) {
85
-		$luminance = $this->calculateLuminance($color);
77
+    /**
78
+     * get color for on-page elements:
79
+     * theme color by default, grey if theme color is to bright
80
+     * @param string $color
81
+     * @param bool $brightBackground
82
+     * @return string
83
+     */
84
+    public function elementColor($color, bool $brightBackground = true) {
85
+        $luminance = $this->calculateLuminance($color);
86 86
 
87
-		if ($brightBackground && $luminance > 0.8) {
88
-			// If the color is too bright in bright mode, we fall back to a darker gray
89
-			return '#aaaaaa';
90
-		}
87
+        if ($brightBackground && $luminance > 0.8) {
88
+            // If the color is too bright in bright mode, we fall back to a darker gray
89
+            return '#aaaaaa';
90
+        }
91 91
 
92
-		if (!$brightBackground && $luminance < 0.2) {
93
-			// If the color is too dark in dark mode, we fall back to a brighter gray
94
-			return '#555555';
95
-		}
92
+        if (!$brightBackground && $luminance < 0.2) {
93
+            // If the color is too dark in dark mode, we fall back to a brighter gray
94
+            return '#555555';
95
+        }
96 96
 
97
-		return $color;
98
-	}
97
+        return $color;
98
+    }
99 99
 
100
-	/**
101
-	 * @param string $color rgb color value
102
-	 * @return float
103
-	 */
104
-	public function calculateLuminance($color) {
105
-		[$red, $green, $blue] = $this->hexToRGB($color);
106
-		$compiler = new Compiler();
107
-		$hsl = $compiler->toHSL($red, $green, $blue);
108
-		return $hsl[3] / 100;
109
-	}
100
+    /**
101
+     * @param string $color rgb color value
102
+     * @return float
103
+     */
104
+    public function calculateLuminance($color) {
105
+        [$red, $green, $blue] = $this->hexToRGB($color);
106
+        $compiler = new Compiler();
107
+        $hsl = $compiler->toHSL($red, $green, $blue);
108
+        return $hsl[3] / 100;
109
+    }
110 110
 
111
-	/**
112
-	 * @param string $color rgb color value
113
-	 * @return float
114
-	 */
115
-	public function calculateLuma($color) {
116
-		[$red, $green, $blue] = $this->hexToRGB($color);
117
-		return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255;
118
-	}
111
+    /**
112
+     * @param string $color rgb color value
113
+     * @return float
114
+     */
115
+    public function calculateLuma($color) {
116
+        [$red, $green, $blue] = $this->hexToRGB($color);
117
+        return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255;
118
+    }
119 119
 
120
-	/**
121
-	 * @param string $color rgb color value
122
-	 * @return int[]
123
-	 */
124
-	public function hexToRGB($color) {
125
-		$hex = preg_replace("/[^0-9A-Fa-f]/", '', $color);
126
-		if (strlen($hex) === 3) {
127
-			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
128
-		}
129
-		if (strlen($hex) !== 6) {
130
-			return 0;
131
-		}
132
-		return [
133
-			hexdec(substr($hex, 0, 2)),
134
-			hexdec(substr($hex, 2, 2)),
135
-			hexdec(substr($hex, 4, 2))
136
-		];
137
-	}
120
+    /**
121
+     * @param string $color rgb color value
122
+     * @return int[]
123
+     */
124
+    public function hexToRGB($color) {
125
+        $hex = preg_replace("/[^0-9A-Fa-f]/", '', $color);
126
+        if (strlen($hex) === 3) {
127
+            $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
128
+        }
129
+        if (strlen($hex) !== 6) {
130
+            return 0;
131
+        }
132
+        return [
133
+            hexdec(substr($hex, 0, 2)),
134
+            hexdec(substr($hex, 2, 2)),
135
+            hexdec(substr($hex, 4, 2))
136
+        ];
137
+    }
138 138
 
139
-	/**
140
-	 * @param $color
141
-	 * @return string base64 encoded radio button svg
142
-	 */
143
-	public function generateRadioButton($color) {
144
-		$radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' .
145
-			'<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>';
146
-		return base64_encode($radioButtonIcon);
147
-	}
139
+    /**
140
+     * @param $color
141
+     * @return string base64 encoded radio button svg
142
+     */
143
+    public function generateRadioButton($color) {
144
+        $radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' .
145
+            '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>';
146
+        return base64_encode($radioButtonIcon);
147
+    }
148 148
 
149 149
 
150
-	/**
151
-	 * @param $app string app name
152
-	 * @return string|ISimpleFile path to app icon / file of logo
153
-	 */
154
-	public function getAppIcon($app) {
155
-		$app = str_replace(['\0', '/', '\\', '..'], '', $app);
156
-		try {
157
-			$appPath = $this->appManager->getAppPath($app);
158
-			$icon = $appPath . '/img/' . $app . '.svg';
159
-			if (file_exists($icon)) {
160
-				return $icon;
161
-			}
162
-			$icon = $appPath . '/img/app.svg';
163
-			if (file_exists($icon)) {
164
-				return $icon;
165
-			}
166
-		} catch (AppPathNotFoundException $e) {
167
-		}
150
+    /**
151
+     * @param $app string app name
152
+     * @return string|ISimpleFile path to app icon / file of logo
153
+     */
154
+    public function getAppIcon($app) {
155
+        $app = str_replace(['\0', '/', '\\', '..'], '', $app);
156
+        try {
157
+            $appPath = $this->appManager->getAppPath($app);
158
+            $icon = $appPath . '/img/' . $app . '.svg';
159
+            if (file_exists($icon)) {
160
+                return $icon;
161
+            }
162
+            $icon = $appPath . '/img/app.svg';
163
+            if (file_exists($icon)) {
164
+                return $icon;
165
+            }
166
+        } catch (AppPathNotFoundException $e) {
167
+        }
168 168
 
169
-		if ($this->config->getAppValue('theming', 'logoMime', '') !== '') {
170
-			$logoFile = null;
171
-			try {
172
-				$folder = $this->appData->getFolder('images');
173
-				if ($folder !== null) {
174
-					return $folder->getFile('logo');
175
-				}
176
-			} catch (NotFoundException $e) {
177
-			}
178
-		}
179
-		return \OC::$SERVERROOT . '/core/img/logo/logo.svg';
180
-	}
169
+        if ($this->config->getAppValue('theming', 'logoMime', '') !== '') {
170
+            $logoFile = null;
171
+            try {
172
+                $folder = $this->appData->getFolder('images');
173
+                if ($folder !== null) {
174
+                    return $folder->getFile('logo');
175
+                }
176
+            } catch (NotFoundException $e) {
177
+            }
178
+        }
179
+        return \OC::$SERVERROOT . '/core/img/logo/logo.svg';
180
+    }
181 181
 
182
-	/**
183
-	 * @param $app string app name
184
-	 * @param $image string relative path to image in app folder
185
-	 * @return string|false absolute path to image
186
-	 */
187
-	public function getAppImage($app, $image) {
188
-		$app = str_replace(['\0', '/', '\\', '..'], '', $app);
189
-		$image = str_replace(['\0', '\\', '..'], '', $image);
190
-		if ($app === "core") {
191
-			$icon = \OC::$SERVERROOT . '/core/img/' . $image;
192
-			if (file_exists($icon)) {
193
-				return $icon;
194
-			}
195
-		}
182
+    /**
183
+     * @param $app string app name
184
+     * @param $image string relative path to image in app folder
185
+     * @return string|false absolute path to image
186
+     */
187
+    public function getAppImage($app, $image) {
188
+        $app = str_replace(['\0', '/', '\\', '..'], '', $app);
189
+        $image = str_replace(['\0', '\\', '..'], '', $image);
190
+        if ($app === "core") {
191
+            $icon = \OC::$SERVERROOT . '/core/img/' . $image;
192
+            if (file_exists($icon)) {
193
+                return $icon;
194
+            }
195
+        }
196 196
 
197
-		try {
198
-			$appPath = $this->appManager->getAppPath($app);
199
-		} catch (AppPathNotFoundException $e) {
200
-			return false;
201
-		}
197
+        try {
198
+            $appPath = $this->appManager->getAppPath($app);
199
+        } catch (AppPathNotFoundException $e) {
200
+            return false;
201
+        }
202 202
 
203
-		$icon = $appPath . '/img/' . $image;
204
-		if (file_exists($icon)) {
205
-			return $icon;
206
-		}
207
-		$icon = $appPath . '/img/' . $image . '.svg';
208
-		if (file_exists($icon)) {
209
-			return $icon;
210
-		}
211
-		$icon = $appPath . '/img/' . $image . '.png';
212
-		if (file_exists($icon)) {
213
-			return $icon;
214
-		}
215
-		$icon = $appPath . '/img/' . $image . '.gif';
216
-		if (file_exists($icon)) {
217
-			return $icon;
218
-		}
219
-		$icon = $appPath . '/img/' . $image . '.jpg';
220
-		if (file_exists($icon)) {
221
-			return $icon;
222
-		}
203
+        $icon = $appPath . '/img/' . $image;
204
+        if (file_exists($icon)) {
205
+            return $icon;
206
+        }
207
+        $icon = $appPath . '/img/' . $image . '.svg';
208
+        if (file_exists($icon)) {
209
+            return $icon;
210
+        }
211
+        $icon = $appPath . '/img/' . $image . '.png';
212
+        if (file_exists($icon)) {
213
+            return $icon;
214
+        }
215
+        $icon = $appPath . '/img/' . $image . '.gif';
216
+        if (file_exists($icon)) {
217
+            return $icon;
218
+        }
219
+        $icon = $appPath . '/img/' . $image . '.jpg';
220
+        if (file_exists($icon)) {
221
+            return $icon;
222
+        }
223 223
 
224
-		return false;
225
-	}
224
+        return false;
225
+    }
226 226
 
227
-	/**
228
-	 * replace default color with a custom one
229
-	 *
230
-	 * @param $svg string content of a svg file
231
-	 * @param $color string color to match
232
-	 * @return string
233
-	 */
234
-	public function colorizeSvg($svg, $color) {
235
-		$svg = preg_replace('/#0082c9/i', $color, $svg);
236
-		return $svg;
237
-	}
227
+    /**
228
+     * replace default color with a custom one
229
+     *
230
+     * @param $svg string content of a svg file
231
+     * @param $color string color to match
232
+     * @return string
233
+     */
234
+    public function colorizeSvg($svg, $color) {
235
+        $svg = preg_replace('/#0082c9/i', $color, $svg);
236
+        return $svg;
237
+    }
238 238
 
239
-	/**
240
-	 * Check if a custom theme is set in the server configuration
241
-	 *
242
-	 * @return bool
243
-	 */
244
-	public function isAlreadyThemed() {
245
-		$theme = $this->config->getSystemValue('theme', '');
246
-		if ($theme !== '') {
247
-			return true;
248
-		}
249
-		return false;
250
-	}
239
+    /**
240
+     * Check if a custom theme is set in the server configuration
241
+     *
242
+     * @return bool
243
+     */
244
+    public function isAlreadyThemed() {
245
+        $theme = $this->config->getSystemValue('theme', '');
246
+        if ($theme !== '') {
247
+            return true;
248
+        }
249
+        return false;
250
+    }
251 251
 
252
-	public function isBackgroundThemed() {
253
-		$backgroundLogo = $this->config->getAppValue('theming', 'backgroundMime', '');
254
-		return $backgroundLogo !== '' && $backgroundLogo !== 'backgroundColor';
255
-	}
252
+    public function isBackgroundThemed() {
253
+        $backgroundLogo = $this->config->getAppValue('theming', 'backgroundMime', '');
254
+        return $backgroundLogo !== '' && $backgroundLogo !== 'backgroundColor';
255
+    }
256 256
 }
Please login to merge, or discard this patch.
apps/workflowengine/lib/Check/RequestTime.php 1 patch
Indentation   +105 added lines, -105 removed lines patch added patch discarded remove patch
@@ -26,109 +26,109 @@
 block discarded – undo
26 26
 use OCP\WorkflowEngine\ICheck;
27 27
 
28 28
 class RequestTime implements ICheck {
29
-	public const REGEX_TIME = '([0-1][0-9]|2[0-3]):([0-5][0-9])';
30
-	public const REGEX_TIMEZONE = '([a-zA-Z]+(?:\\/[a-zA-Z\-\_]+)+)';
31
-
32
-	/** @var bool[] */
33
-	protected $cachedResults;
34
-
35
-	/** @var IL10N */
36
-	protected $l;
37
-
38
-	/** @var ITimeFactory */
39
-	protected $timeFactory;
40
-
41
-	/**
42
-	 * @param ITimeFactory $timeFactory
43
-	 */
44
-	public function __construct(IL10N $l, ITimeFactory $timeFactory) {
45
-		$this->l = $l;
46
-		$this->timeFactory = $timeFactory;
47
-	}
48
-
49
-	/**
50
-	 * @param string $operator
51
-	 * @param string $value
52
-	 * @return bool
53
-	 */
54
-	public function executeCheck($operator, $value) {
55
-		$valueHash = md5($value);
56
-
57
-		if (isset($this->cachedResults[$valueHash])) {
58
-			return $this->cachedResults[$valueHash];
59
-		}
60
-
61
-		$timestamp = $this->timeFactory->getTime();
62
-
63
-		$values = json_decode($value, true);
64
-		$timestamp1 = $this->getTimestamp($timestamp, $values[0]);
65
-		$timestamp2 = $this->getTimestamp($timestamp, $values[1]);
66
-
67
-		if ($timestamp1 < $timestamp2) {
68
-			$in = $timestamp1 <= $timestamp && $timestamp <= $timestamp2;
69
-		} else {
70
-			$in = $timestamp1 <= $timestamp || $timestamp <= $timestamp2;
71
-		}
72
-
73
-		return ($operator === 'in') ? $in : !$in;
74
-	}
75
-
76
-	/**
77
-	 * @param int $currentTimestamp
78
-	 * @param string $value Format: "H:i e"
79
-	 * @return int
80
-	 */
81
-	protected function getTimestamp($currentTimestamp, $value) {
82
-		[$time1, $timezone1] = explode(' ', $value);
83
-		[$hour1, $minute1] = explode(':', $time1);
84
-		$date1 = new \DateTime('now', new \DateTimeZone($timezone1));
85
-		$date1->setTimestamp($currentTimestamp);
86
-		$date1->setTime($hour1, $minute1);
87
-
88
-		return $date1->getTimestamp();
89
-	}
90
-
91
-	/**
92
-	 * @param string $operator
93
-	 * @param string $value
94
-	 * @throws \UnexpectedValueException
95
-	 */
96
-	public function validateCheck($operator, $value) {
97
-		if (!in_array($operator, ['in', '!in'])) {
98
-			throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
99
-		}
100
-
101
-		$regexValue = '\"' . self::REGEX_TIME . ' ' . self::REGEX_TIMEZONE . '\"';
102
-		$result = preg_match('/^\[' . $regexValue . ',' . $regexValue . '\]$/', $value, $matches);
103
-		if (!$result) {
104
-			throw new \UnexpectedValueException($this->l->t('The given time span is invalid'), 2);
105
-		}
106
-
107
-		$values = json_decode($value, true);
108
-		$time1 = \DateTime::createFromFormat('H:i e', $values[0]);
109
-		if ($time1 === false) {
110
-			throw new \UnexpectedValueException($this->l->t('The given start time is invalid'), 3);
111
-		}
112
-
113
-		$time2 = \DateTime::createFromFormat('H:i e', $values[1]);
114
-		if ($time2 === false) {
115
-			throw new \UnexpectedValueException($this->l->t('The given end time is invalid'), 4);
116
-		}
117
-	}
118
-
119
-	public function isAvailableForScope(int $scope): bool {
120
-		return true;
121
-	}
122
-
123
-	/**
124
-	 * returns a list of Entities the checker supports. The values must match
125
-	 * the class name of the entity.
126
-	 *
127
-	 * An empty result means the check is universally available.
128
-	 *
129
-	 * @since 18.0.0
130
-	 */
131
-	public function supportedEntities(): array {
132
-		return [];
133
-	}
29
+    public const REGEX_TIME = '([0-1][0-9]|2[0-3]):([0-5][0-9])';
30
+    public const REGEX_TIMEZONE = '([a-zA-Z]+(?:\\/[a-zA-Z\-\_]+)+)';
31
+
32
+    /** @var bool[] */
33
+    protected $cachedResults;
34
+
35
+    /** @var IL10N */
36
+    protected $l;
37
+
38
+    /** @var ITimeFactory */
39
+    protected $timeFactory;
40
+
41
+    /**
42
+     * @param ITimeFactory $timeFactory
43
+     */
44
+    public function __construct(IL10N $l, ITimeFactory $timeFactory) {
45
+        $this->l = $l;
46
+        $this->timeFactory = $timeFactory;
47
+    }
48
+
49
+    /**
50
+     * @param string $operator
51
+     * @param string $value
52
+     * @return bool
53
+     */
54
+    public function executeCheck($operator, $value) {
55
+        $valueHash = md5($value);
56
+
57
+        if (isset($this->cachedResults[$valueHash])) {
58
+            return $this->cachedResults[$valueHash];
59
+        }
60
+
61
+        $timestamp = $this->timeFactory->getTime();
62
+
63
+        $values = json_decode($value, true);
64
+        $timestamp1 = $this->getTimestamp($timestamp, $values[0]);
65
+        $timestamp2 = $this->getTimestamp($timestamp, $values[1]);
66
+
67
+        if ($timestamp1 < $timestamp2) {
68
+            $in = $timestamp1 <= $timestamp && $timestamp <= $timestamp2;
69
+        } else {
70
+            $in = $timestamp1 <= $timestamp || $timestamp <= $timestamp2;
71
+        }
72
+
73
+        return ($operator === 'in') ? $in : !$in;
74
+    }
75
+
76
+    /**
77
+     * @param int $currentTimestamp
78
+     * @param string $value Format: "H:i e"
79
+     * @return int
80
+     */
81
+    protected function getTimestamp($currentTimestamp, $value) {
82
+        [$time1, $timezone1] = explode(' ', $value);
83
+        [$hour1, $minute1] = explode(':', $time1);
84
+        $date1 = new \DateTime('now', new \DateTimeZone($timezone1));
85
+        $date1->setTimestamp($currentTimestamp);
86
+        $date1->setTime($hour1, $minute1);
87
+
88
+        return $date1->getTimestamp();
89
+    }
90
+
91
+    /**
92
+     * @param string $operator
93
+     * @param string $value
94
+     * @throws \UnexpectedValueException
95
+     */
96
+    public function validateCheck($operator, $value) {
97
+        if (!in_array($operator, ['in', '!in'])) {
98
+            throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
99
+        }
100
+
101
+        $regexValue = '\"' . self::REGEX_TIME . ' ' . self::REGEX_TIMEZONE . '\"';
102
+        $result = preg_match('/^\[' . $regexValue . ',' . $regexValue . '\]$/', $value, $matches);
103
+        if (!$result) {
104
+            throw new \UnexpectedValueException($this->l->t('The given time span is invalid'), 2);
105
+        }
106
+
107
+        $values = json_decode($value, true);
108
+        $time1 = \DateTime::createFromFormat('H:i e', $values[0]);
109
+        if ($time1 === false) {
110
+            throw new \UnexpectedValueException($this->l->t('The given start time is invalid'), 3);
111
+        }
112
+
113
+        $time2 = \DateTime::createFromFormat('H:i e', $values[1]);
114
+        if ($time2 === false) {
115
+            throw new \UnexpectedValueException($this->l->t('The given end time is invalid'), 4);
116
+        }
117
+    }
118
+
119
+    public function isAvailableForScope(int $scope): bool {
120
+        return true;
121
+    }
122
+
123
+    /**
124
+     * returns a list of Entities the checker supports. The values must match
125
+     * the class name of the entity.
126
+     *
127
+     * An empty result means the check is universally available.
128
+     *
129
+     * @since 18.0.0
130
+     */
131
+    public function supportedEntities(): array {
132
+        return [];
133
+    }
134 134
 }
Please login to merge, or discard this patch.
apps/workflowengine/lib/Service/RuleMatcher.php 1 patch
Indentation   +196 added lines, -196 removed lines patch added patch discarded remove patch
@@ -44,200 +44,200 @@
 block discarded – undo
44 44
 
45 45
 class RuleMatcher implements IRuleMatcher {
46 46
 
47
-	/** @var IUserSession */
48
-	protected $session;
49
-	/** @var IManager */
50
-	protected $manager;
51
-	/** @var array */
52
-	protected $contexts;
53
-	/** @var IServerContainer */
54
-	protected $container;
55
-	/** @var array */
56
-	protected $fileInfo = [];
57
-	/** @var IL10N */
58
-	protected $l;
59
-	/** @var IOperation */
60
-	protected $operation;
61
-	/** @var IEntity */
62
-	protected $entity;
63
-	/** @var Logger */
64
-	protected $logger;
65
-	/** @var string */
66
-	protected $eventName;
67
-
68
-	public function __construct(
69
-		IUserSession $session,
70
-		IServerContainer $container,
71
-		IL10N $l,
72
-		Manager $manager,
73
-		Logger $logger
74
-	) {
75
-		$this->session = $session;
76
-		$this->manager = $manager;
77
-		$this->container = $container;
78
-		$this->l = $l;
79
-		$this->logger = $logger;
80
-	}
81
-
82
-	public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
83
-		$this->fileInfo['storage'] = $storage;
84
-		$this->fileInfo['path'] = $path;
85
-		$this->fileInfo['isDir'] = $isDir;
86
-	}
87
-
88
-	public function setEntitySubject(IEntity $entity, $subject): void {
89
-		$this->contexts[get_class($entity)] = [$entity, $subject];
90
-	}
91
-
92
-	public function setOperation(IOperation $operation): void {
93
-		if ($this->operation !== null) {
94
-			throw new RuntimeException('This method must not be called more than once');
95
-		}
96
-		$this->operation = $operation;
97
-	}
98
-
99
-	public function setEntity(IEntity $entity): void {
100
-		if ($this->entity !== null) {
101
-			throw new RuntimeException('This method must not be called more than once');
102
-		}
103
-		$this->entity = $entity;
104
-	}
105
-
106
-	public function setEventName(string $eventName): void {
107
-		if ($this->eventName !== null) {
108
-			throw new RuntimeException('This method must not be called more than once');
109
-		}
110
-		$this->eventName = $eventName;
111
-	}
112
-
113
-	public function getEntity(): IEntity {
114
-		if ($this->entity === null) {
115
-			throw new \LogicException('Entity was not set yet');
116
-		}
117
-		return $this->entity;
118
-	}
119
-
120
-	public function getFlows(bool $returnFirstMatchingOperationOnly = true): array {
121
-		if (!$this->operation) {
122
-			throw new RuntimeException('Operation is not set');
123
-		}
124
-		return $this->getMatchingOperations(get_class($this->operation), $returnFirstMatchingOperationOnly);
125
-	}
126
-
127
-	public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array {
128
-		$scopes[] = new ScopeContext(IManager::SCOPE_ADMIN);
129
-		$user = $this->session->getUser();
130
-		if ($user !== null && $this->manager->isUserScopeEnabled()) {
131
-			$scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
132
-		}
133
-
134
-		$ctx = new LogContext();
135
-		$ctx
136
-			->setScopes($scopes)
137
-			->setEntity($this->entity)
138
-			->setOperation($this->operation);
139
-		$this->logger->logFlowRequests($ctx);
140
-
141
-		$operations = [];
142
-		foreach ($scopes as $scope) {
143
-			$operations = array_merge($operations, $this->manager->getOperations($class, $scope));
144
-		}
145
-
146
-		if ($this->entity instanceof IEntity) {
147
-			/** @var ScopeContext[] $additionalScopes */
148
-			$additionalScopes = $this->manager->getAllConfiguredScopesForOperation($class);
149
-			foreach ($additionalScopes as $hash => $scopeCandidate) {
150
-				if ($scopeCandidate->getScope() !== IManager::SCOPE_USER || in_array($scopeCandidate, $scopes)) {
151
-					continue;
152
-				}
153
-				if ($this->entity->isLegitimatedForUserId($scopeCandidate->getScopeId())) {
154
-					$ctx = new LogContext();
155
-					$ctx
156
-						->setScopes([$scopeCandidate])
157
-						->setEntity($this->entity)
158
-						->setOperation($this->operation);
159
-					$this->logger->logScopeExpansion($ctx);
160
-					$operations = array_merge($operations, $this->manager->getOperations($class, $scopeCandidate));
161
-				}
162
-			}
163
-		}
164
-
165
-		$matches = [];
166
-		foreach ($operations as $operation) {
167
-			$configuredEvents = json_decode($operation['events'], true);
168
-			if ($this->eventName !== null && !in_array($this->eventName, $configuredEvents)) {
169
-				continue;
170
-			}
171
-
172
-			$checkIds = json_decode($operation['checks'], true);
173
-			$checks = $this->manager->getChecks($checkIds);
174
-
175
-			foreach ($checks as $check) {
176
-				if (!$this->check($check)) {
177
-					// Check did not match, continue with the next operation
178
-					continue 2;
179
-				}
180
-			}
181
-
182
-			$ctx = new LogContext();
183
-			$ctx
184
-				->setEntity($this->entity)
185
-				->setOperation($this->operation)
186
-				->setConfiguration($operation);
187
-			$this->logger->logPassedCheck($ctx);
188
-
189
-			if ($returnFirstMatchingOperationOnly) {
190
-				$ctx = new LogContext();
191
-				$ctx
192
-					->setEntity($this->entity)
193
-					->setOperation($this->operation)
194
-					->setConfiguration($operation);
195
-				$this->logger->logRunSingle($ctx);
196
-				return $operation;
197
-			}
198
-			$matches[] = $operation;
199
-		}
200
-
201
-		$ctx = new LogContext();
202
-		$ctx
203
-			->setEntity($this->entity)
204
-			->setOperation($this->operation);
205
-		if (!empty($matches)) {
206
-			$ctx->setConfiguration($matches);
207
-			$this->logger->logRunAll($ctx);
208
-		} else {
209
-			$this->logger->logRunNone($ctx);
210
-		}
211
-
212
-		return $matches;
213
-	}
214
-
215
-	/**
216
-	 * @param array $check
217
-	 * @return bool
218
-	 */
219
-	public function check(array $check) {
220
-		try {
221
-			$checkInstance = $this->container->query($check['class']);
222
-		} catch (QueryException $e) {
223
-			// Check does not exist, assume it matches.
224
-			return true;
225
-		}
226
-
227
-		if ($checkInstance instanceof IFileCheck) {
228
-			if (empty($this->fileInfo)) {
229
-				throw new RuntimeException('Must set file info before running the check');
230
-			}
231
-			$checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path'], $this->fileInfo['isDir']);
232
-		} elseif ($checkInstance instanceof IEntityCheck) {
233
-			foreach ($this->contexts as $entityInfo) {
234
-				[$entity, $subject] = $entityInfo;
235
-				$checkInstance->setEntitySubject($entity, $subject);
236
-			}
237
-		} elseif (!$checkInstance instanceof ICheck) {
238
-			// Check is invalid
239
-			throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
240
-		}
241
-		return $checkInstance->executeCheck($check['operator'], $check['value']);
242
-	}
47
+    /** @var IUserSession */
48
+    protected $session;
49
+    /** @var IManager */
50
+    protected $manager;
51
+    /** @var array */
52
+    protected $contexts;
53
+    /** @var IServerContainer */
54
+    protected $container;
55
+    /** @var array */
56
+    protected $fileInfo = [];
57
+    /** @var IL10N */
58
+    protected $l;
59
+    /** @var IOperation */
60
+    protected $operation;
61
+    /** @var IEntity */
62
+    protected $entity;
63
+    /** @var Logger */
64
+    protected $logger;
65
+    /** @var string */
66
+    protected $eventName;
67
+
68
+    public function __construct(
69
+        IUserSession $session,
70
+        IServerContainer $container,
71
+        IL10N $l,
72
+        Manager $manager,
73
+        Logger $logger
74
+    ) {
75
+        $this->session = $session;
76
+        $this->manager = $manager;
77
+        $this->container = $container;
78
+        $this->l = $l;
79
+        $this->logger = $logger;
80
+    }
81
+
82
+    public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
83
+        $this->fileInfo['storage'] = $storage;
84
+        $this->fileInfo['path'] = $path;
85
+        $this->fileInfo['isDir'] = $isDir;
86
+    }
87
+
88
+    public function setEntitySubject(IEntity $entity, $subject): void {
89
+        $this->contexts[get_class($entity)] = [$entity, $subject];
90
+    }
91
+
92
+    public function setOperation(IOperation $operation): void {
93
+        if ($this->operation !== null) {
94
+            throw new RuntimeException('This method must not be called more than once');
95
+        }
96
+        $this->operation = $operation;
97
+    }
98
+
99
+    public function setEntity(IEntity $entity): void {
100
+        if ($this->entity !== null) {
101
+            throw new RuntimeException('This method must not be called more than once');
102
+        }
103
+        $this->entity = $entity;
104
+    }
105
+
106
+    public function setEventName(string $eventName): void {
107
+        if ($this->eventName !== null) {
108
+            throw new RuntimeException('This method must not be called more than once');
109
+        }
110
+        $this->eventName = $eventName;
111
+    }
112
+
113
+    public function getEntity(): IEntity {
114
+        if ($this->entity === null) {
115
+            throw new \LogicException('Entity was not set yet');
116
+        }
117
+        return $this->entity;
118
+    }
119
+
120
+    public function getFlows(bool $returnFirstMatchingOperationOnly = true): array {
121
+        if (!$this->operation) {
122
+            throw new RuntimeException('Operation is not set');
123
+        }
124
+        return $this->getMatchingOperations(get_class($this->operation), $returnFirstMatchingOperationOnly);
125
+    }
126
+
127
+    public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array {
128
+        $scopes[] = new ScopeContext(IManager::SCOPE_ADMIN);
129
+        $user = $this->session->getUser();
130
+        if ($user !== null && $this->manager->isUserScopeEnabled()) {
131
+            $scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
132
+        }
133
+
134
+        $ctx = new LogContext();
135
+        $ctx
136
+            ->setScopes($scopes)
137
+            ->setEntity($this->entity)
138
+            ->setOperation($this->operation);
139
+        $this->logger->logFlowRequests($ctx);
140
+
141
+        $operations = [];
142
+        foreach ($scopes as $scope) {
143
+            $operations = array_merge($operations, $this->manager->getOperations($class, $scope));
144
+        }
145
+
146
+        if ($this->entity instanceof IEntity) {
147
+            /** @var ScopeContext[] $additionalScopes */
148
+            $additionalScopes = $this->manager->getAllConfiguredScopesForOperation($class);
149
+            foreach ($additionalScopes as $hash => $scopeCandidate) {
150
+                if ($scopeCandidate->getScope() !== IManager::SCOPE_USER || in_array($scopeCandidate, $scopes)) {
151
+                    continue;
152
+                }
153
+                if ($this->entity->isLegitimatedForUserId($scopeCandidate->getScopeId())) {
154
+                    $ctx = new LogContext();
155
+                    $ctx
156
+                        ->setScopes([$scopeCandidate])
157
+                        ->setEntity($this->entity)
158
+                        ->setOperation($this->operation);
159
+                    $this->logger->logScopeExpansion($ctx);
160
+                    $operations = array_merge($operations, $this->manager->getOperations($class, $scopeCandidate));
161
+                }
162
+            }
163
+        }
164
+
165
+        $matches = [];
166
+        foreach ($operations as $operation) {
167
+            $configuredEvents = json_decode($operation['events'], true);
168
+            if ($this->eventName !== null && !in_array($this->eventName, $configuredEvents)) {
169
+                continue;
170
+            }
171
+
172
+            $checkIds = json_decode($operation['checks'], true);
173
+            $checks = $this->manager->getChecks($checkIds);
174
+
175
+            foreach ($checks as $check) {
176
+                if (!$this->check($check)) {
177
+                    // Check did not match, continue with the next operation
178
+                    continue 2;
179
+                }
180
+            }
181
+
182
+            $ctx = new LogContext();
183
+            $ctx
184
+                ->setEntity($this->entity)
185
+                ->setOperation($this->operation)
186
+                ->setConfiguration($operation);
187
+            $this->logger->logPassedCheck($ctx);
188
+
189
+            if ($returnFirstMatchingOperationOnly) {
190
+                $ctx = new LogContext();
191
+                $ctx
192
+                    ->setEntity($this->entity)
193
+                    ->setOperation($this->operation)
194
+                    ->setConfiguration($operation);
195
+                $this->logger->logRunSingle($ctx);
196
+                return $operation;
197
+            }
198
+            $matches[] = $operation;
199
+        }
200
+
201
+        $ctx = new LogContext();
202
+        $ctx
203
+            ->setEntity($this->entity)
204
+            ->setOperation($this->operation);
205
+        if (!empty($matches)) {
206
+            $ctx->setConfiguration($matches);
207
+            $this->logger->logRunAll($ctx);
208
+        } else {
209
+            $this->logger->logRunNone($ctx);
210
+        }
211
+
212
+        return $matches;
213
+    }
214
+
215
+    /**
216
+     * @param array $check
217
+     * @return bool
218
+     */
219
+    public function check(array $check) {
220
+        try {
221
+            $checkInstance = $this->container->query($check['class']);
222
+        } catch (QueryException $e) {
223
+            // Check does not exist, assume it matches.
224
+            return true;
225
+        }
226
+
227
+        if ($checkInstance instanceof IFileCheck) {
228
+            if (empty($this->fileInfo)) {
229
+                throw new RuntimeException('Must set file info before running the check');
230
+            }
231
+            $checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path'], $this->fileInfo['isDir']);
232
+        } elseif ($checkInstance instanceof IEntityCheck) {
233
+            foreach ($this->contexts as $entityInfo) {
234
+                [$entity, $subject] = $entityInfo;
235
+                $checkInstance->setEntitySubject($entity, $subject);
236
+            }
237
+        } elseif (!$checkInstance instanceof ICheck) {
238
+            // Check is invalid
239
+            throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
240
+        }
241
+        return $checkInstance->executeCheck($check['operator'], $check['value']);
242
+    }
243 243
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Sabre/RootCollection.php 1 patch
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -34,40 +34,40 @@
 block discarded – undo
34 34
 use Sabre\DAVACL\PrincipalBackend;
35 35
 
36 36
 class RootCollection extends AbstractPrincipalCollection {
37
-	/** @var ITrashManager */
38
-	private $trashManager;
37
+    /** @var ITrashManager */
38
+    private $trashManager;
39 39
 
40
-	public function __construct(
41
-		ITrashManager $trashManager,
42
-		PrincipalBackend\BackendInterface $principalBackend,
43
-		IConfig $config
44
-	) {
45
-		parent::__construct($principalBackend, 'principals/users');
40
+    public function __construct(
41
+        ITrashManager $trashManager,
42
+        PrincipalBackend\BackendInterface $principalBackend,
43
+        IConfig $config
44
+    ) {
45
+        parent::__construct($principalBackend, 'principals/users');
46 46
 
47
-		$this->trashManager = $trashManager;
48
-		$this->disableListing = !$config->getSystemValue('debug', false);
49
-	}
47
+        $this->trashManager = $trashManager;
48
+        $this->disableListing = !$config->getSystemValue('debug', false);
49
+    }
50 50
 
51
-	/**
52
-	 * This method returns a node for a principal.
53
-	 *
54
-	 * The passed array contains principal information, and is guaranteed to
55
-	 * at least contain a uri item. Other properties may or may not be
56
-	 * supplied by the authentication backend.
57
-	 *
58
-	 * @param array $principalInfo
59
-	 * @return INode
60
-	 */
61
-	public function getChildForPrincipal(array $principalInfo): TrashHome {
62
-		[, $name] = \Sabre\Uri\split($principalInfo['uri']);
63
-		$user = \OC::$server->getUserSession()->getUser();
64
-		if (is_null($user) || $name !== $user->getUID()) {
65
-			throw new \Sabre\DAV\Exception\Forbidden();
66
-		}
67
-		return new TrashHome($principalInfo, $this->trashManager, $user);
68
-	}
51
+    /**
52
+     * This method returns a node for a principal.
53
+     *
54
+     * The passed array contains principal information, and is guaranteed to
55
+     * at least contain a uri item. Other properties may or may not be
56
+     * supplied by the authentication backend.
57
+     *
58
+     * @param array $principalInfo
59
+     * @return INode
60
+     */
61
+    public function getChildForPrincipal(array $principalInfo): TrashHome {
62
+        [, $name] = \Sabre\Uri\split($principalInfo['uri']);
63
+        $user = \OC::$server->getUserSession()->getUser();
64
+        if (is_null($user) || $name !== $user->getUID()) {
65
+            throw new \Sabre\DAV\Exception\Forbidden();
66
+        }
67
+        return new TrashHome($principalInfo, $this->trashManager, $user);
68
+    }
69 69
 
70
-	public function getName(): string {
71
-		return 'trashbin';
72
-	}
70
+    public function getName(): string {
71
+        return 'trashbin';
72
+    }
73 73
 }
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Sabre/TrashHome.php 1 patch
Indentation   +65 added lines, -65 removed lines patch added patch discarded remove patch
@@ -34,69 +34,69 @@
 block discarded – undo
34 34
 use Sabre\DAV\ICollection;
35 35
 
36 36
 class TrashHome implements ICollection {
37
-	/** @var ITrashManager */
38
-	private $trashManager;
39
-
40
-	/** @var array */
41
-	private $principalInfo;
42
-
43
-	/** @var IUser */
44
-	private $user;
45
-
46
-	public function __construct(
47
-		array $principalInfo,
48
-		ITrashManager $trashManager,
49
-		IUser $user
50
-	) {
51
-		$this->principalInfo = $principalInfo;
52
-		$this->trashManager = $trashManager;
53
-		$this->user = $user;
54
-	}
55
-
56
-	public function delete() {
57
-		throw new Forbidden();
58
-	}
59
-
60
-	public function getName(): string {
61
-		[, $name] = \Sabre\Uri\split($this->principalInfo['uri']);
62
-		return $name;
63
-	}
64
-
65
-	public function setName($name) {
66
-		throw new Forbidden('Permission denied to rename this trashbin');
67
-	}
68
-
69
-	public function createFile($name, $data = null) {
70
-		throw new Forbidden('Not allowed to create files in the trashbin');
71
-	}
72
-
73
-	public function createDirectory($name) {
74
-		throw new Forbidden('Not allowed to create folders in the trashbin');
75
-	}
76
-
77
-	public function getChild($name) {
78
-		if ($name === 'restore') {
79
-			return new RestoreFolder();
80
-		}
81
-		if ($name === 'trash') {
82
-			return new TrashRoot($this->user, $this->trashManager);
83
-		}
84
-
85
-		throw new NotFound();
86
-	}
87
-
88
-	public function getChildren(): array {
89
-		return [
90
-			new RestoreFolder(),
91
-			new TrashRoot($this->user, $this->trashManager)
92
-		];
93
-	}
94
-
95
-	public function childExists($name): bool {
96
-		return $name === 'restore' || $name === 'trash';
97
-	}
98
-
99
-	public function getLastModified(): int {
100
-		return 0;
101
-	}
37
+    /** @var ITrashManager */
38
+    private $trashManager;
39
+
40
+    /** @var array */
41
+    private $principalInfo;
42
+
43
+    /** @var IUser */
44
+    private $user;
45
+
46
+    public function __construct(
47
+        array $principalInfo,
48
+        ITrashManager $trashManager,
49
+        IUser $user
50
+    ) {
51
+        $this->principalInfo = $principalInfo;
52
+        $this->trashManager = $trashManager;
53
+        $this->user = $user;
54
+    }
55
+
56
+    public function delete() {
57
+        throw new Forbidden();
58
+    }
59
+
60
+    public function getName(): string {
61
+        [, $name] = \Sabre\Uri\split($this->principalInfo['uri']);
62
+        return $name;
63
+    }
64
+
65
+    public function setName($name) {
66
+        throw new Forbidden('Permission denied to rename this trashbin');
67
+    }
68
+
69
+    public function createFile($name, $data = null) {
70
+        throw new Forbidden('Not allowed to create files in the trashbin');
71
+    }
72
+
73
+    public function createDirectory($name) {
74
+        throw new Forbidden('Not allowed to create folders in the trashbin');
75
+    }
76
+
77
+    public function getChild($name) {
78
+        if ($name === 'restore') {
79
+            return new RestoreFolder();
80
+        }
81
+        if ($name === 'trash') {
82
+            return new TrashRoot($this->user, $this->trashManager);
83
+        }
84
+
85
+        throw new NotFound();
86
+    }
87
+
88
+    public function getChildren(): array {
89
+        return [
90
+            new RestoreFolder(),
91
+            new TrashRoot($this->user, $this->trashManager)
92
+        ];
93
+    }
94
+
95
+    public function childExists($name): bool {
96
+        return $name === 'restore' || $name === 'trash';
97
+    }
98
+
99
+    public function getLastModified(): int {
100
+        return 0;
101
+    }
102 102
 }
Please login to merge, or discard this patch.