Completed
Push — some-scrutinizing ( d3de44...c6cac2 )
by Maxence
02:32
created

CircleProvider   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 553
Duplicated Lines 4.7 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 41
c 0
b 0
f 0
lcom 1
cbo 3
dl 26
loc 553
rs 8.2769

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A identifier() 0 3 1
A create() 0 17 3
A update() 0 11 1
A delete() 0 7 1
A deleteFromSelf() 0 11 1
A move() 12 12 1
A getShareChildId() 14 14 2
A createShare() 0 7 1
A createShareChild() 0 10 1
A getSharesInFolder() 0 15 2
A getSharesBy() 0 19 3
A editShareEntry() 0 6 1
A getShareById() 0 20 3
A getSharesByPath() 0 3 1
A getSharedWith() 0 21 3
A editShareFromParentEntry() 0 9 3
A getShareByToken() 0 3 1
A getChildren() 0 3 1
A userDeleted() 0 4 1
A groupDeleted() 0 3 1
A userDeletedFromGroup() 0 3 1
B createShareObject() 0 36 2
A isAccessibleResult() 0 14 4
A errorShareAlreadyExist() 0 12 1

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

1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
28
namespace OCA\Circles;
29
30
31
use \OC\Share20\Exception\InvalidShare;
32
use OCA\Circles\AppInfo\Application;
33
use OCA\Circles\Db\CircleProviderRequestBuilder;
34
use OCA\Circles\Model\Circle;
35
use OCP\Files\Folder;
36
use OCP\Files\Node;
37
use OCP\Files\IRootFolder;
38
use OC\Files\Cache\Cache;
39
use OC\Share20\Share;
40
use OCP\Share\IShare;
41
use OCP\Share\Exceptions\ShareNotFound;
42
use OCP\Share\IShareProvider;
43
use OCP\IDBConnection;
44
use OCP\IL10N;
45
use OCP\ILogger;
46
use OCP\IURLGenerator;
47
use OCP\Security\ISecureRandom;
48
use OCP\IUserManager;
49
50
class CircleProvider extends CircleProviderRequestBuilder implements IShareProvider {
51
52
	private $misc;
53
54
55
	/** @var ILogger */
56
	private $logger;
57
58
	/** @var ISecureRandom */
59
	private $secureRandom;
60
61
	/** @var IUserManager */
62
	private $userManager;
63
64
	/** @var IRootFolder */
65
	private $rootFolder;
66
67
	/** @var IL10N */
68
	private $l;
69
70
	/** @var IURLGenerator */
71
	private $urlGenerator;
72
73
74
	/**
75
	 * DefaultShareProvider constructor.
76
	 *
77
	 * @param IDBConnection $connection
78
	 * @param ISecureRandom $secureRandom
79
	 * @param IUserManager $userManager
80
	 * @param IRootFolder $rootFolder
81
	 * @param IL10N $l
82
	 * @param ILogger $logger
83
	 * @param IURLGenerator $urlGenerator
84
	 */
85
	public function __construct(
86
		IDBConnection $connection, ISecureRandom $secureRandom, IUserManager $userManager,
87
		IRootFolder $rootFolder, IL10N $l, ILogger $logger, IURLGenerator $urlGenerator
88
	) {
89
		$this->dbConnection = $connection;
90
		$this->secureRandom = $secureRandom;
91
		$this->userManager = $userManager;
92
		$this->rootFolder = $rootFolder;
93
		$this->l = $l;
94
		$this->logger = $logger;
95
		$this->urlGenerator = $urlGenerator;
96
97
		$app = new Application();
98
		$this->misc = $app->getContainer()
99
						  ->query('MiscService');
100
101
	}
102
103
104
	/**
105
	 * Return the identifier of this provider.
106
	 *
107
	 * @return string
108
	 */
109
	public function identifier() {
110
		return 'ocCircleShare';
111
	}
112
113
114
	/**
115
	 * Create a share if it does not exist already.
116
	 *
117
	 * @param IShare $share
118
	 *
119
	 * @return IShare The share object
120
	 * @throws \Exception
121
	 */
122
	// TODO: check if user can create a share in this circle !
123
	public function create(IShare $share) {
124
		$nodeId = $share->getNode()
125
						->getId();
126
127
		$qb = $this->findShareParentSql($nodeId, $share->getSharedWith());
128
		$exists = $qb->execute();
129
		$data = $exists->fetch();
130
		$exists->closeCursor();
131
132
		if ($data !== false && sizeof($data) > 0) {
133
			throw $this->errorShareAlreadyExist($share);
134
		}
135
136
		$shareId = $this->createShare($share);
137
138
		return $this->getShareById($shareId);
139
	}
140
141
142
	/**
143
	 * Update a share
144
	 * permissions right, owner and initiator
145
	 *
146
	 * @param IShare $share
147
	 *
148
	 * @return IShare The share object
149
	 */
150
	public function update(IShare $share) {
151
152
		$qb = $this->getBaseUpdateSql();
153
		$this->limitToShare($qb, $share->getId());
154
		$qb->set('permissions', $qb->createNamedParameter($share->getPermissions()))
155
		   ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
156
		   ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
157
		$qb->execute();
158
159
		return $share;
160
	}
161
162
163
	/**
164
	 * Delete a share, and it's children
165
	 *
166
	 * @param IShare $share
167
	 */
168
	public function delete(IShare $share) {
169
170
		$qb = $this->getBaseDeleteSql();
171
		$this->limitToShareAndChildren($qb, $share->getId());
172
173
		$qb->execute();
174
	}
175
176
177
	/**
178
	 * Unshare a file from self as recipient.
179
	 * Because every circles share are group shares, we will set permissions to 0
180
	 *
181
	 * @param IShare $share
182
	 * @param string $userId
183
	 */
184
	public function deleteFromSelf(IShare $share, $userId) {
185
		$childId = $this->getShareChildId($share, $userId, true);
186
187
		$qb = $this->getBaseUpdateSql();
188
		$qb->set('permissions', $qb->createNamedParameter(0));
189
		// Overkill
190
		// $this->limitToShareChildren($qb, $userId, $share->getId());
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
191
		$this->limitToShare($qb, $childId);
192
193
		$qb->execute();
194
	}
195
196
197
	/**
198
	 * Move a share as a recipient.
199
	 *
200
	 * @param IShare $share
201
	 * @param string $userId
202
	 *
203
	 * @return IShare
204
	 *
205
	 */
206 View Code Duplication
	public function move(IShare $share, $userId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
207
208
		$childId = $this->getShareChildId($share, $userId, true);
209
210
		$qb = $this->getBaseUpdateSql();
211
		$qb->set('file_target', $qb->createNamedParameter($share->getTarget()));
212
		$this->limitToShareChildren($qb, $userId, $share->getId());
213
		$this->limitToShare($qb, $childId);
214
		$qb->execute();
215
216
		return $share;
217
	}
218
219
220
	/**
221
	 * return the child ID of a share
222
	 *
223
	 * @param IShare $share
224
	 * @param $userId
225
	 *
226
	 * @return bool
227
	 */
228 View Code Duplication
	private function getShareChildId(IShare $share, $userId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
229
		$qb = $this->getBaseSelectSql($share->getId());
230
		$this->limitToShareChildren($qb, $userId, $share->getId());
231
232
		$child = $qb->execute();
233
		$data = $child->fetch();
234
		$child->closeCursor();
235
236
		if ($data === false) {
237
			return $this->createShareChild($userId, $share);
238
		}
239
240
		return $data['id'];
241
	}
242
243
244
	/**
245
	 * Create a child and returns its ID
246
	 *
247
	 * @param IShare $share
248
	 *
249
	 * @return int
250
	 */
251
	private function createShare($share) {
252
		$qb = $this->getBaseInsertSql($share);
253
		$qb->execute();
254
		$id = $qb->getLastInsertId();
255
256
		return (int)$id;
257
	}
258
259
260
	/**
261
	 * Create a child and returns its ID
262
	 *
263
	 * @param string $userId
264
	 * @param IShare $share
265
	 *
266
	 * @return int
267
	 */
268
	private function createShareChild($userId, $share) {
269
		$qb = $this->getBaseInsertSql($share);
270
271
		$qb->setValue('parent', $qb->createNamedParameter($share->getId()));
272
		$qb->setValue('share_with', $qb->createNamedParameter($userId));
273
		$qb->execute();
274
		$id = $qb->getLastInsertId();
275
276
		return (int)$id;
277
	}
278
279
280
	/**
281
	 * Get all shares by the given user in a folder
282
	 *
283
	 * @param string $userId
284
	 * @param Folder $node
285
	 * @param bool $reshares Also get the shares where $user is the owner instead of just the
286
	 *     shares where $user is the initiator
287
	 *
288
	 * @return IShare[]
289
	 */
290
	public function getSharesInFolder($userId, Folder $node, $reshares) {
291
		$this->misc->log("CircleProvider: getSharesInFolder");
292
293
		$qb = $this->getBaseSelectSql();
294
		$this->linkToMember($qb, $userId);
295
		$cursor = $qb->execute();
296
297
		$shares = [];
298
		while ($data = $cursor->fetch()) {
299
			$shares[$data['file_source']][] = $this->createShareObject($data);
300
		}
301
		$cursor->closeCursor();
302
303
		return $shares;
304
	}
305
306
307
	/**
308
	 * Get all shares by the given user
309
	 *
310
	 * @param string $userId
311
	 * @param int $shareType
312
	 * @param Node|null $node
313
	 * @param bool $reShares
314
	 * @param int $limit The maximum number of shares to be returned, -1 for all shares
315
	 * @param int $offset
316
	 *
317
	 * @return IShare[]
318
	 * @internal param bool $reshares Also get the shares where $user is the owner instead of just
319
	 *     the shares where $user is the initiator
320
	 */
321
	public function getSharesBy($userId, $shareType, $node, $reShares, $limit, $offset) {
322
		$qb = $this->getBaseSelectSql();
323
		$this->limitToOwner($qb, $userId, $reShares);
324
325
		if ($node !== null) {
326
			$this->limitToFile($qb, $node->getId());
327
		}
328
329
		$this->limitToPage($qb, $limit, $offset);
330
		$cursor = $qb->execute();
331
332
		$shares = [];
333
		while ($data = $cursor->fetch()) {
334
			$shares[] = $this->createShareObject($this->editShareEntry($data));
335
		}
336
		$cursor->closeCursor();
337
338
		return $shares;
339
	}
340
341
342
	/**
343
	 * returns a better formatted string to display more information about
344
	 * the circle to the Sharing UI
345
	 *
346
	 * @param $data
347
	 *
348
	 * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
349
	 */
350
	private function editShareEntry($data) {
351
		$data['share_with'] =
352
			sprintf('%s (%s)', $data['circle_name'], Circle::TypeLongString($data['circle_type']));
353
354
		return $data;
355
	}
356
357
358
	/**
359
	 * Get share by its id
360
	 *
361
	 * @param int $shareId
362
	 * @param string|null $recipientId
363
	 *
364
	 * @return IShare
0 ignored issues
show
Documentation introduced by
Should the return type not be Share?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
365
	 * @throws ShareNotFound
366
	 */
367
	public function getShareById($shareId, $recipientId = null) {
368
		$qb = $this->getBaseSelectSql();
369
		$this->limitToShare($qb, $shareId);
370
371
		$cursor = $qb->execute();
372
		$data = $cursor->fetch();
373
		$cursor->closeCursor();
374
375
		if ($data === false) {
376
			throw new ShareNotFound();
377
		}
378
379
		try {
380
			$share = $this->createShareObject($data);
381
		} catch (InvalidShare $e) {
0 ignored issues
show
Bug introduced by
The class OC\Share20\Exception\InvalidShare does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
382
			throw new ShareNotFound();
383
		}
384
385
		return $share;
386
	}
387
388
389
	/**
390
	 * Get shares for a given path
391
	 *
392
	 * @param Node $path
393
	 *
394
	 * @return IShare[]
0 ignored issues
show
Documentation introduced by
Should the return type not be IShare[]|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
395
	 */
396
	public function getSharesByPath(Node $path) {
397
		return null;
398
	}
399
400
401
	/**
402
	 * Get shared with the given user
403
	 *
404
	 * @param string $userId get shares where this user is the recipient
405
	 * @param int $shareType
406
	 * @param Node|null $node
407
	 * @param int $limit The max number of entries returned, -1 for all
408
	 * @param int $offset
409
	 *
410
	 * @return IShare[]
411
	 */
412
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
413
		$this->misc->log("CircleProvider: getSharedWith " . $userId . '   ' . $shareType);
414
415
		$qb = $this->getCompleteSelectSql();
416
		$this->linkToMember($qb, $userId);
417
		$this->linkToFileCache($qb, $userId);
418
		$this->limitToPage($qb, $limit, $offset);
419
420
		$cursor = $qb->execute();
421
422
		$shares = [];
423
		while ($data = $cursor->fetch()) {
424
			self::editShareFromParentEntry($data);
425
			if (self::isAccessibleResult($data)) {
426
				$shares[] = $this->createShareObject($data);
427
			}
428
		}
429
		$cursor->closeCursor();
430
431
		return $shares;
432
	}
433
434
435
	private static function editShareFromParentEntry(& $data) {
436
		if ($data['parent_id'] > 0) {
437
			if ($data['parent_perms'] === '0') {
438
				return;
439
			}
440
441
			$data['file_target'] = $data['parent_target'];
442
		}
443
	}
444
445
446
	/**
447
	 * Get a share by token
448
	 *
449
	 * @param string $token
450
	 *
451
	 * @return IShare
0 ignored issues
show
Documentation introduced by
Should the return type not be IShare|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
452
	 * @throws ShareNotFound
453
	 */
454
	public function getShareByToken($token) {
455
		return null;
456
	}/** @noinspection PhpUnusedParameterInspection */
457
	/** @noinspection PhpUnusedParameterInspection */
458
459
460
	/**
461
	 * We don't return a thing about children.
462
	 * The call to this function is deprecated and should be removed in next release of NC.
463
	 * Also, we get the children in the delete() method.
464
	 *
465
	 * @param IShare $parent
466
	 *
467
	 * @deprecated
468
	 * @return array
469
	 */
470
	public function getChildren(IShare $parent) {
471
		return [];
472
	}
473
474
475
	/**
476
	 * A user is deleted from the system
477
	 * So clean up the relevant shares.
478
	 *
479
	 * @param string $uid
480
	 * @param int $shareType
481
	 */
482
	public function userDeleted($uid, $shareType) {
483
		$this->misc->log("CircleProvider: userDeleted");
484
		// TODO: Implement userDeleted() method.
485
	}
486
487
488
	/**
489
	 * A group is deleted from the system.
490
	 * We handle our own groups.
491
	 *
492
	 * @param string $gid
493
	 */
494
	public function groupDeleted($gid) {
495
		return;
496
	}
497
498
499
	/**
500
	 * A user is deleted from a group.
501
	 * We handle our own groups.
502
	 *
503
	 * @param string $uid
504
	 * @param string $gid
505
	 */
506
	public function userDeletedFromGroup($uid, $gid) {
507
		return;
508
	}
509
510
511
	/**
512
	 * Create a share object from an database row
513
	 *
514
	 * @param array $data
515
	 *
516
	 * @return IShare
0 ignored issues
show
Documentation introduced by
Should the return type not be Share?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
517
	 * @throws InvalidShare
518
	 * @throws ShareNotFound
519
	 */
520
	private function createShareObject($data) {
521
522
		$share = new Share($this->rootFolder, $this->userManager);
523
		$share->setId((int)$data['id'])
524
			  ->setShareType((int)$data['share_type'])
525
			  ->setPermissions((int)$data['permissions'])
526
			  ->setTarget($data['file_target'])
527
			  ->setMailSend((bool)$data['mail_send']);
528
529
		$shareTime = new \DateTime();
530
		$shareTime->setTimestamp((int)$data['stime']);
531
		$share->setShareTime($shareTime);
532
533
		$share->setSharedWith($data['share_with']);
534
		$share->setSharedBy($data['uid_initiator']);
535
		$share->setShareOwner($data['uid_owner']);
536
537
		$share->setNodeId((int)$data['file_source']);
538
		$share->setNodeType($data['item_type']);
539
540
		if (isset($data['f_permissions'])) {
541
			$entryData = $data;
542
			$entryData['permissions'] = $entryData['f_permissions'];
543
			$entryData['parent'] = $entryData['f_parent'];
544
			$share->setNodeCacheEntry(
545
				Cache::cacheEntryFromData(
546
					$entryData,
547
					\OC::$server->getMimeTypeLoader()
548
				)
549
			);
550
		}
551
552
		$share->setProviderId($this->identifier());
553
554
		return $share;
555
	}
556
557
558
	/**
559
	 * Returns whether the given database result can be interpreted as
560
	 * a share with accessible file (not trashed, not deleted)
561
	 *
562
	 * Complete copy/paste from others ShareProvider
563
	 *
564
	 * @param $data
565
	 *
566
	 * @return bool
567
	 */
568
	private static function isAccessibleResult($data) {
569
		if ($data['fileid'] === null) {
570
			return false;
571
		}
572
573
		$pathSections = explode('/', $data['path'], 2);
574
		if ($pathSections[0] !== 'files'
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !($pathSections[0...d'], 2)[0] === 'home');.
Loading history...
575
			&& explode(':', $data['storage_string_id'], 2)[0] === 'home'
576
		) {
577
			return false;
578
		}
579
580
		return true;
581
	}
582
583
584
	/**
585
	 * @param IShare $share
586
	 *
587
	 * @return \Exception
588
	 */
589
	private function errorShareAlreadyExist($share) {
590
		$share_src = $share->getNode()
591
						   ->getName();
592
593
		$message = 'Sharing %s failed, this item is already shared with this circle';
594
		$message_t = $this->l->t($message, array($share_src));
595
		$this->logger->debug(
596
			sprintf($message, $share_src, $share->getSharedWith()), ['app' => 'circles']
597
		);
598
599
		return new \Exception($message_t);
600
	}
601
602
}
603