Completed
Push — master ( 6b4a2d...0f3ba9 )
by Joas
28:55 queued 11:48
created

Provider::getParameters()   D

Complexity

Conditions 16
Paths 20

Size

Total Lines 48
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 36
nc 20
nop 1
dl 0
loc 48
rs 4.9765
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 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\Files\Folder;
32
use OCP\Files\InvalidPathException;
33
use OCP\Files\IRootFolder;
34
use OCP\Files\Node;
35
use OCP\Files\NotFoundException;
36
use OCP\IL10N;
37
use OCP\IURLGenerator;
38
use OCP\IUser;
39
use OCP\IUserManager;
40
use OCP\L10N\IFactory;
41
42
class Provider implements IProvider {
43
44
	/** @var IFactory */
45
	protected $languageFactory;
46
47
	/** @var IL10N */
48
	protected $l;
49
	/** @var IL10N */
50
	protected $activityLang;
51
52
	/** @var IURLGenerator */
53
	protected $url;
54
55
	/** @var IManager */
56
	protected $activityManager;
57
58
	/** @var IUserManager */
59
	protected $userManager;
60
61
	/** @var IRootFolder */
62
	protected $rootFolder;
63
64
	/** @var IEventMerger */
65
	protected $eventMerger;
66
67
	/** @var string[] cached displayNames - key is the UID and value the displayname */
68
	protected $displayNames = [];
69
70
	protected $fileIsEncrypted = false;
71
72
	/**
73
	 * @param IFactory $languageFactory
74
	 * @param IURLGenerator $url
75
	 * @param IManager $activityManager
76
	 * @param IUserManager $userManager
77
	 * @param IRootFolder $rootFolder
78
	 * @param IEventMerger $eventMerger
79
	 */
80 View Code Duplication
	public function __construct(IFactory $languageFactory, IURLGenerator $url, IManager $activityManager, IUserManager $userManager, IRootFolder $rootFolder, IEventMerger $eventMerger) {
81
		$this->languageFactory = $languageFactory;
82
		$this->url = $url;
83
		$this->activityManager = $activityManager;
84
		$this->userManager = $userManager;
85
		$this->rootFolder = $rootFolder;
86
		$this->eventMerger = $eventMerger;
87
	}
88
89
	/**
90
	 * @param string $language
91
	 * @param IEvent $event
92
	 * @param IEvent|null $previousEvent
93
	 * @return IEvent
94
	 * @throws \InvalidArgumentException
95
	 * @since 11.0.0
96
	 */
97
	public function parse($language, IEvent $event, IEvent $previousEvent = null) {
98
		if ($event->getApp() !== 'files') {
99
			throw new \InvalidArgumentException();
100
		}
101
102
		$this->l = $this->languageFactory->get('files', $language);
103
		$this->activityLang = $this->languageFactory->get('activity', $language);
104
105
		if ($this->activityManager->isFormattingFilteredObject()) {
106
			try {
107
				return $this->parseShortVersion($event, $previousEvent);
108
			} catch (\InvalidArgumentException $e) {
109
				// Ignore and simply use the long version...
110
			}
111
		}
112
113
		return $this->parseLongVersion($event, $previousEvent);
114
	}
115
116
	protected function setIcon(IEvent $event, $icon) {
117
		if ($this->activityManager->getRequirePNG()) {
118
			$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', $icon . '.png')));
119
		} else {
120
			$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', $icon . '.svg')));
121
		}
122
	}
123
124
	/**
125
	 * @param IEvent $event
126
	 * @param IEvent|null $previousEvent
127
	 * @return IEvent
128
	 * @throws \InvalidArgumentException
129
	 * @since 11.0.0
130
	 */
131
	public function parseShortVersion(IEvent $event, IEvent $previousEvent = null) {
132
		$parsedParameters = $this->getParameters($event);
133
134 View Code Duplication
		if ($event->getSubject() === 'created_by') {
135
			$subject = $this->l->t('Created by {user}');
136
			$this->setIcon($event, 'add-color');
137
		} else if ($event->getSubject() === 'changed_by') {
138
			$subject = $this->l->t('Changed by {user}');
139
			$this->setIcon($event, 'change');
140
		} else if ($event->getSubject() === 'deleted_by') {
141
			$subject = $this->l->t('Deleted by {user}');
142
			$this->setIcon($event, 'delete-color');
143
		} else if ($event->getSubject() === 'restored_by') {
144
			$subject = $this->l->t('Restored by {user}');
145
		} else if ($event->getSubject() === 'renamed_by') {
146
			$subject = $this->l->t('Renamed by {user}');
147
			$this->setIcon($event, 'change');
148
		} else if ($event->getSubject() === 'moved_by') {
149
			$subject = $this->l->t('Moved by {user}');
150
			$this->setIcon($event, 'change');
151
		} else {
152
			throw new \InvalidArgumentException();
153
		}
154
155 View Code Duplication
		if (!isset($parsedParameters['user'])) {
156
			// External user via public link share
157
			$subject = str_replace('{user}', $this->activityLang->t('"remote user"'), $subject);
158
		}
159
160
		$this->setSubjects($event, $subject, $parsedParameters);
161
162
		return $this->eventMerger->mergeEvents('user', $event, $previousEvent);
163
	}
164
165
	/**
166
	 * @param IEvent $event
167
	 * @param IEvent|null $previousEvent
168
	 * @return IEvent
169
	 * @throws \InvalidArgumentException
170
	 * @since 11.0.0
171
	 */
172
	public function parseLongVersion(IEvent $event, IEvent $previousEvent = null) {
173
		$this->fileIsEncrypted = false;
174
		$parsedParameters = $this->getParameters($event);
175
176
		if ($event->getSubject() === 'created_self') {
177
			$subject = $this->l->t('You created {file}');
178
			if ($this->fileIsEncrypted) {
179
				$subject = $this->l->t('You created an encrypted file in {file}');
180
			}
181
			$this->setIcon($event, 'add-color');
182
		} else if ($event->getSubject() === 'created_by') {
183
			$subject = $this->l->t('{user} created {file}');
184
			if ($this->fileIsEncrypted) {
185
				$subject = $this->l->t('{user} created an encrypted file in {file}');
186
			}
187
			$this->setIcon($event, 'add-color');
188
		} else if ($event->getSubject() === 'created_public') {
189
			$subject = $this->l->t('{file} was created in a public folder');
190
			$this->setIcon($event, 'add-color');
191
		} else if ($event->getSubject() === 'changed_self') {
192
			$subject = $this->l->t('You changed {file}');
193
			if ($this->fileIsEncrypted) {
194
				$subject = $this->l->t('You changed an encrypted file in {file}');
195
			}
196
			$this->setIcon($event, 'change');
197
		} else if ($event->getSubject() === 'changed_by') {
198
			$subject = $this->l->t('{user} changed {file}');
199
			if ($this->fileIsEncrypted) {
200
				$subject = $this->l->t('{user} changed an encrypted file in {file}');
201
			}
202
			$this->setIcon($event, 'change');
203
		} else if ($event->getSubject() === 'deleted_self') {
204
			$subject = $this->l->t('You deleted {file}');
205
			if ($this->fileIsEncrypted) {
206
				$subject = $this->l->t('You deleted an encrypted file in {file}');
207
			}
208
			$this->setIcon($event, 'delete-color');
209
		} else if ($event->getSubject() === 'deleted_by') {
210
			$subject = $this->l->t('{user} deleted {file}');
211
			if ($this->fileIsEncrypted) {
212
				$subject = $this->l->t('{user} deleted an encrypted file in {file}');
213
			}
214
			$this->setIcon($event, 'delete-color');
215 View Code Duplication
		} else if ($event->getSubject() === 'restored_self') {
216
			$subject = $this->l->t('You restored {file}');
217
		} else if ($event->getSubject() === 'restored_by') {
218
			$subject = $this->l->t('{user} restored {file}');
219
		} else if ($event->getSubject() === 'renamed_self') {
220
			$subject = $this->l->t('You renamed {oldfile} to {newfile}');
221
			$this->setIcon($event, 'change');
222
		} else if ($event->getSubject() === 'renamed_by') {
223
			$subject = $this->l->t('{user} renamed {oldfile} to {newfile}');
224
			$this->setIcon($event, 'change');
225
		} else if ($event->getSubject() === 'moved_self') {
226
			$subject = $this->l->t('You moved {oldfile} to {newfile}');
227
			$this->setIcon($event, 'change');
228
		} else if ($event->getSubject() === 'moved_by') {
229
			$subject = $this->l->t('{user} moved {oldfile} to {newfile}');
230
			$this->setIcon($event, 'change');
231
		} else {
232
			throw new \InvalidArgumentException();
233
		}
234
235
		if ($this->fileIsEncrypted) {
236
			$event->setSubject($event->getSubject() . '_enc', $event->getSubjectParameters());
237
		}
238
239 View Code Duplication
		if (!isset($parsedParameters['user'])) {
240
			// External user via public link share
241
			$subject = str_replace('{user}', $this->activityLang->t('"remote user"'), $subject);
242
		}
243
244
		$this->setSubjects($event, $subject, $parsedParameters);
245
246
		$event = $this->eventMerger->mergeEvents('file', $event, $previousEvent);
247
248
		if ($event->getChildEvent() === null) {
249
			// Couldn't group by file, maybe we can group by user
250
			$event = $this->eventMerger->mergeEvents('user', $event, $previousEvent);
251
		}
252
253
		return $event;
254
	}
255
256 View Code Duplication
	protected function setSubjects(IEvent $event, $subject, array $parameters) {
257
		$placeholders = $replacements = [];
258
		foreach ($parameters as $placeholder => $parameter) {
259
			$placeholders[] = '{' . $placeholder . '}';
260
			if ($parameter['type'] === 'file') {
261
				$replacements[] = $parameter['path'];
262
			} else {
263
				$replacements[] = $parameter['name'];
264
			}
265
		}
266
267
		$event->setParsedSubject(str_replace($placeholders, $replacements, $subject))
268
			->setRichSubject($subject, $parameters);
269
	}
270
271
	/**
272
	 * @param IEvent $event
273
	 * @return array
274
	 * @throws \InvalidArgumentException
275
	 */
276
	protected function getParameters(IEvent $event) {
277
		$parameters = $event->getSubjectParameters();
278
		switch ($event->getSubject()) {
279
			case 'created_self':
280
			case 'created_public':
281
			case 'changed_self':
282
			case 'deleted_self':
283
			case 'restored_self':
284
				return [
285
					'file' => $this->getFile($parameters[0], $event),
286
				];
287
			case 'created_by':
288
			case 'changed_by':
289
			case 'deleted_by':
290
			case 'restored_by':
291
				if ($parameters[1] === '') {
292
					// External user via public link share
293
					return [
294
						'file' => $this->getFile($parameters[0], $event),
295
					];
296
				}
297
				return [
298
					'file' => $this->getFile($parameters[0], $event),
299
					'user' => $this->getUser($parameters[1]),
300
				];
301
			case 'renamed_self':
302
			case 'moved_self':
303
				return [
304
					'newfile' => $this->getFile($parameters[0]),
305
					'oldfile' => $this->getFile($parameters[1]),
306
				];
307
			case 'renamed_by':
308
			case 'moved_by':
309
				if ($parameters[1] === '') {
310
					// External user via public link share
311
					return [
312
						'newfile' => $this->getFile($parameters[0]),
313
						'oldfile' => $this->getFile($parameters[2]),
314
					];
315
				}
316
				return [
317
					'newfile' => $this->getFile($parameters[0]),
318
					'user' => $this->getUser($parameters[1]),
319
					'oldfile' => $this->getFile($parameters[2]),
320
				];
321
		}
322
		return [];
323
	}
324
325
	/**
326
	 * @param array|string $parameter
327
	 * @param IEvent|null $event
328
	 * @return array
329
	 * @throws \InvalidArgumentException
330
	 */
331
	protected function getFile($parameter, IEvent $event = null) {
332
		if (is_array($parameter)) {
333
			$path = reset($parameter);
334
			$id = (string) key($parameter);
335
		} else if ($event !== null) {
336
			// Legacy from before ownCloud 8.2
337
			$path = $parameter;
338
			$id = $event->getObjectId();
339
		} else {
340
			throw new \InvalidArgumentException('Could not generate file parameter');
341
		}
342
343
		$encryptionContainer = $this->getEndToEndEncryptionContainer($id, $path);
344
		if ($encryptionContainer instanceof Folder) {
345
			$this->fileIsEncrypted = true;
346
			try {
347
				$fullPath = rtrim($encryptionContainer->getPath(), '/');
348
				// Remove /user/files/...
349
				list(,,, $path) = explode('/', $fullPath, 4);
350
				if (!$path) {
351
					throw new InvalidPathException('Path could not be split correctly');
352
				}
353
354
				return [
355
					'type' => 'file',
356
					'id' => $encryptionContainer->getId(),
357
					'name' => $encryptionContainer->getName(),
358
					'path' => $path,
359
					'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $encryptionContainer->getId()]),
360
				];
361
			} catch (\Exception $e) {
362
				// fall back to the normal one
363
				$this->fileIsEncrypted = false;
364
			}
365
		}
366
367
		return [
368
			'type' => 'file',
369
			'id' => $id,
370
			'name' => basename($path),
371
			'path' => trim($path, '/'),
372
			'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]),
373
		];
374
	}
375
376
	protected $fileEncrypted = [];
377
378
	/**
379
	 * Check if a file is end2end encrypted
380
	 * @param int $fileId
381
	 * @param string $path
382
	 * @return Folder|null
383
	 */
384
	protected function getEndToEndEncryptionContainer($fileId, $path) {
385
		if (isset($this->fileEncrypted[$fileId])) {
386
			return $this->fileEncrypted[$fileId];
387
		}
388
389
		$fileName = basename($path);
390
		if (!preg_match('/^[0-9a-fA-F]{32}$/', $fileName)) {
391
			$this->fileEncrypted[$fileId] = false;
392
			return $this->fileEncrypted[$fileId];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fileEncrypted[$fileId]; (false) is incompatible with the return type documented by OCA\Files\Activity\Provi...oEndEncryptionContainer of type OCP\Files\Folder|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
393
		}
394
395
		$userFolder = $this->rootFolder->getUserFolder($this->activityManager->getCurrentUserId());
396
		$files = $userFolder->getById($fileId);
397
		if (empty($files)) {
398
			try {
399
				// Deleted, try with parent
400
				$file = $this->findExistingParent($userFolder, dirname($path));
401
			} catch (NotFoundException $e) {
402
				return null;
403
			}
404
405
			if (!$file instanceof Folder || !$file->isEncrypted()) {
406
				return null;
407
			}
408
409
			$this->fileEncrypted[$fileId] = $file;
410
			return $file;
411
		}
412
413
		$file = array_shift($files);
414
415
		if ($file instanceof Folder && $file->isEncrypted()) {
416
			// If the folder is encrypted, it is the Container,
417
			// but can be the name is just fine.
418
			$this->fileEncrypted[$fileId] = true;
419
			return null;
420
		}
421
422
		$this->fileEncrypted[$fileId] = $this->getParentEndToEndEncryptionContainer($userFolder, $file);
0 ignored issues
show
Bug introduced by
It seems like $file defined by array_shift($files) on line 413 can be null; however, OCA\Files\Activity\Provi...ndEncryptionContainer() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
423
		return $this->fileEncrypted[$fileId];
424
	}
425
426
	/**
427
	 * @param Folder $userFolder
428
	 * @param string $path
429
	 * @return Folder
430
	 * @throws NotFoundException
431
	 */
432
	protected function findExistingParent(Folder $userFolder, $path) {
433
		if ($path === '/') {
434
			throw new NotFoundException('Reached the root');
435
		}
436
437
		try {
438
			$folder = $userFolder->get(dirname($path));
439
		} catch (NotFoundException $e) {
440
			return $this->findExistingParent($userFolder, dirname($path));
441
		}
442
443
		return $folder;
444
	}
445
446
	/**
447
	 * Check all parents until the user's root folder if one is encrypted
448
	 *
449
	 * @param Folder $userFolder
450
	 * @param Node $file
451
	 * @return Node|null
452
	 */
453
	protected function getParentEndToEndEncryptionContainer(Folder $userFolder, Node $file) {
454
		try {
455
			$parent = $file->getParent();
456
457
			if ($userFolder->getId() === $parent->getId()) {
458
				return null;
459
			}
460
		} catch (\Exception $e) {
461
			return null;
462
		}
463
464
		if ($parent->isEncrypted()) {
465
			return $parent;
466
		}
467
468
		return $this->getParentEndToEndEncryptionContainer($userFolder, $parent);
469
	}
470
471
	/**
472
	 * @param string $uid
473
	 * @return array
474
	 */
475 View Code Duplication
	protected function getUser($uid) {
476
		if (!isset($this->displayNames[$uid])) {
477
			$this->displayNames[$uid] = $this->getDisplayName($uid);
478
		}
479
480
		return [
481
			'type' => 'user',
482
			'id' => $uid,
483
			'name' => $this->displayNames[$uid],
484
		];
485
	}
486
487
	/**
488
	 * @param string $uid
489
	 * @return string
490
	 */
491
	protected function getDisplayName($uid) {
492
		$user = $this->userManager->get($uid);
493
		if ($user instanceof IUser) {
494
			return $user->getDisplayName();
495
		} else {
496
			return $uid;
497
		}
498
	}
499
}
500