Completed
Pull Request — master (#300)
by Joas
06:17
created

APIv2   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 3
dl 0
loc 384
ccs 159
cts 159
cp 1
rs 6.96
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 24 1
B validateParameters() 0 26 9
A getDefault() 0 3 1
A getFilter() 0 3 1
A listFilters() 0 23 2
C get() 0 67 16
B generateHeaders() 0 32 7
B getPreview() 0 42 7
A getPreviewFromPath() 0 11 3
A getPreviewPathFromMimeType() 0 8 2
A getPreviewLink() 0 13 4

How to fix   Complexity   

Complex Class

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
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 *
6
 * @author Joas Schilling <[email protected]>
7
 *
8
 * @license AGPL-3.0
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OCA\Activity\Controller;
25
26
27
use OC\Files\View;
28
use OCA\Activity\Data;
29
use OCA\Activity\Exception\InvalidFilterException;
30
use OCA\Activity\GroupHelper;
31
use OCA\Activity\UserSettings;
32
use OCA\Activity\ViewInfoCache;
33
use OCP\Activity\IFilter;
34
use OCP\Activity\IManager;
35
use OCP\AppFramework\Http;
36
use OCP\AppFramework\Http\DataResponse;
37
use OCP\AppFramework\OCSController;
38
use OCP\Files\FileInfo;
39
use OCP\Files\IMimeTypeDetector;
40
use OCP\IPreview;
41
use OCP\IRequest;
42
use OCP\IURLGenerator;
43
use OCP\IUser;
44
use OCP\IUserSession;
45
46
class APIv2 extends OCSController {
47
48
	/** @var string */
49
	protected $filter;
50
51
	/** @var int */
52
	protected $since;
53
54
	/** @var int */
55
	protected $limit;
56
57
	/** @var string */
58
	protected $sort;
59
60
	/** @var string */
61
	protected $objectType;
62
63
	/** @var int */
64
	protected $objectId;
65
66
	/** @var string */
67
	protected $user;
68
69
	/** @var bool */
70
	protected $loadPreviews;
71
72
	/** @var IManager */
73
	protected $activityManager;
74
75
	/** @var Data */
76
	protected $data;
77
78
	/** @var GroupHelper */
79
	protected $helper;
80
81
	/** @var UserSettings */
82
	protected $settings;
83
84
	/** @var IURLGenerator */
85
	protected $urlGenerator;
86
87
	/** @var IUserSession */
88
	protected $userSession;
89
90
	/** @var IPreview */
91
	protected $preview;
92
93
	/** @var IMimeTypeDetector */
94
	protected $mimeTypeDetector;
95
96
	/** @var View */
97
	protected $view;
98
99
	/** @var ViewInfoCache */
100
	protected $infoCache;
101
102
	/**
103
	 * OCSEndPoint constructor.
104
	 *
105
	 * @param string $appName
106
	 * @param IRequest $request
107
	 * @param IManager $activityManager
108
	 * @param Data $data
109
	 * @param GroupHelper $helper
110
	 * @param UserSettings $settings
111
	 * @param IURLGenerator $urlGenerator
112
	 * @param IUserSession $userSession
113
	 * @param IPreview $preview
114
	 * @param IMimeTypeDetector $mimeTypeDetector
115
	 * @param View $view
116
	 * @param ViewInfoCache $infoCache
117
	 */
118 66
	public function __construct($appName,
119
								IRequest $request,
120
								IManager $activityManager,
121
								Data $data,
122
								GroupHelper $helper,
123
								UserSettings $settings,
124
								IURLGenerator $urlGenerator,
125
								IUserSession $userSession,
126
								IPreview $preview,
127
								IMimeTypeDetector $mimeTypeDetector,
128
								View $view,
129
								ViewInfoCache $infoCache) {
130 66
		parent::__construct($appName, $request);
131 66
		$this->activityManager = $activityManager;
132 66
		$this->data = $data;
133 66
		$this->helper = $helper;
134 66
		$this->settings = $settings;
135 66
		$this->urlGenerator = $urlGenerator;
136 66
		$this->userSession = $userSession;
137 66
		$this->preview = $preview;
138 66
		$this->mimeTypeDetector = $mimeTypeDetector;
139 66
		$this->view = $view;
140 66
		$this->infoCache = $infoCache;
141 66
	}
142
143
	/**
144
	 * @param string $filter
145
	 * @param int $since
146
	 * @param int $limit
147
	 * @param bool $previews
148
	 * @param string $objectType
149
	 * @param int $objectId
150
	 * @param string $sort
151
	 * @throws InvalidFilterException when the filter is invalid
152
	 * @throws \OutOfBoundsException when no user is given
153
	 */
154 22
	protected function validateParameters($filter, $since, $limit, $previews, $objectType, $objectId, $sort) {
155 22
		$this->filter = \is_string($filter) ? $filter : 'all';
156 22
		if ($this->filter !== $this->data->validateFilter($this->filter)) {
157 2
			throw new InvalidFilterException('Invalid filter');
158
		}
159 20
		$this->since = (int) $since;
160 20
		$this->limit = (int) $limit;
161 20
		$this->loadPreviews = (bool) $previews;
162 20
		$this->objectType = (string) $objectType;
163 20
		$this->objectId = (int) $objectId;
164 20
		$this->sort = \in_array($sort, ['asc', 'desc'], true) ? $sort : 'desc';
165
166 20
		if (($this->objectType !== '' && $this->objectId === 0) || ($this->objectType === '' && $this->objectId !== 0)) {
167
			// Only allowed together
168 2
			$this->objectType = '';
169 2
			$this->objectId = 0;
170
		}
171
172 20
		$user = $this->userSession->getUser();
173 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...
174 19
			$this->user = $user->getUID();
175
		} else {
176
			// No user logged in
177 1
			throw new \OutOfBoundsException('Not logged in');
178
		}
179 19
	}
180
181
	/**
182
	 * @NoAdminRequired
183
	 *
184
	 * @param int $since
185
	 * @param int $limit
186
	 * @param bool $previews
187
	 * @param string $object_type
188
	 * @param int $object_id
189
	 * @param string $sort
190
	 * @return DataResponse
191
	 */
192 2
	public function getDefault($since = 0, $limit = 50, $previews = false, $object_type = '', $object_id = 0, $sort = 'desc'): DataResponse {
193 2
		return $this->get('all', $since, $limit, $previews, $object_type, $object_id, $sort);
194
	}
195
196
	/**
197
	 * @NoAdminRequired
198
	 *
199
	 * @param string $filter
200
	 * @param int $since
201
	 * @param int $limit
202
	 * @param bool $previews
203
	 * @param string $object_type
204
	 * @param int $object_id
205
	 * @param string $sort
206
	 * @return DataResponse
207
	 */
208 2
	public function getFilter($filter, $since = 0, $limit = 50, $previews = false, $object_type = '', $object_id = 0, $sort = 'desc'): DataResponse {
209 2
		return $this->get($filter, $since, $limit, $previews, $object_type, $object_id, $sort);
210
	}
211
212
	/**
213
	 * @NoAdminRequired
214
	 *
215
	 * @return DataResponse
216
	 */
217 1
	public function listFilters(): DataResponse {
218 1
		$filters = $this->activityManager->getFilters();
219
220 1
		$filters = array_map(function(IFilter $filter) {
221
			return [
222 1
				'id' => $filter->getIdentifier(),
223 1
				'name' => $filter->getName(),
224 1
				'icon' => $filter->getIcon(),
225 1
				'priority' => $filter->getPriority(),
226
			];
227 1
		}, $filters);
228
229
		// php 5.6 has problems with usort and objects
230 1
		usort($filters, function(array $a, array $b) {
231 1
			if ($a['priority'] === $b['priority']) {
232 1
				return $a['id'] > $b['id'];
233
			}
234
235 1
			return $a['priority'] > $b['priority'];
236 1
		});
237
238 1
		return new DataResponse($filters);
239
	}
240
241
	/**
242
	 * @param string $filter
243
	 * @param int $since
244
	 * @param int $limit
245
	 * @param bool $previews
246
	 * @param string $filterObjectType
247
	 * @param int $filterObjectId
248
	 * @param string $sort
249
	 * @return DataResponse
250
	 */
251 11
	protected function get($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort): DataResponse {
252
		try {
253 11
			$this->validateParameters($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort);
254 2
		} catch (InvalidFilterException $e) {
255 1
			return new DataResponse(null, Http::STATUS_NOT_FOUND);
256 1
		} catch (\OutOfBoundsException $e) {
257 1
			return new DataResponse(null, Http::STATUS_FORBIDDEN);
258
		}
259
260 9
		$this->activityManager->setRequirePNG($this->request->isUserAgent([IRequest::USER_AGENT_CLIENT_IOS]));
261
		try {
262 9
			$response = $this->data->get(
263 9
				$this->helper,
264 9
				$this->settings,
265 9
				$this->user,
266
267 9
				$this->since,
268 9
				$this->limit,
269 9
				$this->sort,
270
271 9
				$this->filter,
272 9
				$this->objectType,
273 9
				$this->objectId
274
			);
275 2
		} catch (\OutOfBoundsException $e) {
276
			// Invalid since argument
277 1
			return new DataResponse(null, Http::STATUS_FORBIDDEN);
278 1
		} catch (\BadMethodCallException $e) {
279
			// No activity settings enabled
280 1
			return new DataResponse(null, Http::STATUS_NO_CONTENT);
281
		}
282 7
		$this->activityManager->setRequirePNG(false);
283
284 7
		$headers = $this->generateHeaders($response['headers'], $response['has_more'], $response['data']);
285 7
		if (empty($response['data']) || $this->request->getHeader('If-None-Match') === $headers['ETag']) {
286 3
			return new DataResponse([], Http::STATUS_NOT_MODIFIED, $headers);
287
		}
288
289 4
		$preparedActivities = [];
290 4
		foreach ($response['data'] as $activity) {
291 4
			$activity['datetime'] = date(\DateTime::ATOM, $activity['timestamp']);
292 4
			unset($activity['timestamp']);
293
294 4
			if ($this->loadPreviews) {
295 3
				$activity['previews'] = [];
296 3
				if ($activity['object_type'] === 'files') {
297 2
					if (!empty($activity['objects']) && \is_array($activity['objects'])) {
298 1
						foreach ($activity['objects'] as $objectId => $objectName) {
299 1
							if (((int) $objectId) === 0 || $objectName === '') {
300
								// No file, no preview
301 1
								continue;
302
							}
303
304 1
							$activity['previews'][] = $this->getPreview($activity['affecteduser'], (int) $objectId, $objectName);
305
						}
306 1
					} else if ($activity['object_id']) {
307 1
						$activity['previews'][] = $this->getPreview($activity['affecteduser'], (int) $activity['object_id'], $activity['object_name']);
308
					}
309
				}
310
			}
311
312 4
			unset($activity['affecteduser']);
313 4
			$preparedActivities[] = $activity;
314
		}
315
316 4
		return new DataResponse($preparedActivities, Http::STATUS_OK, $headers);
317
	}
318
319 6
	protected function generateHeaders(array $headers, bool $hasMoreActivities, array $data): array {
320 6
		if ($hasMoreActivities && isset($headers['X-Activity-Last-Given'])) {
321
			// Set the "Link" header for the next page
322
			$nextPageParameters = [
323 3
				'since' => $headers['X-Activity-Last-Given'],
324 3
				'limit' => $this->limit,
325 3
				'sort' => $this->sort,
326
			];
327 3
			if ($this->objectType && $this->objectId) {
328 1
				$nextPageParameters['object_type'] = $this->objectType;
329 1
				$nextPageParameters['object_id'] = $this->objectId;
330
			}
331 3
			if ($this->request->getParam('format') !== null) {
332 1
				$nextPageParameters['format'] = $this->request->getParam('format');
333
			}
334
335 3
			$nextPage = $this->request->getServerProtocol(); # http
336 3
			$nextPage .= '://' . $this->request->getServerHost(); # localhost
337 3
			$nextPage .= $this->request->getScriptName(); # /ocs/v2.php
338 3
			$nextPage .= $this->request->getPathInfo(); # /apps/activity/api/v2/activity
339 3
			$nextPage .= '?' . http_build_query($nextPageParameters);
340 3
			$headers['Link'] = '<' . $nextPage . '>; rel="next"';
341
		}
342
343 6
		$ids = [];
344 6
		foreach ($data as $activity) {
345 2
			$ids[] = $activity['activity_id'];
346
		}
347 6
		$headers['ETag'] = md5(json_encode($ids));
348
349 6
		return $headers;
350
	}
351
352 7
	protected function getPreview(string $owner, int $fileId, string $filePath): array {
353 7
		$info = $this->infoCache->getInfoById($owner, $fileId, $filePath);
354
355 7
		if (!$info['exists'] || $info['view'] !== '') {
356 3
			return $this->getPreviewFromPath($fileId, $filePath, $info);
357
		}
358
359
		$preview = [
360 4
			'link'			=> $this->getPreviewLink($info['path'], $info['is_dir'], $info['view']),
361 4
			'source'		=> '',
362 4
			'mimeType'		=> 'application/octet-stream',
363
			'isMimeTypeIcon' => true,
364 4
			'fileId'		=> $fileId,
365 4
			'view'			=> $info['view'] ?: 'files',
366
		];
367
368
		// show a preview image if the file still exists
369 4
		if ($info['is_dir']) {
370 1
			$preview['source'] = $this->getPreviewPathFromMimeType('dir');
371 1
			$preview['mimeType'] = 'dir';
372
		} else {
373 3
			$this->view->chroot('/' . $owner . '/files');
374 3
			$fileInfo = $this->view->getFileInfo($info['path']);
375 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...
376 1
				$preview = $this->getPreviewFromPath($fileId, $filePath, $info);
377 2
			} else if ($this->preview->isAvailable($fileInfo)) {
378 1
				$preview['source'] = $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreview', [
379 1
					'file' => $info['path'],
380 1
					'c' => $this->view->getETag($info['path']),
381 1
					'x' => 150,
382 1
					'y' => 150,
383
				]);
384 1
				$preview['mimeType'] = $fileInfo->getMimetype();
385 1
				$preview['isMimeTypeIcon'] = false;
386
			} else {
387 1
				$preview['source'] = $this->getPreviewPathFromMimeType($fileInfo->getMimetype());
388 1
				$preview['mimeType'] = $fileInfo->getMimetype();
389
			}
390
		}
391
392 4
		return $preview;
393
	}
394
395 3
	protected function getPreviewFromPath(int $fileId, string $filePath, array $info): array {
396 3
		$mimeType = $info['is_dir'] ? 'dir' : $this->mimeTypeDetector->detectPath($filePath);
397
		return [
398 3
			'link'			=> $this->getPreviewLink($info['path'], $info['is_dir'], $info['view']),
399 3
			'source'		=> $this->getPreviewPathFromMimeType($mimeType),
400 3
			'mimeType'		=> $mimeType,
401
			'isMimeTypeIcon' => true,
402 3
			'fileId'		=> $fileId,
403 3
			'view'			=> $info['view'] ?: 'files',
404
		];
405
	}
406
407 3
	protected function getPreviewPathFromMimeType(string $mimeType): string {
408 3
		$mimeTypeIcon = $this->mimeTypeDetector->mimeTypeIcon($mimeType);
409 3
		if (substr($mimeTypeIcon, -4) === '.png') {
410 1
			$mimeTypeIcon = substr($mimeTypeIcon, 0, -4) . '.svg';
411
		}
412
413 3
		return $this->urlGenerator->getAbsoluteURL($mimeTypeIcon);
414
	}
415
416 6
	protected function getPreviewLink(string $path, bool $isDir, string $view): string {
417
		$params = [
418 6
			'dir' => $path,
419
		];
420 6
		if (!$isDir) {
421 3
			$params['dir'] = (substr_count($path, '/') === 1) ? '/' : \dirname($path);
422 3
			$params['scrollto'] = basename($path);
423
		}
424 6
		if ($view !== '') {
425 2
			$params['view'] = $view;
426
		}
427 6
		return $this->urlGenerator->linkToRouteAbsolute('files.view.index', $params);
428
	}
429
}
430