Manager   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 481
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 239
dl 0
loc 481
rs 2.96
c 0
b 0
f 0
wmc 68

25 Methods

Rating   Name   Duplication   Size   Complexity  
A invalidateAccessCacheForUser() 0 7 2
A invalidateAccessCacheForProvider() 0 6 1
A invalidateAccessCacheForCollectionByUser() 0 8 2
A getResourcesByCollectionForUser() 0 25 4
A getCollection() 0 14 2
A invalidateAccessCacheForResourceByUser() 0 11 3
A checkAccessCacheForUserByResource() 0 19 3
A invalidateAccessCacheForCollection() 0 6 1
A cacheAccessForCollection() 0 13 3
A invalidateAccessCacheForResource() 0 10 2
A getResourceForUser() 0 30 5
A canAccessResource() 0 21 6
A getResourceRichObject() 0 11 4
A invalidateAccessCacheForProviderByUser() 0 8 2
A newCollection() 0 9 1
A cacheAccessForResource() 0 14 3
A createResource() 0 2 1
A registerResourceProvider() 0 3 1
A canAccessCollection() 0 19 4
A getCollectionForUser() 0 28 5
A __construct() 0 4 1
A invalidateAccessCacheForAllCollections() 0 6 1
A checkAccessCacheForUserByCollection() 0 18 3
A getType() 0 2 1
B searchCollections() 0 41 7

How to fix   Complexity   

Complex Class

Complex classes like Manager 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.

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 Manager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2018 Joas Schilling <[email protected]>
7
 *
8
 * @author Daniel Kesselberg <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Julius Härtl <[email protected]>
11
 *
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
namespace OC\Collaboration\Resources;
29
30
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
31
use OCP\Collaboration\Resources\CollectionException;
32
use OCP\Collaboration\Resources\ICollection;
33
use OCP\Collaboration\Resources\IManager;
34
use OCP\Collaboration\Resources\IProvider;
35
use OCP\Collaboration\Resources\IProviderManager;
36
use OCP\Collaboration\Resources\IResource;
37
use OCP\Collaboration\Resources\ResourceException;
38
use OCP\DB\QueryBuilder\IQueryBuilder;
39
use OCP\IDBConnection;
40
use OCP\IUser;
41
use Psr\Log\LoggerInterface;
42
43
class Manager implements IManager {
44
	public const TABLE_COLLECTIONS = 'collres_collections';
45
	public const TABLE_RESOURCES = 'collres_resources';
46
	public const TABLE_ACCESS_CACHE = 'collres_accesscache';
47
48
	/** @var IDBConnection */
49
	protected $connection;
50
	/** @var IProviderManager */
51
	protected $providerManager;
52
	/** @var LoggerInterface */
53
	protected $logger;
54
55
	/** @var string[] */
56
	protected $providers = [];
57
58
59
	public function __construct(IDBConnection $connection, IProviderManager $providerManager, LoggerInterface $logger) {
60
		$this->connection = $connection;
61
		$this->providerManager = $providerManager;
62
		$this->logger = $logger;
63
	}
64
65
	/**
66
	 * @param int $id
67
	 * @return ICollection
68
	 * @throws CollectionException when the collection could not be found
69
	 * @since 16.0.0
70
	 */
71
	public function getCollection(int $id): ICollection {
72
		$query = $this->connection->getQueryBuilder();
73
		$query->select('*')
74
			->from(self::TABLE_COLLECTIONS)
75
			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
76
		$result = $query->execute();
77
		$row = $result->fetch();
78
		$result->closeCursor();
79
80
		if (!$row) {
81
			throw new CollectionException('Collection not found');
82
		}
83
84
		return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name']);
85
	}
86
87
	/**
88
	 * @param int $id
89
	 * @param IUser|null $user
90
	 * @return ICollection
91
	 * @throws CollectionException when the collection could not be found
92
	 * @since 16.0.0
93
	 */
94
	public function getCollectionForUser(int $id, ?IUser $user): ICollection {
95
		$query = $this->connection->getQueryBuilder();
96
		$userId = $user instanceof IUser ? $user->getUID() : '';
97
98
		$query->select('*')
99
			->from(self::TABLE_COLLECTIONS, 'c')
100
			->leftJoin(
101
				'c', self::TABLE_ACCESS_CACHE, 'a',
102
				$query->expr()->andX(
103
					$query->expr()->eq('c.id', 'a.collection_id'),
104
					$query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
105
				)
106
			)
107
			->where($query->expr()->eq('c.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
108
		$result = $query->execute();
109
		$row = $result->fetch();
110
		$result->closeCursor();
111
112
		if (!$row) {
113
			throw new CollectionException('Collection not found');
114
		}
115
116
		$access = $row['access'] === null ? null : (bool) $row['access'];
117
		if ($user instanceof IUser) {
118
			return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access);
119
		}
120
121
		return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access);
122
	}
123
124
	/**
125
	 * @param IUser $user
126
	 * @param string $filter
127
	 * @param int $limit
128
	 * @param int $start
129
	 * @return ICollection[]
130
	 * @since 16.0.0
131
	 */
132
	public function searchCollections(IUser $user, string $filter, int $limit = 50, int $start = 0): array {
133
		$query = $this->connection->getQueryBuilder();
134
		$userId = $user->getUID();
135
136
		$query->select('c.*', 'a.access')
137
			->from(self::TABLE_COLLECTIONS, 'c')
138
			->leftJoin(
139
				'c', self::TABLE_ACCESS_CACHE, 'a',
140
				$query->expr()->andX(
141
					$query->expr()->eq('c.id', 'a.collection_id'),
142
					$query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
143
				)
144
			)
145
			->where($query->expr()->eq('a.access', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
146
			->orderBy('c.id')
147
			->setMaxResults($limit)
148
			->setFirstResult($start);
149
150
		if ($filter !== '') {
151
			$query->andWhere($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%')));
152
		}
153
154
		$result = $query->execute();
155
		$collections = [];
156
157
		$foundResults = 0;
158
		while ($row = $result->fetch()) {
159
			$foundResults++;
160
			$access = $row['access'] === null ? null : (bool) $row['access'];
161
			$collection = new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access);
162
			if ($collection->canAccess($user)) {
163
				$collections[] = $collection;
164
			}
165
		}
166
		$result->closeCursor();
167
168
		if (empty($collections) && $foundResults === $limit) {
169
			return $this->searchCollections($user, $filter, $limit, $start + $limit);
170
		}
171
172
		return $collections;
173
	}
174
175
	/**
176
	 * @param string $name
177
	 * @return ICollection
178
	 * @since 16.0.0
179
	 */
180
	public function newCollection(string $name): ICollection {
181
		$query = $this->connection->getQueryBuilder();
182
		$query->insert(self::TABLE_COLLECTIONS)
183
			->values([
184
				'name' => $query->createNamedParameter($name),
185
			]);
186
		$query->execute();
187
188
		return new Collection($this, $this->connection, $query->getLastInsertId(), $name);
189
	}
190
191
	/**
192
	 * @param string $type
193
	 * @param string $id
194
	 * @return IResource
195
	 * @since 16.0.0
196
	 */
197
	public function createResource(string $type, string $id): IResource {
198
		return new Resource($this, $this->connection, $type, $id);
199
	}
200
201
	/**
202
	 * @param string $type
203
	 * @param string $id
204
	 * @param IUser|null $user
205
	 * @return IResource
206
	 * @throws ResourceException
207
	 * @since 16.0.0
208
	 */
209
	public function getResourceForUser(string $type, string $id, ?IUser $user): IResource {
210
		$query = $this->connection->getQueryBuilder();
211
		$userId = $user instanceof IUser ? $user->getUID() : '';
212
213
		$query->select('r.*', 'a.access')
214
			->from(self::TABLE_RESOURCES, 'r')
215
			->leftJoin(
216
				'r', self::TABLE_ACCESS_CACHE, 'a',
217
				$query->expr()->andX(
218
					$query->expr()->eq('r.resource_id', 'a.resource_id'),
219
					$query->expr()->eq('r.resource_type', 'a.resource_type'),
220
					$query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
221
				)
222
			)
223
			->where($query->expr()->eq('r.resource_type', $query->createNamedParameter($type, IQueryBuilder::PARAM_STR)))
224
			->andWhere($query->expr()->eq('r.resource_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)));
225
		$result = $query->execute();
226
		$row = $result->fetch();
227
		$result->closeCursor();
228
229
		if (!$row) {
230
			throw new ResourceException('Resource not found');
231
		}
232
233
		$access = $row['access'] === null ? null : (bool) $row['access'];
234
		if ($user instanceof IUser) {
235
			return new Resource($this, $this->connection, $type, $id, $user, $access);
236
		}
237
238
		return new Resource($this, $this->connection, $type, $id, null, $access);
239
	}
240
241
	/**
242
	 * @param ICollection $collection
243
	 * @param IUser|null $user
244
	 * @return IResource[]
245
	 * @since 16.0.0
246
	 */
247
	public function getResourcesByCollectionForUser(ICollection $collection, ?IUser $user): array {
248
		$query = $this->connection->getQueryBuilder();
249
		$userId = $user instanceof IUser ? $user->getUID() : '';
250
251
		$query->select('r.*', 'a.access')
252
			->from(self::TABLE_RESOURCES, 'r')
253
			->leftJoin(
254
				'r', self::TABLE_ACCESS_CACHE, 'a',
255
				$query->expr()->andX(
256
					$query->expr()->eq('r.resource_id', 'a.resource_id'),
257
					$query->expr()->eq('r.resource_type', 'a.resource_type'),
258
					$query->expr()->eq('a.user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
259
				)
260
			)
261
			->where($query->expr()->eq('r.collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)));
262
263
		$resources = [];
264
		$result = $query->execute();
265
		while ($row = $result->fetch()) {
266
			$access = $row['access'] === null ? null : (bool) $row['access'];
267
			$resources[] = new Resource($this, $this->connection, $row['resource_type'], $row['resource_id'], $user, $access);
268
		}
269
		$result->closeCursor();
270
271
		return $resources;
272
	}
273
274
	/**
275
	 * Get the rich object data of a resource
276
	 *
277
	 * @param IResource $resource
278
	 * @return array
279
	 * @since 16.0.0
280
	 */
281
	public function getResourceRichObject(IResource $resource): array {
282
		foreach ($this->providerManager->getResourceProviders() as $provider) {
283
			if ($provider->getType() === $resource->getType()) {
284
				try {
285
					return $provider->getResourceRichObject($resource);
286
				} catch (ResourceException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
287
				}
288
			}
289
		}
290
291
		return [];
292
	}
293
294
	/**
295
	 * Can a user/guest access the collection
296
	 *
297
	 * @param IResource $resource
298
	 * @param IUser|null $user
299
	 * @return bool
300
	 * @since 16.0.0
301
	 */
302
	public function canAccessResource(IResource $resource, ?IUser $user): bool {
303
		$access = $this->checkAccessCacheForUserByResource($resource, $user);
304
		if (\is_bool($access)) {
305
			return $access;
306
		}
307
308
		$access = false;
309
		foreach ($this->providerManager->getResourceProviders() as $provider) {
310
			if ($provider->getType() === $resource->getType()) {
311
				try {
312
					if ($provider->canAccessResource($resource, $user)) {
313
						$access = true;
314
						break;
315
					}
316
				} catch (ResourceException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
317
				}
318
			}
319
		}
320
321
		$this->cacheAccessForResource($resource, $user, $access);
322
		return $access;
323
	}
324
325
	/**
326
	 * Can a user/guest access the collection
327
	 *
328
	 * @param ICollection $collection
329
	 * @param IUser|null $user
330
	 * @return bool
331
	 * @since 16.0.0
332
	 */
333
	public function canAccessCollection(ICollection $collection, ?IUser $user): bool {
334
		$access = $this->checkAccessCacheForUserByCollection($collection, $user);
335
		if (\is_bool($access)) {
336
			return $access;
337
		}
338
339
		$access = null;
340
		// Access is granted when a user can access all resources
341
		foreach ($collection->getResources() as $resource) {
342
			if (!$resource->canAccess($user)) {
343
				$access = false;
344
				break;
345
			}
346
347
			$access = true;
348
		}
349
350
		$this->cacheAccessForCollection($collection, $user, $access);
351
		return $access;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $access could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
352
	}
353
354
	protected function checkAccessCacheForUserByResource(IResource $resource, ?IUser $user): ?bool {
355
		$query = $this->connection->getQueryBuilder();
356
		$userId = $user instanceof IUser ? $user->getUID() : '';
357
358
		$query->select('access')
359
			->from(self::TABLE_ACCESS_CACHE)
360
			->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId(), IQueryBuilder::PARAM_STR)))
361
			->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)))
362
			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
363
			->setMaxResults(1);
364
365
		$hasAccess = null;
366
		$result = $query->execute();
367
		if ($row = $result->fetch()) {
368
			$hasAccess = (bool) $row['access'];
369
		}
370
		$result->closeCursor();
371
372
		return $hasAccess;
373
	}
374
375
	protected function checkAccessCacheForUserByCollection(ICollection $collection, ?IUser $user): ?bool {
376
		$query = $this->connection->getQueryBuilder();
377
		$userId = $user instanceof IUser ? $user->getUID() : '';
378
379
		$query->select('access')
380
			->from(self::TABLE_ACCESS_CACHE)
381
			->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId(), IQueryBuilder::PARAM_INT)))
382
			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
383
			->setMaxResults(1);
384
385
		$hasAccess = null;
386
		$result = $query->execute();
387
		if ($row = $result->fetch()) {
388
			$hasAccess = (bool) $row['access'];
389
		}
390
		$result->closeCursor();
391
392
		return $hasAccess;
393
	}
394
395
	public function cacheAccessForResource(IResource $resource, ?IUser $user, bool $access): void {
396
		$query = $this->connection->getQueryBuilder();
397
		$userId = $user instanceof IUser ? $user->getUID() : '';
398
399
		$query->insert(self::TABLE_ACCESS_CACHE)
400
			->values([
401
				'user_id' => $query->createNamedParameter($userId),
402
				'resource_id' => $query->createNamedParameter($resource->getId()),
403
				'resource_type' => $query->createNamedParameter($resource->getType()),
404
				'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
405
			]);
406
		try {
407
			$query->execute();
408
		} catch (UniqueConstraintViolationException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
409
		}
410
	}
411
412
	public function cacheAccessForCollection(ICollection $collection, ?IUser $user, bool $access): void {
413
		$query = $this->connection->getQueryBuilder();
414
		$userId = $user instanceof IUser ? $user->getUID() : '';
415
416
		$query->insert(self::TABLE_ACCESS_CACHE)
417
			->values([
418
				'user_id' => $query->createNamedParameter($userId),
419
				'collection_id' => $query->createNamedParameter($collection->getId()),
420
				'access' => $query->createNamedParameter($access, IQueryBuilder::PARAM_BOOL),
421
			]);
422
		try {
423
			$query->execute();
424
		} catch (UniqueConstraintViolationException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
425
		}
426
	}
427
428
	public function invalidateAccessCacheForUser(?IUser $user): void {
429
		$query = $this->connection->getQueryBuilder();
430
		$userId = $user instanceof IUser ? $user->getUID() : '';
431
432
		$query->delete(self::TABLE_ACCESS_CACHE)
433
			->where($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
434
		$query->execute();
435
	}
436
437
	public function invalidateAccessCacheForResource(IResource $resource): void {
438
		$query = $this->connection->getQueryBuilder();
439
440
		$query->delete(self::TABLE_ACCESS_CACHE)
441
			->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
442
			->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType(), IQueryBuilder::PARAM_STR)));
443
		$query->execute();
444
445
		foreach ($resource->getCollections() as $collection) {
446
			$this->invalidateAccessCacheForCollection($collection);
447
		}
448
	}
449
450
	public function invalidateAccessCacheForAllCollections(): void {
451
		$query = $this->connection->getQueryBuilder();
452
453
		$query->delete(self::TABLE_ACCESS_CACHE)
454
			->where($query->expr()->neq('collection_id', $query->createNamedParameter(0)));
455
		$query->execute();
456
	}
457
458
	public function invalidateAccessCacheForCollection(ICollection $collection): void {
459
		$query = $this->connection->getQueryBuilder();
460
461
		$query->delete(self::TABLE_ACCESS_CACHE)
462
			->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())));
463
		$query->execute();
464
	}
465
466
	public function invalidateAccessCacheForProvider(IProvider $provider): void {
467
		$query = $this->connection->getQueryBuilder();
468
469
		$query->delete(self::TABLE_ACCESS_CACHE)
470
			->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)));
471
		$query->execute();
472
	}
473
474
	public function invalidateAccessCacheForResourceByUser(IResource $resource, ?IUser $user): void {
475
		$query = $this->connection->getQueryBuilder();
476
		$userId = $user instanceof IUser ? $user->getUID() : '';
477
478
		$query->delete(self::TABLE_ACCESS_CACHE)
479
			->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
480
			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
481
		$query->execute();
482
483
		foreach ($resource->getCollections() as $collection) {
484
			$this->invalidateAccessCacheForCollectionByUser($collection, $user);
485
		}
486
	}
487
488
	protected function invalidateAccessCacheForCollectionByUser(ICollection $collection, ?IUser $user): void {
489
		$query = $this->connection->getQueryBuilder();
490
		$userId = $user instanceof IUser ? $user->getUID() : '';
491
492
		$query->delete(self::TABLE_ACCESS_CACHE)
493
			->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())))
494
			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
495
		$query->execute();
496
	}
497
498
	public function invalidateAccessCacheForProviderByUser(IProvider $provider, ?IUser $user): void {
499
		$query = $this->connection->getQueryBuilder();
500
		$userId = $user instanceof IUser ? $user->getUID() : '';
501
502
		$query->delete(self::TABLE_ACCESS_CACHE)
503
			->where($query->expr()->eq('resource_type', $query->createNamedParameter($provider->getType(), IQueryBuilder::PARAM_STR)))
504
			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
505
		$query->execute();
506
	}
507
508
	/**
509
	 * @param string $provider
510
	 */
511
	public function registerResourceProvider(string $provider): void {
512
		$this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]);
513
		$this->providerManager->registerResourceProvider($provider);
514
	}
515
516
	/**
517
	 * Get the resource type of the provider
518
	 *
519
	 * @return string
520
	 * @since 16.0.0
521
	 */
522
	public function getType(): string {
523
		return '';
524
	}
525
}
526