Passed
Push — master ( c8a360...d3d2db )
by Roeland
09:38 queued 11s
created

Provider::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 16
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Joas Schilling <[email protected]>
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
namespace OCA\Files\Activity;
26
27
use OCP\Activity\IEvent;
28
use OCP\Activity\IEventMerger;
29
use OCP\Activity\IManager;
30
use OCP\Activity\IProvider;
31
use OCP\Contacts\IManager as IContactsManager;
32
use OCP\Federation\ICloudIdManager;
33
use OCP\Files\Folder;
34
use OCP\Files\InvalidPathException;
35
use OCP\Files\IRootFolder;
36
use OCP\Files\Node;
37
use OCP\Files\NotFoundException;
38
use OCP\IL10N;
39
use OCP\IURLGenerator;
40
use OCP\IUser;
41
use OCP\IUserManager;
42
use OCP\L10N\IFactory;
43
44
class Provider implements IProvider {
45
46
	/** @var IFactory */
47
	protected $languageFactory;
48
49
	/** @var IL10N */
50
	protected $l;
51
	/** @var IL10N */
52
	protected $activityLang;
53
54
	/** @var IURLGenerator */
55
	protected $url;
56
57
	/** @var IManager */
58
	protected $activityManager;
59
60
	/** @var IUserManager */
61
	protected $userManager;
62
63
	/** @var IRootFolder */
64
	protected $rootFolder;
65
66
	/** @var IEventMerger */
67
	protected $eventMerger;
68
69
	/** @var ICloudIdManager */
70
	protected $cloudIdManager;
71
72
	/** @var IContactsManager */
73
	protected $contactsManager;
74
75
	/** @var string[] cached displayNames - key is the cloud id and value the displayname */
76
	protected $displayNames = [];
77
78
	protected $fileIsEncrypted = false;
79
80
	public function __construct(IFactory $languageFactory,
81
								IURLGenerator $url,
82
								IManager $activityManager,
83
								IUserManager $userManager,
84
								IRootFolder $rootFolder,
85
								ICloudIdManager $cloudIdManager,
86
								IContactsManager $contactsManager,
87
								IEventMerger $eventMerger) {
88
		$this->languageFactory = $languageFactory;
89
		$this->url = $url;
90
		$this->activityManager = $activityManager;
91
		$this->userManager = $userManager;
92
		$this->rootFolder = $rootFolder;
93
		$this->cloudIdManager = $cloudIdManager;
94
		$this->contactsManager = $contactsManager;
95
		$this->eventMerger = $eventMerger;
96
	}
97
98
	/**
99
	 * @param string $language
100
	 * @param IEvent $event
101
	 * @param IEvent|null $previousEvent
102
	 * @return IEvent
103
	 * @throws \InvalidArgumentException
104
	 * @since 11.0.0
105
	 */
106
	public function parse($language, IEvent $event, IEvent $previousEvent = null) {
107
		if ($event->getApp() !== 'files') {
108
			throw new \InvalidArgumentException();
109
		}
110
111
		$this->l = $this->languageFactory->get('files', $language);
112
		$this->activityLang = $this->languageFactory->get('activity', $language);
113
114
		if ($this->activityManager->isFormattingFilteredObject()) {
115
			try {
116
				return $this->parseShortVersion($event, $previousEvent);
117
			} catch (\InvalidArgumentException $e) {
118
				// Ignore and simply use the long version...
119
			}
120
		}
121
122
		return $this->parseLongVersion($event, $previousEvent);
123
	}
124
125
	protected function setIcon(IEvent $event, string $icon, string $app = 'files') {
126
		if ($this->activityManager->getRequirePNG()) {
127
			$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath($app, $icon . '.png')));
128
		} else {
129
			$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath($app, $icon . '.svg')));
130
		}
131
	}
132
133
	/**
134
	 * @param IEvent $event
135
	 * @param IEvent|null $previousEvent
136
	 * @return IEvent
137
	 * @throws \InvalidArgumentException
138
	 * @since 11.0.0
139
	 */
140
	public function parseShortVersion(IEvent $event, IEvent $previousEvent = null) {
141
		$parsedParameters = $this->getParameters($event);
142
143
		if ($event->getSubject() === 'created_by') {
144
			$subject = $this->l->t('Created by {user}');
145
			$this->setIcon($event, 'add-color');
146
		} else if ($event->getSubject() === 'changed_by') {
147
			$subject = $this->l->t('Changed by {user}');
148
			$this->setIcon($event, 'change');
149
		} else if ($event->getSubject() === 'deleted_by') {
150
			$subject = $this->l->t('Deleted by {user}');
151
			$this->setIcon($event, 'delete-color');
152
		} else if ($event->getSubject() === 'restored_by') {
153
			$subject = $this->l->t('Restored by {user}');
154
			$this->setIcon($event, 'actions/history', 'core');
155
		} else if ($event->getSubject() === 'renamed_by') {
156
			$subject = $this->l->t('Renamed by {user}');
157
			$this->setIcon($event, 'change');
158
		} else if ($event->getSubject() === 'moved_by') {
159
			$subject = $this->l->t('Moved by {user}');
160
			$this->setIcon($event, 'change');
161
		} else {
162
			throw new \InvalidArgumentException();
163
		}
164
165
		if (!isset($parsedParameters['user'])) {
166
			// External user via public link share
167
			$subject = str_replace('{user}', $this->activityLang->t('"remote user"'), $subject);
168
		}
169
170
		$this->setSubjects($event, $subject, $parsedParameters);
171
172
		return $this->eventMerger->mergeEvents('user', $event, $previousEvent);
173
	}
174
175
	/**
176
	 * @param IEvent $event
177
	 * @param IEvent|null $previousEvent
178
	 * @return IEvent
179
	 * @throws \InvalidArgumentException
180
	 * @since 11.0.0
181
	 */
182
	public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
183
		$this->fileIsEncrypted = false;
184
		$parsedParameters = $this->getParameters($event);
185
186
		if ($event->getSubject() === 'created_self') {
187
			$subject = $this->l->t('You created {file}');
188
			if ($this->fileIsEncrypted) {
189
				$subject = $this->l->t('You created an encrypted file in {file}');
190
			}
191
			$this->setIcon($event, 'add-color');
192
		} else if ($event->getSubject() === 'created_by') {
193
			$subject = $this->l->t('{user} created {file}');
194
			if ($this->fileIsEncrypted) {
195
				$subject = $this->l->t('{user} created an encrypted file in {file}');
196
			}
197
			$this->setIcon($event, 'add-color');
198
		} else if ($event->getSubject() === 'created_public') {
199
			$subject = $this->l->t('{file} was created in a public folder');
200
			$this->setIcon($event, 'add-color');
201
		} else if ($event->getSubject() === 'changed_self') {
202
			$subject = $this->l->t('You changed {file}');
203
			if ($this->fileIsEncrypted) {
204
				$subject = $this->l->t('You changed an encrypted file in {file}');
205
			}
206
			$this->setIcon($event, 'change');
207
		} else if ($event->getSubject() === 'changed_by') {
208
			$subject = $this->l->t('{user} changed {file}');
209
			if ($this->fileIsEncrypted) {
210
				$subject = $this->l->t('{user} changed an encrypted file in {file}');
211
			}
212
			$this->setIcon($event, 'change');
213
		} else if ($event->getSubject() === 'deleted_self') {
214
			$subject = $this->l->t('You deleted {file}');
215
			if ($this->fileIsEncrypted) {
216
				$subject = $this->l->t('You deleted an encrypted file in {file}');
217
			}
218
			$this->setIcon($event, 'delete-color');
219
		} else if ($event->getSubject() === 'deleted_by') {
220
			$subject = $this->l->t('{user} deleted {file}');
221
			if ($this->fileIsEncrypted) {
222
				$subject = $this->l->t('{user} deleted an encrypted file in {file}');
223
			}
224
			$this->setIcon($event, 'delete-color');
225
		} else if ($event->getSubject() === 'restored_self') {
226
			$subject = $this->l->t('You restored {file}');
227
			$this->setIcon($event, 'actions/history', 'core');
228
		} else if ($event->getSubject() === 'restored_by') {
229
			$subject = $this->l->t('{user} restored {file}');
230
			$this->setIcon($event, 'actions/history', 'core');
231
		} else if ($event->getSubject() === 'renamed_self') {
232
			$subject = $this->l->t('You renamed {oldfile} to {newfile}');
233
			$this->setIcon($event, 'change');
234
		} else if ($event->getSubject() === 'renamed_by') {
235
			$subject = $this->l->t('{user} renamed {oldfile} to {newfile}');
236
			$this->setIcon($event, 'change');
237
		} else if ($event->getSubject() === 'moved_self') {
238
			$subject = $this->l->t('You moved {oldfile} to {newfile}');
239
			$this->setIcon($event, 'change');
240
		} else if ($event->getSubject() === 'moved_by') {
241
			$subject = $this->l->t('{user} moved {oldfile} to {newfile}');
242
			$this->setIcon($event, 'change');
243
		} else {
244
			throw new \InvalidArgumentException();
245
		}
246
247
		if ($this->fileIsEncrypted) {
248
			$event->setSubject($event->getSubject() . '_enc', $event->getSubjectParameters());
249
		}
250
251
		if (!isset($parsedParameters['user'])) {
252
			// External user via public link share
253
			$subject = str_replace('{user}', $this->activityLang->t('"remote user"'), $subject);
254
		}
255
256
		$this->setSubjects($event, $subject, $parsedParameters);
257
258
		if ($event->getSubject() === 'moved_self' || $event->getSubject() === 'moved_by') {
259
			$event = $this->eventMerger->mergeEvents('oldfile', $event, $previousEvent);
260
		} else {
261
			$event = $this->eventMerger->mergeEvents('file', $event, $previousEvent);
262
		}
263
264
		if ($event->getChildEvent() === null) {
265
			// Couldn't group by file, maybe we can group by user
266
			$event = $this->eventMerger->mergeEvents('user', $event, $previousEvent);
267
		}
268
269
		return $event;
270
	}
271
272
	protected function setSubjects(IEvent $event, $subject, array $parameters) {
273
		$placeholders = $replacements = [];
274
		foreach ($parameters as $placeholder => $parameter) {
275
			$placeholders[] = '{' . $placeholder . '}';
276
			if ($parameter['type'] === 'file') {
277
				$replacements[] = $parameter['path'];
278
			} else {
279
				$replacements[] = $parameter['name'];
280
			}
281
		}
282
283
		$event->setParsedSubject(str_replace($placeholders, $replacements, $subject))
284
			->setRichSubject($subject, $parameters);
285
	}
286
287
	/**
288
	 * @param IEvent $event
289
	 * @return array
290
	 * @throws \InvalidArgumentException
291
	 */
292
	protected function getParameters(IEvent $event) {
293
		$parameters = $event->getSubjectParameters();
294
		switch ($event->getSubject()) {
295
			case 'created_self':
296
			case 'created_public':
297
			case 'changed_self':
298
			case 'deleted_self':
299
			case 'restored_self':
300
				return [
301
					'file' => $this->getFile($parameters[0], $event),
302
				];
303
			case 'created_by':
304
			case 'changed_by':
305
			case 'deleted_by':
306
			case 'restored_by':
307
				if ($parameters[1] === '') {
308
					// External user via public link share
309
					return [
310
						'file' => $this->getFile($parameters[0], $event),
311
					];
312
				}
313
				return [
314
					'file' => $this->getFile($parameters[0], $event),
315
					'user' => $this->getUser($parameters[1]),
316
				];
317
			case 'renamed_self':
318
			case 'moved_self':
319
				return [
320
					'newfile' => $this->getFile($parameters[0]),
321
					'oldfile' => $this->getFile($parameters[1]),
322
				];
323
			case 'renamed_by':
324
			case 'moved_by':
325
				if ($parameters[1] === '') {
326
					// External user via public link share
327
					return [
328
						'newfile' => $this->getFile($parameters[0]),
329
						'oldfile' => $this->getFile($parameters[2]),
330
					];
331
				}
332
				return [
333
					'newfile' => $this->getFile($parameters[0]),
334
					'user' => $this->getUser($parameters[1]),
335
					'oldfile' => $this->getFile($parameters[2]),
336
				];
337
		}
338
		return [];
339
	}
340
341
	/**
342
	 * @param array|string $parameter
343
	 * @param IEvent|null $event
344
	 * @return array
345
	 * @throws \InvalidArgumentException
346
	 */
347
	protected function getFile($parameter, IEvent $event = null) {
348
		if (is_array($parameter)) {
349
			$path = reset($parameter);
350
			$id = (string) key($parameter);
351
		} else if ($event !== null) {
352
			// Legacy from before ownCloud 8.2
353
			$path = $parameter;
354
			$id = $event->getObjectId();
355
		} else {
356
			throw new \InvalidArgumentException('Could not generate file parameter');
357
		}
358
359
		$encryptionContainer = $this->getEndToEndEncryptionContainer($id, $path);
360
		if ($encryptionContainer instanceof Folder) {
361
			$this->fileIsEncrypted = true;
362
			try {
363
				$fullPath = rtrim($encryptionContainer->getPath(), '/');
364
				// Remove /user/files/...
365
				list(,,, $path) = explode('/', $fullPath, 4);
366
				if (!$path) {
367
					throw new InvalidPathException('Path could not be split correctly');
368
				}
369
370
				return [
371
					'type' => 'file',
372
					'id' => $encryptionContainer->getId(),
373
					'name' => $encryptionContainer->getName(),
374
					'path' => $path,
375
					'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $encryptionContainer->getId()]),
376
				];
377
			} catch (\Exception $e) {
378
				// fall back to the normal one
379
				$this->fileIsEncrypted = false;
380
			}
381
		}
382
383
		return [
384
			'type' => 'file',
385
			'id' => $id,
386
			'name' => basename($path),
387
			'path' => trim($path, '/'),
388
			'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]),
389
		];
390
	}
391
392
	protected $fileEncrypted = [];
393
394
	/**
395
	 * Check if a file is end2end encrypted
396
	 * @param int $fileId
397
	 * @param string $path
398
	 * @return Folder|null
399
	 */
400
	protected function getEndToEndEncryptionContainer($fileId, $path) {
401
		if (isset($this->fileEncrypted[$fileId])) {
402
			return $this->fileEncrypted[$fileId];
403
		}
404
405
		$fileName = basename($path);
406
		if (!preg_match('/^[0-9a-fA-F]{32}$/', $fileName)) {
407
			$this->fileEncrypted[$fileId] = false;
408
			return $this->fileEncrypted[$fileId];
409
		}
410
411
		$userFolder = $this->rootFolder->getUserFolder($this->activityManager->getCurrentUserId());
412
		$files = $userFolder->getById($fileId);
413
		if (empty($files)) {
414
			try {
415
				// Deleted, try with parent
416
				$file = $this->findExistingParent($userFolder, dirname($path));
417
			} catch (NotFoundException $e) {
418
				return null;
419
			}
420
421
			if (!$file instanceof Folder || !$file->isEncrypted()) {
0 ignored issues
show
introduced by
$file is always a sub-type of OCP\Files\Folder.
Loading history...
422
				return null;
423
			}
424
425
			$this->fileEncrypted[$fileId] = $file;
426
			return $file;
427
		}
428
429
		$file = array_shift($files);
430
431
		if ($file instanceof Folder && $file->isEncrypted()) {
432
			// If the folder is encrypted, it is the Container,
433
			// but can be the name is just fine.
434
			$this->fileEncrypted[$fileId] = true;
435
			return null;
436
		}
437
438
		$this->fileEncrypted[$fileId] = $this->getParentEndToEndEncryptionContainer($userFolder, $file);
439
		return $this->fileEncrypted[$fileId];
440
	}
441
442
	/**
443
	 * @param Folder $userFolder
444
	 * @param string $path
445
	 * @return Folder
446
	 * @throws NotFoundException
447
	 */
448
	protected function findExistingParent(Folder $userFolder, $path) {
449
		if ($path === '/') {
450
			throw new NotFoundException('Reached the root');
451
		}
452
453
		try {
454
			$folder = $userFolder->get(dirname($path));
455
		} catch (NotFoundException $e) {
456
			return $this->findExistingParent($userFolder, dirname($path));
457
		}
458
459
		return $folder;
460
	}
461
462
	/**
463
	 * Check all parents until the user's root folder if one is encrypted
464
	 *
465
	 * @param Folder $userFolder
466
	 * @param Node $file
467
	 * @return Node|null
468
	 */
469
	protected function getParentEndToEndEncryptionContainer(Folder $userFolder, Node $file) {
470
		try {
471
			$parent = $file->getParent();
472
473
			if ($userFolder->getId() === $parent->getId()) {
474
				return null;
475
			}
476
		} catch (\Exception $e) {
477
			return null;
478
		}
479
480
		if ($parent->isEncrypted()) {
481
			return $parent;
482
		}
483
484
		return $this->getParentEndToEndEncryptionContainer($userFolder, $parent);
485
	}
486
487
	/**
488
	 * @param string $uid
489
	 * @return array
490
	 */
491
	protected function getUser($uid) {
492
		// First try local user
493
		$user = $this->userManager->get($uid);
494
		if ($user instanceof IUser) {
495
			return [
496
				'type' => 'user',
497
				'id' => $user->getUID(),
498
				'name' => $user->getDisplayName(),
499
			];
500
		}
501
502
		// Then a contact from the addressbook
503
		if ($this->cloudIdManager->isValidCloudId($uid)) {
504
			$cloudId = $this->cloudIdManager->resolveCloudId($uid);
505
			return [
506
				'type' => 'user',
507
				'id' => $cloudId->getUser(),
508
				'name' => $this->getDisplayNameFromAddressBook($cloudId->getDisplayId()),
509
				'server' => $cloudId->getRemote(),
510
			];
511
		}
512
513
		// Fallback to empty dummy data
514
		return [
515
			'type' => 'user',
516
			'id' => $uid,
517
			'name' => $uid,
518
		];
519
	}
520
521
	protected function getDisplayNameFromAddressBook(string $search): string {
522
		if (isset($this->displayNames[$search])) {
523
			return $this->displayNames[$search];
524
		}
525
526
		$addressBookContacts = $this->contactsManager->search($search, ['CLOUD']);
527
		foreach ($addressBookContacts as $contact) {
528
			if (isset($contact['isLocalSystemBook'])) {
529
				continue;
530
			}
531
532
			if (isset($contact['CLOUD'])) {
533
				$cloudIds = $contact['CLOUD'];
534
				if (is_string($cloudIds)) {
535
					$cloudIds = [$cloudIds];
536
				}
537
538
				$lowerSearch = strtolower($search);
539
				foreach ($cloudIds as $cloudId) {
540
					if (strtolower($cloudId) === $lowerSearch) {
541
						$this->displayNames[$search] = $contact['FN'] . " ($cloudId)";
542
						return $this->displayNames[$search];
543
					}
544
				}
545
			}
546
		}
547
548
		return $search;
549
	}
550
}
551