Completed
Pull Request — master (#174)
by Joas
03:32
created

APIv2   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 401
Duplicated Lines 1.75 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 50
c 0
b 0
f 0
lcom 2
cbo 3
dl 7
loc 401
ccs 148
cts 148
cp 1
rs 8.6206

11 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 24 1
D validateParameters() 0 26 9
A getDefault() 0 3 1
A getFilter() 0 3 1
A listFilters() 7 21 2
C get() 0 65 16
C generateHeaders() 0 32 7
B getPreview() 0 37 6
A getPreviewFromPath() 0 10 1
A getPreviewPathFromMimeType() 0 8 2
A getPreviewLink() 0 13 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like APIv2 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use APIv2, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 *
7
 * @license AGPL-3.0
8
 *
9
 * This code is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License, version 3,
11
 * as published by the Free Software Foundation.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License, version 3,
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
20
 *
21
 */
22
23
namespace OCA\Activity\Controller;
24
25
26
use OC\Files\View;
27
use OCA\Activity\Data;
28
use OCA\Activity\Exception\InvalidFilterException;
29
use OCA\Activity\GroupHelper;
30
use OCA\Activity\UserSettings;
31
use OCA\Activity\ViewInfoCache;
32
use OCP\Activity\IFilter;
33
use OCP\Activity\IManager;
34
use OCP\AppFramework\Http;
35
use OCP\AppFramework\Http\DataResponse;
36
use OCP\AppFramework\OCSController;
37
use OCP\Files\FileInfo;
38
use OCP\Files\IMimeTypeDetector;
39
use OCP\IPreview;
40
use OCP\IRequest;
41
use OCP\IURLGenerator;
42
use OCP\IUser;
43
use OCP\IUserSession;
44
45
class APIv2 extends OCSController {
46
47
	/** @var string */
48
	protected $filter;
49
50
	/** @var int */
51
	protected $since;
52
53
	/** @var int */
54
	protected $limit;
55
56
	/** @var string */
57
	protected $sort;
58
59
	/** @var string */
60
	protected $objectType;
61
62
	/** @var int */
63
	protected $objectId;
64
65
	/** @var string */
66
	protected $user;
67
68
	/** @var bool */
69
	protected $loadPreviews;
70
71
	/** @var IManager */
72
	protected $activityManager;
73
74
	/** @var Data */
75
	protected $data;
76
77
	/** @var GroupHelper */
78
	protected $helper;
79
80
	/** @var UserSettings */
81
	protected $settings;
82
83
	/** @var IURLGenerator */
84
	protected $urlGenerator;
85
86
	/** @var IUserSession */
87
	protected $userSession;
88
89
	/** @var IPreview */
90
	protected $preview;
91
92
	/** @var IMimeTypeDetector */
93
	protected $mimeTypeDetector;
94
95
	/** @var View */
96
	protected $view;
97
98
	/** @var ViewInfoCache */
99
	protected $infoCache;
100
101
	/**
102
	 * OCSEndPoint constructor.
103
	 *
104
	 * @param string $appName
105
	 * @param IRequest $request
106
	 * @param IManager $activityManager
107
	 * @param Data $data
108
	 * @param GroupHelper $helper
109
	 * @param UserSettings $settings
110
	 * @param IURLGenerator $urlGenerator
111
	 * @param IUserSession $userSession
112
	 * @param IPreview $preview
113
	 * @param IMimeTypeDetector $mimeTypeDetector
114
	 * @param View $view
115
	 * @param ViewInfoCache $infoCache
116
	 */
117 66
	public function __construct($appName,
118
								IRequest $request,
119
								IManager $activityManager,
120
								Data $data,
121
								GroupHelper $helper,
122
								UserSettings $settings,
123
								IURLGenerator $urlGenerator,
124
								IUserSession $userSession,
125
								IPreview $preview,
126
								IMimeTypeDetector $mimeTypeDetector,
127
								View $view,
128
								ViewInfoCache $infoCache) {
129 66
		parent::__construct($appName, $request);
130 66
		$this->activityManager = $activityManager;
131 66
		$this->data = $data;
132 66
		$this->helper = $helper;
133 66
		$this->settings = $settings;
134 66
		$this->urlGenerator = $urlGenerator;
135 66
		$this->userSession = $userSession;
136 66
		$this->preview = $preview;
137 66
		$this->mimeTypeDetector = $mimeTypeDetector;
138 66
		$this->view = $view;
139 66
		$this->infoCache = $infoCache;
140 66
	}
141
142
	/**
143
	 * @param string $filter
144
	 * @param int $since
145
	 * @param int $limit
146
	 * @param bool $previews
147
	 * @param string $objectType
148
	 * @param int $objectId
149
	 * @param string $sort
150
	 * @throws InvalidFilterException when the filter is invalid
151
	 * @throws \OutOfBoundsException when no user is given
152
	 */
153 22
	protected function validateParameters($filter, $since, $limit, $previews, $objectType, $objectId, $sort) {
154 22
		$this->filter = is_string($filter) ? $filter : 'all';
155 22
		if ($this->filter !== $this->data->validateFilter($this->filter)) {
156 2
			throw new InvalidFilterException();
157
		}
158 20
		$this->since = (int) $since;
159 20
		$this->limit = (int) $limit;
160 20
		$this->loadPreviews = (bool) $previews;
161 20
		$this->objectType = (string) $objectType;
162 20
		$this->objectId = (int) $objectId;
163 20
		$this->sort = in_array($sort, ['asc', 'desc'], true) ? $sort : 'desc';
164
165 20
		if (($this->objectType !== '' && $this->objectId === 0) || ($this->objectType === '' && $this->objectId !== 0)) {
166
			// Only allowed together
167 2
			$this->objectType = '';
168 2
			$this->objectId = 0;
169
		}
170
171 20
		$user = $this->userSession->getUser();
172 20
		if ($user instanceof IUser) {
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
173 19
			$this->user = $user->getUID();
174
		} else {
175
			// No user logged in
176 1
			throw new \OutOfBoundsException();
177
		}
178 19
	}
179
180
	/**
181
	 * @NoAdminRequired
182
	 *
183
	 * @param int $since
184
	 * @param int $limit
185
	 * @param bool $previews
186
	 * @param string $object_type
187
	 * @param int $object_id
188
	 * @param string $sort
189
	 * @return DataResponse
190
	 */
191 2
	public function getDefault($since = 0, $limit = 50, $previews = false, $object_type = '', $object_id = 0, $sort = 'desc') {
192 2
		return $this->get('all', $since, $limit, $previews, $object_type, $object_id, $sort);
193
	}
194
195
	/**
196
	 * @NoAdminRequired
197
	 *
198
	 * @param string $filter
199
	 * @param int $since
200
	 * @param int $limit
201
	 * @param bool $previews
202
	 * @param string $object_type
203
	 * @param int $object_id
204
	 * @param string $sort
205
	 * @return DataResponse
206
	 */
207 2
	public function getFilter($filter, $since = 0, $limit = 50, $previews = false, $object_type = '', $object_id = 0, $sort = 'desc') {
208 2
		return $this->get($filter, $since, $limit, $previews, $object_type, $object_id, $sort);
209
	}
210
211
	/**
212
	 * @NoAdminRequired
213
	 *
214
	 * @return DataResponse
215
	 */
216 1
	public function listFilters() {
217 1
		$filters = $this->activityManager->getFilters();
218
219 View Code Duplication
		usort($filters, function(IFilter $a, IFilter $b) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220 1
			if ($a->getPriority() === $b->getPriority()) {
221 1
				return $a->getIdentifier() > $b->getIdentifier();
222
			}
223
224 1
			return $a->getPriority() > $b->getPriority();
225 1
		});
226
227 1
		$filters = array_map(function(IFilter $filter) {
228
			return [
229 1
				'id' => $filter->getIdentifier(),
230 1
				'name' => $filter->getName(),
231 1
				'icon' => $filter->getIcon(),
232
			];
233 1
		}, $filters);
234
235 1
		return new DataResponse($filters);
236
	}
237
238
	/**
239
	 * @param string $filter
240
	 * @param int $since
241
	 * @param int $limit
242
	 * @param bool $previews
243
	 * @param string $filterObjectType
244
	 * @param int $filterObjectId
245
	 * @param string $sort
246
	 * @return DataResponse
247
	 */
248 11
	protected function get($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort) {
249
		try {
250 11
			$this->validateParameters($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort);
251 2
		} catch (InvalidFilterException $e) {
252 1
			return new DataResponse(null, Http::STATUS_NOT_FOUND);
253 1
		} catch (\OutOfBoundsException $e) {
254 1
			return new DataResponse(null, Http::STATUS_FORBIDDEN);
255
		}
256
257
		try {
258 9
			$response = $this->data->get(
259 9
				$this->helper,
260 9
				$this->settings,
261 9
				$this->user,
262
263 9
				$this->since,
264 9
				$this->limit,
265 9
				$this->sort,
266
267 9
				$this->filter,
268 9
				$this->objectType,
269 9
				$this->objectId
270
			);
271 2
		} catch (\OutOfBoundsException $e) {
272
			// Invalid since argument
273 1
			return new DataResponse(null, Http::STATUS_FORBIDDEN);
274 1
		} catch (\BadMethodCallException $e) {
275
			// No activity settings enabled
276 1
			return new DataResponse(null, Http::STATUS_NO_CONTENT);
277
		}
278
279 7
		$headers = $this->generateHeaders($response['headers'], $response['has_more'], $response['data']);
280 7
		if (empty($response['data']) || $this->request->getHeader('If-None-Match') === $headers['ETag']) {
281 3
			return new DataResponse([], Http::STATUS_NOT_MODIFIED, $headers);
282
		}
283
284 4
		$preparedActivities = [];
285 4
		foreach ($response['data'] as $activity) {
286 4
			$activity['datetime'] = date(\DateTime::ATOM, $activity['timestamp']);
287 4
			unset($activity['timestamp']);
288
289 4
			if ($this->loadPreviews) {
290 3
				$activity['previews'] = [];
291 3
				if ($activity['object_type'] === 'files') {
292 2
					if (!empty($activity['objects']) && is_array($activity['objects'])) {
293 1
						foreach ($activity['objects'] as $objectId => $objectName) {
294 1
							if (((int) $objectId) === 0 || $objectName === '') {
295
								// No file, no preview
296 1
								continue;
297
							}
298
299 1
							$activity['previews'][] = $this->getPreview($activity['affecteduser'], (int) $objectId, $objectName);
300
						}
301 1
					} else if ($activity['object_id']) {
302 1
						$activity['previews'][] = $this->getPreview($activity['affecteduser'], (int) $activity['object_id'], $activity['object_name']);
303
					}
304
				}
305
			}
306
307 4
			unset($activity['affecteduser']);
308 4
			$preparedActivities[] = $activity;
309
		}
310
311 4
		return new DataResponse($preparedActivities, Http::STATUS_OK, $headers);
312
	}
313
314
	/**
315
	 * @param array $headers
316
	 * @param bool $hasMoreActivities
317
	 * @param array $data
318
	 * @return array
319
	 */
320 6
	protected function generateHeaders(array $headers, $hasMoreActivities, array $data) {
321 6
		if ($hasMoreActivities && isset($headers['X-Activity-Last-Given'])) {
322
			// Set the "Link" header for the next page
323
			$nextPageParameters = [
324 3
				'since' => $headers['X-Activity-Last-Given'],
325 3
				'limit' => $this->limit,
326 3
				'sort' => $this->sort,
327
			];
328 3
			if ($this->objectType && $this->objectId) {
329 1
				$nextPageParameters['object_type'] = $this->objectType;
330 1
				$nextPageParameters['object_id'] = $this->objectId;
331
			}
332 3
			if ($this->request->getParam('format') !== null) {
333 1
				$nextPageParameters['format'] = $this->request->getParam('format');
334
			}
335
336 3
			$nextPage = $this->request->getServerProtocol(); # http
337 3
			$nextPage .= '://' . $this->request->getServerHost(); # localhost
338 3
			$nextPage .= $this->request->getScriptName(); # /ocs/v2.php
339 3
			$nextPage .= $this->request->getPathInfo(); # /apps/activity/api/v2/activity
340 3
			$nextPage .= '?' . http_build_query($nextPageParameters);
341 3
			$headers['Link'] = '<' . $nextPage . '>; rel="next"';
342
		}
343
344 6
		$ids = [];
345 6
		foreach ($data as $activity) {
346 2
			$ids[] = $activity['activity_id'];
347
		}
348 6
		$headers['ETag'] = md5(json_encode($ids));
349
350 6
		return $headers;
351
	}
352
353
	/**
354
	 * @param string $owner
355
	 * @param int $fileId
356
	 * @param string $filePath
357
	 * @return array
358
	 */
359 7
	protected function getPreview($owner, $fileId, $filePath) {
360 7
		$info = $this->infoCache->getInfoById($owner, $fileId, $filePath);
361
362 7
		if (!$info['exists'] || $info['view'] !== '') {
363 3
			return $this->getPreviewFromPath($filePath, $info);
364
		}
365
366
		$preview = [
367 4
			'link'			=> $this->getPreviewLink($info['path'], $info['is_dir'], $info['view']),
368 4
			'source'		=> '',
369
			'isMimeTypeIcon' => true,
370
		];
371
372
		// show a preview image if the file still exists
373 4
		if ($info['is_dir']) {
374 1
			$preview['source'] = $this->getPreviewPathFromMimeType('dir');
375
		} else {
376 3
			$this->view->chroot('/' . $owner . '/files');
377 3
			$fileInfo = $this->view->getFileInfo($info['path']);
378 3
			if (!($fileInfo instanceof FileInfo)) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\FileInfo does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
379 1
				$pathPreview = $this->getPreviewFromPath($filePath, $info);
380 1
				$preview['source'] = $pathPreview['source'];
381 2
			} else if ($this->preview->isAvailable($fileInfo)) {
382 1
				$preview['isMimeTypeIcon'] = false;
383 1
				$preview['source'] = $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreview', [
384 1
					'file' => $info['path'],
385 1
					'c' => $this->view->getETag($info['path']),
386 1
					'x' => 150,
387 1
					'y' => 150,
388
				]);
389
			} else {
390 1
				$preview['source'] = $this->getPreviewPathFromMimeType($fileInfo->getMimetype());
391
			}
392
		}
393
394 4
		return $preview;
395
	}
396
397
	/**
398
	 * @param string $filePath
399
	 * @param array $info
400
	 * @return array
401
	 */
402 3
	protected function getPreviewFromPath($filePath, $info) {
403 3
		$mimeType = $this->mimeTypeDetector->detectPath($filePath);
404
		$preview = [
405 3
			'link'			=> $this->getPreviewLink($info['path'], $info['is_dir'], $info['view']),
406 3
			'source'		=> $this->getPreviewPathFromMimeType($mimeType),
407
			'isMimeTypeIcon' => true,
408
		];
409
410 3
		return $preview;
411
	}
412
413
	/**
414
	 * @param string $mimeType
415
	 * @return string
416
	 */
417 3
	protected function getPreviewPathFromMimeType($mimeType) {
418 3
		$mimeTypeIcon = $this->mimeTypeDetector->mimeTypeIcon($mimeType);
419 3
		if (substr($mimeTypeIcon, -4) === '.png') {
420 1
			$mimeTypeIcon = substr($mimeTypeIcon, 0, -4) . '.svg';
421
		}
422
423 3
		return $this->urlGenerator->getAbsoluteURL($mimeTypeIcon);
424
	}
425
426
	/**
427
	 * @param string $path
428
	 * @param bool $isDir
429
	 * @param string $view
430
	 * @return string
431
	 */
432 6
	protected function getPreviewLink($path, $isDir, $view) {
433
		$params = [
434 6
			'dir' => $path,
435
		];
436 6
		if (!$isDir) {
437 3
			$params['dir'] = (substr_count($path, '/') === 1) ? '/' : dirname($path);
438 3
			$params['scrollto'] = basename($path);
439
		}
440 6
		if ($view !== '') {
441 2
			$params['view'] = $view;
442
		}
443 6
		return $this->urlGenerator->linkToRouteAbsolute('files.view.index', $params);
444
	}
445
}
446