DefaultShareProvider   F
last analyzed

Complexity

Total Complexity 112

Size/Duplication

Total Lines 1194
Duplicated Lines 1.09 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
dl 13
loc 1194
rs 0.824
c 0
b 0
f 0
wmc 112
lcom 1
cbo 13

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A identifier() 0 3 1
C create() 0 89 9
B update() 0 69 4
A getChildren() 0 31 2
A delete() 0 15 2
A deleteFromSelf() 0 8 3
A move() 0 3 1
B updateForRecipient() 0 78 8
B getAllSharesBy() 0 49 5
B getSharesBy() 0 45 5
B getShareById() 0 49 6
A getSharesByPath() 0 26 2
A isAccessibleResult() 0 15 4
A getSharedWithUserQuery() 0 25 2
A getSharedWithGroupQuery() 0 30 2
A getSharedWithUserGroupQuery() 0 34 2
C getSharedWith() 0 71 13
B getAllSharedWith() 0 66 9
A getShareByToken() 0 27 3
A createShare() 0 38 5
A chunkSharesToMaps() 0 25 3
B resolveGroupShares() 0 48 5
B userDeleted() 0 57 4
A groupDeleted() 0 36 4
A userDeletedFromGroup() 13 31 4
A validate() 0 7 3

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

1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author phisch <[email protected]>
6
 * @author Roeland Jago Douma <[email protected]>
7
 * @author Vincent Petry <[email protected]>
8
 * @author Piotr Mrowczynski <[email protected]>
9
 *
10
 * @copyright Copyright (c) 2018, ownCloud GmbH
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
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, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
namespace OC\Share20;
27
28
use OCP\Files\File;
29
use OCP\Share\IShare;
30
use OCP\Share\IShareProvider;
31
use OC\Share20\Exception\InvalidShare;
32
use OC\Share20\Exception\ProviderException;
33
use OCP\Share\Exceptions\ShareNotFound;
34
use OC\Share20\Exception\BackendError;
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCP\IGroup;
37
use OCP\IGroupManager;
38
use OCP\IUserManager;
39
use OCP\Files\IRootFolder;
40
use OCP\IDBConnection;
41
use OCP\Files\Node;
42
43
/**
44
 * Class DefaultShareProvider
45
 *
46
 * @package OC\Share20
47
 */
48
class DefaultShareProvider implements IShareProvider {
49
50
	// Special share type for user modified group shares
51
	const SHARE_TYPE_USERGROUP = 2;
52
53
	/** @var IDBConnection */
54
	private $dbConn;
55
56
	/** @var IUserManager */
57
	private $userManager;
58
59
	/** @var IGroupManager */
60
	private $groupManager;
61
62
	/** @var IRootFolder */
63
	private $rootFolder;
64
65
	/**
66
	 * DefaultShareProvider constructor.
67
	 *
68
	 * @param IDBConnection $connection
69
	 * @param IUserManager $userManager
70
	 * @param IGroupManager $groupManager
71
	 * @param IRootFolder $rootFolder
72
	 */
73
	public function __construct(
74
			IDBConnection $connection,
75
			IUserManager $userManager,
76
			IGroupManager $groupManager,
77
			IRootFolder $rootFolder) {
78
		$this->dbConn = $connection;
79
		$this->userManager = $userManager;
80
		$this->groupManager = $groupManager;
81
		$this->rootFolder = $rootFolder;
82
	}
83
84
	/**
85
	 * Return the identifier of this provider.
86
	 *
87
	 * @return string Containing only [a-zA-Z0-9]
88
	 */
89
	public function identifier() {
90
		return 'ocinternal';
91
	}
92
93
	/**
94
	 * Share a path
95
	 *
96
	 * @param \OCP\Share\IShare $share
97
	 * @return \OCP\Share\IShare The share object
98
	 * @throws ShareNotFound
99
	 * @throws InvalidArgumentException if the share validation failed
100
	 * @throws \Exception
101
	 */
102
	public function create(\OCP\Share\IShare $share) {
103
		$this->validate($share);
104
		$qb = $this->dbConn->getQueryBuilder();
105
106
		$qb->insert('share');
107
		$qb->setValue('share_type', $qb->createNamedParameter($share->getShareType()));
108
109
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
110
			//Set the UID of the user we share with
111
			$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
112
			$qb->setValue('accepted', $share->getState());
113
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
114
			//Set the GID of the group we share with
115
			$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
116
			$qb->setValue('accepted', $share->getState());
117
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
118
			//Set the token of the share
119
			$qb->setValue('token', $qb->createNamedParameter($share->getToken()));
120
121
			//If a password is set store it
122
			if ($share->getPassword() !== null) {
123
				$qb->setValue('share_with', $qb->createNamedParameter($share->getPassword()));
124
			}
125
126
			//If an expiration date is set store it
127
			if ($share->getExpirationDate() !== null) {
128
				$qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime'));
129
			}
130
131
			if (\method_exists($share, 'getParent')) {
132
				$qb->setValue('parent', $qb->createNamedParameter($share->getParent()));
133
			}
134
135
			// Set user-defined name
136
			$qb->setValue('share_name', $qb->createNamedParameter($share->getName()));
137
		} else {
138
			throw new \Exception('invalid share type!');
139
		}
140
141
		// Set what is shares
142
		$qb->setValue('item_type', $qb->createParameter('itemType'));
143
		if ($share->getNode() instanceof \OCP\Files\File) {
144
			$qb->setParameter('itemType', 'file');
145
		} else {
146
			$qb->setParameter('itemType', 'folder');
147
		}
148
149
		// Set the file id
150
		$qb->setValue('item_source', $qb->createNamedParameter($share->getNode()->getId()));
151
		$qb->setValue('file_source', $qb->createNamedParameter($share->getNode()->getId()));
152
153
		// set the permissions
154
		$qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions()));
155
156
		// Set who created this share
157
		$qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
158
159
		// Set who is the owner of this file/folder (and this the owner of the share)
160
		$qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()));
161
162
		// Set the file target
163
		$qb->setValue('file_target', $qb->createNamedParameter($share->getTarget()));
164
165
		// Set the time this share was created
166
		$qb->setValue('stime', $qb->createNamedParameter(\time()));
167
168
		// insert the data and fetch the id of the share
169
		$this->dbConn->beginTransaction();
170
		$qb->execute();
171
		$id = $this->dbConn->lastInsertId('*PREFIX*share');
172
173
		// Now fetch the inserted share and create a complete share object
174
		$qb = $this->dbConn->getQueryBuilder();
175
		$qb->select('*')
176
			->from('share')
177
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
178
179
		$cursor = $qb->execute();
180
		$data = $cursor->fetch();
181
		$this->dbConn->commit();
182
		$cursor->closeCursor();
183
184
		if ($data === false) {
185
			throw new ShareNotFound();
186
		}
187
188
		$share = $this->createShare($data);
189
		return $share;
190
	}
191
192
	/**
193
	 * Update a share
194
	 *
195
	 * @param \OCP\Share\IShare $share
196
	 * @return \OCP\Share\IShare The share object
197
	 * @throws InvalidArgumentException if the share validation failed
198
	 */
199
	public function update(\OCP\Share\IShare $share) {
200
		$this->validate($share);
201
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
202
			/*
203
			 * We allow updating the recipient on user shares.
204
			 */
205
			$qb = $this->dbConn->getQueryBuilder();
206
			$qb->update('share')
207
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
208
				->set('share_with', $qb->createNamedParameter($share->getSharedWith()))
209
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
210
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
211
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
212
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
213
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
214
				->set('accepted', $qb->createNamedParameter($share->getState()))
215
				->set('mail_send', $qb->createNamedParameter((int) $share->getMailSend()))
216
				->execute();
217
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
218
			$qb = $this->dbConn->getQueryBuilder();
219
			$qb->update('share')
220
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
221
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
222
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
223
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
224
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
225
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
226
				->set('accepted', $qb->createNamedParameter($share->getState()))
227
				->set('mail_send', $qb->createNamedParameter((int) $share->getMailSend()))
228
				->execute();
229
230
			/*
231
			 * Update all user defined group shares
232
			 */
233
			$qb = $this->dbConn->getQueryBuilder();
234
			$qb->update('share')
235
				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
236
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
237
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
238
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
239
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
240
				->execute();
241
242
			/*
243
			 * Now update the permissions for all children
244
			 */
245
			$qb = $this->dbConn->getQueryBuilder();
246
			$qb->update('share')
247
				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
248
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
249
				->execute();
250
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
251
			$qb = $this->dbConn->getQueryBuilder();
252
			$qb->update('share')
253
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
254
				->set('share_with', $qb->createNamedParameter($share->getPassword()))
255
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
256
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
257
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
258
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
259
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
260
				->set('token', $qb->createNamedParameter($share->getToken()))
261
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
262
				->set('share_name', $qb->createNamedParameter($share->getName()))
263
				->execute();
264
		}
265
266
		return $share;
267
	}
268
269
	/**
270
	 * Get all children of this share
271
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
272
	 *
273
	 * @param \OCP\Share\IShare $parent
274
	 * @return \OCP\Share\IShare[]
275
	 */
276
	public function getChildren(\OCP\Share\IShare $parent) {
277
		$children = [];
278
279
		$qb = $this->dbConn->getQueryBuilder();
280
		$qb->select('*')
281
			->from('share')
282
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
283
			->andWhere(
284
				$qb->expr()->in(
285
					'share_type',
286
					$qb->createNamedParameter([
287
						\OCP\Share::SHARE_TYPE_USER,
288
						\OCP\Share::SHARE_TYPE_GROUP,
289
						\OCP\Share::SHARE_TYPE_LINK,
290
					], IQueryBuilder::PARAM_INT_ARRAY)
291
				)
292
			)
293
			->andWhere($qb->expr()->orX(
294
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
295
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
296
			))
297
			->orderBy('id');
298
299
		$cursor = $qb->execute();
300
		while ($data = $cursor->fetch()) {
301
			$children[] = $this->createShare($data);
302
		}
303
		$cursor->closeCursor();
304
305
		return $children;
306
	}
307
308
	/**
309
	 * Delete a share
310
	 *
311
	 * @param \OCP\Share\IShare $share
312
	 */
313
	public function delete(\OCP\Share\IShare $share) {
314
		$qb = $this->dbConn->getQueryBuilder();
315
		$qb->delete('share')
316
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
317
318
		/*
319
		 * If the share is a group share delete all possible
320
		 * user defined groups shares.
321
		 */
322
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
323
			$qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
324
		}
325
326
		$qb->execute();
327
	}
328
329
	/**
330
	 * Unshare a share from the recipient. If this is a group share
331
	 * this means we need a special entry in the share db.
332
	 *
333
	 * @param \OCP\Share\IShare $share
334
	 * @param string $recipient UserId of recipient
335
	 * @throws BackendError
336
	 * @throws ProviderException
337
	 */
338
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipient) {
339
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP || $share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
340
			$share->setState(\OCP\Share::STATE_REJECTED);
341
			$this->updateForRecipient($share, $recipient);
342
		} else {
343
			throw new ProviderException('Invalid share type ' . $share->getShareType());
344
		}
345
	}
346
347
	/**
348
	 * @inheritdoc
349
	 */
350
	public function move(\OCP\Share\IShare $share, $recipient) {
351
		return $this->updateForRecipient($share, $recipient);
352
	}
353
354
	/**
355
	 * @inheritdoc
356
	 */
357
	public function updateForRecipient(\OCP\Share\IShare $share, $recipient) {
358
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
359
			if ($share->getSharedWith() !== $recipient) {
360
				throw new ProviderException('Recipient does not match');
361
			}
362
363
			// Just update the target
364
			$qb = $this->dbConn->getQueryBuilder();
365
			$qb->update('share')
366
				->set('accepted', $qb->createNamedParameter($share->getState()))
367
				->set('file_target', $qb->createNamedParameter($share->getTarget()))
368
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
369
				->execute();
370
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
371
			$group = $this->groupManager->get($share->getSharedWith());
372
			$user = $this->userManager->get($recipient);
373
374
			if ($group === null) {
375
				throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
376
			}
377
378
			if (!$group->inGroup($user)) {
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($recipient) on line 372 can be null; however, OCP\IGroup::inGroup() 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...
379
				throw new ProviderException('Recipient not in receiving group');
380
			}
381
382
			// Check if there is a usergroup share
383
			$qb = $this->dbConn->getQueryBuilder();
384
			$stmt = $qb->select('id')
385
				->from('share')
386
				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
387
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
388
				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
389
				->andWhere($qb->expr()->orX(
390
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
391
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
392
				))
393
				->setMaxResults(1)
394
				->execute();
395
396
			$data = $stmt->fetch();
397
			$stmt->closeCursor();
398
399
			if ($data === false) {
400
				// No usergroup share yet. Create one.
401
				$qb = $this->dbConn->getQueryBuilder();
402
				$qb->insert('share')
403
					->values([
404
						'share_type' => $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP),
405
						'share_with' => $qb->createNamedParameter($recipient),
406
						'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
407
						'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
408
						'parent' => $qb->createNamedParameter($share->getId()),
409
						'item_type' => $qb->createNamedParameter($share->getNode() instanceof File ? 'file' : 'folder'),
410
						'item_source' => $qb->createNamedParameter($share->getNode()->getId()),
411
						'file_source' => $qb->createNamedParameter($share->getNode()->getId()),
412
						'file_target' => $qb->createNamedParameter($share->getTarget()),
413
						'permissions' => $qb->createNamedParameter($share->getPermissions()),
414
						'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
415
						'accepted' => $qb->createNamedParameter($share->getState()),
416
					])->execute();
417
			} else {
418
				// Already a usergroup share. Update it.
419
				$qb = $this->dbConn->getQueryBuilder();
420
				$qb->update('share')
421
					->set('accepted', $qb->createNamedParameter($share->getState()))
422
					->set('file_target', $qb->createNamedParameter($share->getTarget()))
423
					// make sure to reset the permissions to the one of the parent share,
424
					// as legacy entries with zero permissions might still exist
425
					->set('permissions', $qb->createNamedParameter($share->getPermissions()))
426
					->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
427
					->execute();
428
			}
429
		} else {
430
			throw new ProviderException('Can\'t update share of recipient for share type ' . $share->getShareType());
431
		}
432
433
		return $share;
434
	}
435
436
	/**
437
	 * @inheritdoc
438
	 */
439
	public function getAllSharesBy($userId, $shareTypes, $nodeIDs, $reshares) {
440
		$shares = [];
441
		$qb = $this->dbConn->getQueryBuilder();
442
443
		$nodeIdsChunks = \array_chunk($nodeIDs, 100);
444
		foreach ($nodeIdsChunks as $nodeIdsChunk) {
445
			$qb->select('*')
446
				->from('share')
447
				->andWhere($qb->expr()->orX(
448
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
449
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
450
				));
451
452
			$orX = $qb->expr()->orX();
453
454
			foreach ($shareTypes as $shareType) {
455
				$orX->add($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
456
			}
457
458
			$qb->andWhere($orX);
459
460
			/**
461
			 * Reshares for this user are shares where they are the owner.
462
			 */
463
			if ($reshares === false) {
464
				$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
465
			} else {
466
				$qb->andWhere(
467
					$qb->expr()->orX(
468
						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
469
						$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('uid_ini...amedParameter($userId)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
470
					)
471
				);
472
			}
473
474
			$qb->andWhere($qb->expr()->in('file_source', $qb->createParameter('file_source_ids')));
475
			$qb->setParameter('file_source_ids', $nodeIdsChunk, IQueryBuilder::PARAM_INT_ARRAY);
476
477
			$qb->orderBy('id');
478
479
			$cursor = $qb->execute();
480
			while ($data = $cursor->fetch()) {
481
				$shares[] = $this->createShare($data);
482
			}
483
			$cursor->closeCursor();
484
		}
485
		
486
		return $shares;
487
	}
488
489
	/**
490
	 * @inheritdoc
491
	 */
492
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
493
		$qb = $this->dbConn->getQueryBuilder();
494
		$qb->select('*')
495
			->from('share')
496
			->andWhere($qb->expr()->orX(
497
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
498
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
499
			));
500
501
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
502
503
		/**
504
		 * Reshares for this user are shares where they are the owner.
505
		 */
506
		if ($reshares === false) {
507
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
508
		} else {
509
			$qb->andWhere(
510
				$qb->expr()->orX(
511
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
512
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('uid_ini...amedParameter($userId)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
513
				)
514
			);
515
		}
516
517
		if ($node !== null) {
518
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
519
		}
520
521
		if ($limit !== -1) {
522
			$qb->setMaxResults($limit);
523
		}
524
525
		$qb->setFirstResult($offset);
526
		$qb->orderBy('id');
527
528
		$cursor = $qb->execute();
529
		$shares = [];
530
		while ($data = $cursor->fetch()) {
531
			$shares[] = $this->createShare($data);
532
		}
533
		$cursor->closeCursor();
534
535
		return $shares;
536
	}
537
538
	/**
539
	 * @inheritdoc
540
	 */
541
	public function getShareById($id, $recipientId = null) {
542
		$qb = $this->dbConn->getQueryBuilder();
543
544
		$qb->select('*')
545
			->from('share')
546
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
547
			->andWhere(
548
				$qb->expr()->in(
549
					'share_type',
550
					$qb->createNamedParameter([
551
						\OCP\Share::SHARE_TYPE_USER,
552
						\OCP\Share::SHARE_TYPE_GROUP,
553
						\OCP\Share::SHARE_TYPE_LINK,
554
					], IQueryBuilder::PARAM_INT_ARRAY)
555
				)
556
			)
557
			->andWhere($qb->expr()->orX(
558
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
559
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
560
			));
561
		
562
		$cursor = $qb->execute();
563
		$data = $cursor->fetch();
564
		$cursor->closeCursor();
565
566
		if ($data === false) {
567
			throw new ShareNotFound();
568
		}
569
570
		try {
571
			$share = $this->createShare($data);
572
		} catch (InvalidShare $e) {
573
			throw new ShareNotFound();
574
		}
575
576
		// If the recipient is set for a group share resolve to that user
577
		if ($recipientId !== null && $share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
578
			$resolvedShares = $this->resolveGroupShares([$share], $recipientId);
579
			if (\count($resolvedShares) === 1) {
580
				// If we pass to resolveGroupShares() an with one element,
581
				// we expect to receive exactly one element, otherwise it is error
582
				$share = $resolvedShares[0];
583
			} else {
584
				throw new ProviderException("ResolveGroupShares() returned wrong result");
585
			}
586
		}
587
588
		return $share;
589
	}
590
591
	/**
592
	 * Get shares for a given path
593
	 *
594
	 * @param \OCP\Files\Node $path
595
	 * @return \OCP\Share\IShare[]
596
	 */
597
	public function getSharesByPath(Node $path) {
598
		$qb = $this->dbConn->getQueryBuilder();
599
600
		$cursor = $qb->select('*')
601
			->from('share')
602
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
603
			->andWhere(
604
				$qb->expr()->orX(
605
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)),
606
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('share_t...are::SHARE_TYPE_GROUP)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
607
				)
608
			)
609
			->andWhere($qb->expr()->orX(
610
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
611
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
612
			))
613
			->execute();
614
615
		$shares = [];
616
		while ($data = $cursor->fetch()) {
617
			$shares[] = $this->createShare($data);
618
		}
619
		$cursor->closeCursor();
620
621
		return $shares;
622
	}
623
624
	/**
625
	 * Returns whether the given database result can be interpreted as
626
	 * a share with accessible file (not trashed, not deleted)
627
	 */
628
	private function isAccessibleResult($data) {
629
		// exclude shares leading to deleted file entries
630
		if ($data['fileid'] === null) {
631
			return false;
632
		}
633
634
		// exclude shares leading to trashbin on home storages
635
		$pathSections = \explode('/', $data['path'], 2);
636
		// FIXME: would not detect rare md5'd home storage case properly
637
		$storagePrefix = \explode(':', $data['storage_string_id'], 2)[0];
638
		if ($pathSections[0] !== 'files' && \in_array($storagePrefix, ['home', 'object'], true)) {
639
			return false;
640
		}
641
		return true;
642
	}
643
644
	/*
645
	 * Get shared with user shares for the given userId and node
646
	 *
647
	 * @param string $userId
648
	 * @param Node|null $node
649
	 * @return DB\QueryBuilder\IQueryBuilder $qb
650
	 */
651
	public function getSharedWithUserQuery($userId, $node) {
652
		$qb = $this->dbConn->getQueryBuilder();
653
		$qb->select('s.*', 'f.fileid', 'f.path')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'f.fileid'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
654
			->selectAlias('st.id', 'storage_string_id')
655
			->from('share', 's')
656
			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
657
			->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'));
658
659
		// Order by id
660
		$qb->orderBy('s.id');
661
662
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)))
663
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
664
			->andWhere($qb->expr()->orX(
665
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
666
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
667
			));
668
669
		// Filter by node if provided
670
		if ($node !== null) {
671
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
672
		}
673
674
		return $qb;
675
	}
676
677
	/*
678
	 * Get shared with group shares for the given groups and node
679
	 *
680
	 * @param IGroup[] $groups
681
	 * @param Node|null $node
682
	 * @return DB\QueryBuilder\IQueryBuilder $qb
683
	 */
684
	private function getSharedWithGroupQuery($groups, $node) {
685
		$qb = $this->dbConn->getQueryBuilder();
686
		$qb->select('s.*', 'f.fileid', 'f.path')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'f.fileid'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
687
			->selectAlias('st.id', 'storage_string_id')
688
			->from('share', 's')
689
			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
690
			->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
691
			->orderBy('s.id');
692
693
		// Filter by node if provided
694
		if ($node !== null) {
695
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
696
		}
697
698
		$groups = \array_map(function (IGroup $group) {
699
			return $group->getGID();
700
		}, $groups);
701
702
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
703
			->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter(
704
				$groups,
705
				IQueryBuilder::PARAM_STR_ARRAY
706
			)))
707
			->andWhere($qb->expr()->orX(
708
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
709
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
710
			));
711
712
		return $qb;
713
	}
714
715
	/*
716
	 * Get shared with group and shared with user shares for the given groups, userId and node
717
	 *
718
	 * @param IGroup[] $groups
719
	 * @param string $userId
720
	 * @param Node|null $node
721
	 * @return DB\QueryBuilder\IQueryBuilder $qb
722
	 */
723
	private function getSharedWithUserGroupQuery($groups, $userId, $node) {
724
		$qb = $this->dbConn->getQueryBuilder();
725
		$qb->select('s.*', 'f.fileid', 'f.path')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'f.fileid'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
726
			->selectAlias('st.id', 'storage_string_id')
727
			->from('share', 's')
728
			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
729
			->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
730
			->orderBy('s.id');
731
732
		// Filter by node if provided
733
		if ($node !== null) {
734
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
735
		}
736
737
		$groups = \array_map(function (IGroup $group) {
738
			return $group->getGID();
739
		}, $groups);
740
741
		$qb->andWhere($qb->expr()->orX(
742
			$qb->expr()->andX(
743
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)),
744
				$qb->expr()->in('share_with', $qb->createNamedParameter(
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::andX() has too many arguments starting with $qb->expr()->in('share_w...lder::PARAM_STR_ARRAY)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
745
					$groups,
746
					IQueryBuilder::PARAM_STR_ARRAY
747
				))
748
			),
749
			$qb->expr()->andX(
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->andX($qb->e...medParameter($userId))).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
750
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)),
751
				$qb->expr()->eq('share_with', $qb->createNamedParameter($userId))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::andX() has too many arguments starting with $qb->expr()->eq('share_w...amedParameter($userId)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
752
			)
753
		));
754
755
		return $qb;
756
	}
757
758
	/**
759
	 * @inheritdoc
760
	 */
761
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
762
		/** @var Share[] $shares */
763
		$shares = [];
764
765
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
766
			// Create SharedWithUser query
767
			$qb = $this->getSharedWithUserQuery($userId, $node);
768
769
			// Set limit and offset
770
			if ($limit !== -1) {
771
				$qb->setMaxResults($limit);
772
			}
773
			$qb->setFirstResult($offset);
774
775
			$cursor = $qb->execute();
776
777
			while ($data = $cursor->fetch()) {
778
				if ($this->isAccessibleResult($data)) {
779
					$shares[] = $this->createShare($data);
780
				}
781
			}
782
			$cursor->closeCursor();
783
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
784
			$user = $this->userManager->get($userId);
785
			$allGroups = $this->groupManager->getUserGroups($user, 'sharing');
786
787
			/** @var Share[] $shares2 */
788
			$shares2 = [];
789
790
			$start = 0;
791
			while (true) {
792
				$groups = \array_slice($allGroups, $start, 100);
793
				$start += 100;
794
795
				if ($groups === []) {
796
					break;
797
				}
798
799
				// Create SharedWithGroups query
800
				$qb = $this->getSharedWithGroupQuery($groups, $node);
801
				$qb->setFirstResult(0);
802
803
				if ($limit !== -1) {
804
					$qb->setMaxResults($limit - \count($shares));
805
				}
806
807
				$cursor = $qb->execute();
808
				while ($data = $cursor->fetch()) {
809
					if ($offset > 0) {
810
						$offset--;
811
						continue;
812
					}
813
814
					if ($this->isAccessibleResult($data)) {
815
						$shares2[] = $this->createShare($data);
816
					}
817
				}
818
				$cursor->closeCursor();
819
			}
820
821
			//Resolve all group shares to user specific shares
822
			if (!empty($shares2)) {
823
				$resolvedGroupShares = $this->resolveGroupShares($shares2, $userId);
824
				$shares = \array_merge($shares, $resolvedGroupShares);
825
			}
826
		} else {
827
			throw new BackendError('Invalid backend');
828
		}
829
830
		return $shares;
831
	}
832
833
	/**
834
	 * @inheritdoc
835
	 */
836
	public function getAllSharedWith($userId, $node) {
837
		// Create array of sharedWith objects (target user -> $userId or group of which user is a member
838
		$user = $this->userManager->get($userId);
839
840
		// Check if user is member of some groups and chunk them
841
		$allGroups = $this->groupManager->getUserGroups($user, 'sharing');
842
843
		// Make chunks
844
		$sharedWithGroupChunks = \array_chunk($allGroups, 100);
845
846
		// Check how many group chunks do we need
847
		$sharedWithGroupChunksNo = \count($sharedWithGroupChunks);
848
849
		// If there are not groups, query only user, if there are groups, query both
850
		$chunkedResults = [];
851
		if ($sharedWithGroupChunksNo === 0) {
852
			// There are no groups, query only for user
853
			$qb = $this->getSharedWithUserQuery($userId, $node);
854
			$cursor = $qb->execute();
855
			$chunkedResults[] = $cursor->fetchAll();
856
			$cursor->closeCursor();
857
		} else {
858
			// There are groups, query both for user and for groups
859
			$userSharesRetrieved = false;
860
			for ($chunkNo = 0; $chunkNo < $sharedWithGroupChunksNo; $chunkNo++) {
861
				// Get respective group chunk
862
				$groups = $sharedWithGroupChunks[$chunkNo];
863
864
				// Check if user shares were already retrieved
865
				// One cannot retrieve user shares multiple times, since it will result in duplicated
866
				// user shares with each query
867
				if ($userSharesRetrieved === false) {
868
					$qb = $this->getSharedWithUserGroupQuery($groups, $userId, $node);
869
					$userSharesRetrieved = true;
870
				} else {
871
					$qb = $this->getSharedWithGroupQuery($groups, $node);
872
				}
873
				$cursor = $qb->execute();
874
				$chunkedResults[] = $cursor->fetchAll();
875
				$cursor->closeCursor();
876
			}
877
		}
878
879
		$resolvedShares = [];
880
		$groupShares = [];
881
		foreach ($chunkedResults as $resultBatch) {
882
			foreach ($resultBatch as $data) {
883
				if ($this->isAccessibleResult($data)) {
884
					$share = $this->createShare($data);
885
					if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
886
						$groupShares[] = $share;
887
					} else {
888
						$resolvedShares[] = $share;
889
					}
890
				}
891
			}
892
		}
893
894
		//Resolve all group shares to user specific shares
895
		if (!empty($groupShares)) {
896
			$resolvedGroupShares = $this->resolveGroupShares($groupShares, $userId);
897
			$resolvedShares = \array_merge($resolvedShares, $resolvedGroupShares);
898
		}
899
900
		return $resolvedShares;
901
	}
902
903
	/**
904
	 * Get a share by token
905
	 *
906
	 * @param string $token
907
	 * @return \OCP\Share\IShare
908
	 * @throws ShareNotFound
909
	 */
910
	public function getShareByToken($token) {
911
		$qb = $this->dbConn->getQueryBuilder();
912
913
		$cursor = $qb->select('*')
914
			->from('share')
915
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)))
916
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
917
			->andWhere($qb->expr()->orX(
918
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
919
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
920
			))
921
			->execute();
922
923
		$data = $cursor->fetch();
924
925
		if ($data === false) {
926
			throw new ShareNotFound();
927
		}
928
929
		try {
930
			$share = $this->createShare($data);
931
		} catch (InvalidShare $e) {
932
			throw new ShareNotFound();
933
		}
934
935
		return $share;
936
	}
937
	
938
	/**
939
	 * Create a share object from an database row
940
	 *
941
	 * @param mixed[] $data
942
	 * @return \OCP\Share\IShare
943
	 * @throws InvalidShare
944
	 */
945
	private function createShare($data) {
946
		$share = new Share($this->rootFolder, $this->userManager);
947
		$share->setId((int)$data['id'])
948
			->setShareType((int)$data['share_type'])
949
			->setPermissions((int)$data['permissions'])
950
			->setTarget($data['file_target'])
951
			->setMailSend((bool)$data['mail_send']);
952
953
		$shareTime = new \DateTime();
954
		$shareTime->setTimestamp((int)$data['stime']);
955
		$share->setShareTime($shareTime);
956
957
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
958
			$share->setSharedWith($data['share_with']);
959
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
960
			$share->setSharedWith($data['share_with']);
961
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
962
			$share->setPassword($data['share_with']);
963
			$share->setToken($data['token']);
964
		}
965
966
		$share->setSharedBy($data['uid_initiator']);
967
		$share->setShareOwner($data['uid_owner']);
968
969
		$share->setNodeId((int)$data['file_source']);
970
		$share->setNodeType($data['item_type']);
971
		$share->setName($data['share_name']);
972
		$share->setState((int)$data['accepted']);
973
974
		if ($data['expiration'] !== null) {
975
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
976
			$share->setExpirationDate($expiration);
0 ignored issues
show
Security Bug introduced by
It seems like $expiration defined by \DateTime::createFromFor...', $data['expiration']) on line 975 can also be of type false; however, OC\Share20\Share::setExpirationDate() does only seem to accept object<DateTime>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
977
		}
978
979
		$share->setProviderId($this->identifier());
980
981
		return $share;
982
	}
983
984
	/**
985
	 * Will return two maps:
986
	 * - $chunkedShareIds responsible to split shareIds into chunks containing 100 elements
987
	 * 	 e.g. $chunkedShareIds { { "4", "52", "54",... }[100], { .. }[2] }[2]
988
	 *
989
	 * - $shareIdToShareMap responsible to split shareIds into chunks containing 100 elements
990
	 * 	 e.g. $shareIdToShareMap { "4" => IShare, "52" => IShare, "54" => IShare, ... }[102]
991
	 *
992
	 * @param \OCP\Share\IShare[] $shares
993
	 * @return array $chunkedSharesToMaps e.g { $chunkedShareIds, $shareIdToShareMap }[2]
994
	 */
995
	private function chunkSharesToMaps($shares) {
996
		$chunkedShareIds = [];
997
		$shareIdToShareMap = [];
998
		$chunkId = 0;
999
		$shareNo = 0;
1000
		foreach ($shares as $share) {
1001
			// Map unique shareIds to IShare
1002
			$shareId = $share->getId();
1003
			$shareIdToShareMap[$shareId] = $share;
1004
1005
			// Chunk shareId array
1006
			if ($shareNo >= 100) {
1007
				// If we have over 100 shares in the array, start next chunk
1008
				$shareNo = 0;
1009
				$chunkId++;
1010
			} else {
1011
				// Increase number of shares in current array
1012
				$shareNo++;
1013
			}
1014
			$chunkedShareIds[$chunkId][] = $shareId;
1015
		}
1016
1017
		$chunkedSharesToMaps = [$chunkedShareIds, $shareIdToShareMap];
1018
		return $chunkedSharesToMaps;
1019
	}
1020
1021
	/**
1022
	 * Resolve a group shares to a user specific share.
1023
	 * Thus if the user moved their group share make sure this is properly reflected here,
1024
	 * If $shares array contains exactly 2 elements, where
1025
	 * only 1 will be changed(resolved), it returns exactly 2 elements, containing the resolved one.
1026
	 *
1027
	 * @param \OCP\Share\IShare[] $shares e.g. { IShare, IShare }[2]
1028
	 * @param string $userId
1029
	 * @return \OCP\Share\IShare[] $resolvedShares
1030
	 * @throws ProviderException
1031
	 */
1032
	private function resolveGroupShares($shares, $userId) {
1033
		$qb = $this->dbConn->getQueryBuilder();
1034
1035
		list($chunkedShareIds, $shareIdToShareMap) = $this->chunkSharesToMaps($shares);
1036
		foreach ($chunkedShareIds as $shareIdsChunk) {
1037
			$qb->select('*')
1038
				->from('share')
1039
				->where($qb->expr()->in('parent', $qb->createNamedParameter(
1040
					$shareIdsChunk,
1041
					IQueryBuilder::PARAM_STR_ARRAY
1042
				)))
1043
				->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
1044
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
1045
				->andWhere($qb->expr()->orX(
1046
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1047
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('item_ty...medParameter('folder')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1048
				));
1049
1050
			$stmt = $qb->execute();
1051
1052
			// Resolve $shareIdToShareMap array containing group shares
1053
			$shareParents = [];
1054
			while ($data = $stmt->fetch()) {
1055
				// Get share parent
1056
				$shareParent = $data['parent'];
1057
1058
				// Ensure uniquenes of parents
1059
				if (!isset($shareParents[$shareParent])) {
1060
					$shareParents[$shareParent] = true;
1061
				} else {
1062
					throw new ProviderException('Parent of share should be unique');
1063
				}
1064
1065
				// Resolve only shares contained in the map.
1066
				// This will ensure that we return the same amount of shares in the input as in the output
1067
				// If $shareParent is contained in $shareIdToShareMap, it means that needs resolving
1068
				if (isset($shareIdToShareMap[$shareParent])) {
1069
					$share = $shareIdToShareMap[$shareParent];
1070
					$share->setState(\intval($data['accepted']));
1071
					$share->setTarget($data['file_target']);
1072
				}
1073
			}
1074
			$stmt->closeCursor();
1075
		}
1076
		
1077
		$resolvedShares = \array_values($shareIdToShareMap);
1078
		return $resolvedShares;
1079
	}
1080
1081
	/**
1082
	 * A user is deleted from the system
1083
	 * So clean up the relevant shares.
1084
	 *
1085
	 * @param string $uid
1086
	 * @param int $shareType
1087
	 */
1088
	public function userDeleted($uid, $shareType) {
1089
		$qb = $this->dbConn->getQueryBuilder();
1090
1091
		$qb->delete('share');
1092
1093
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
1094
			/*
1095
			 * Delete all user shares that are owned by this user
1096
			 * or that are received by this user
1097
			 */
1098
1099
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)));
1100
1101
			$qb->andWhere(
1102
				$qb->expr()->orX(
1103
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1104
					$qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('share_w...teNamedParameter($uid)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1105
				)
1106
			);
1107
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
1108
			/*
1109
			 * Delete all group shares that are owned by this user
1110
			 * Or special user group shares that are received by this user
1111
			 */
1112
			$qb->where(
1113
				$qb->expr()->andX(
1114
					$qb->expr()->orX(
1115
						$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)),
1116
						$qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('share_t...:SHARE_TYPE_USERGROUP)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1117
					),
1118
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::andX() has too many arguments starting with $qb->expr()->eq('uid_own...teNamedParameter($uid)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1119
				)
1120
			);
1121
1122
			$qb->orWhere(
1123
				$qb->expr()->andX(
1124
					$qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)),
1125
					$qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::andX() has too many arguments starting with $qb->expr()->eq('share_w...teNamedParameter($uid)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1126
				)
1127
			);
1128
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
1129
			/*
1130
			 * Delete all link shares owned by this user.
1131
			 * And all link shares initiated by this user (until #22327 is in)
1132
			 */
1133
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)));
1134
1135
			$qb->andWhere(
1136
				$qb->expr()->orX(
1137
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1138
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($uid))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('uid_ini...teNamedParameter($uid)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1139
				)
1140
			);
1141
		}
1142
1143
		$qb->execute();
1144
	}
1145
1146
	/**
1147
	 * Delete all shares received by this group. As well as any custom group
1148
	 * shares for group members.
1149
	 *
1150
	 * @param string $gid
1151
	 */
1152
	public function groupDeleted($gid) {
1153
		/*
1154
		 * First delete all custom group shares for group members
1155
		 */
1156
		$qb = $this->dbConn->getQueryBuilder();
1157
		$qb->select('id')
1158
			->from('share')
1159
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
1160
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1161
1162
		$cursor = $qb->execute();
1163
		$ids = [];
1164
		while ($row = $cursor->fetch()) {
1165
			$ids[] = (int)$row['id'];
1166
		}
1167
		$cursor->closeCursor();
1168
1169
		if (!empty($ids)) {
1170
			$chunks = \array_chunk($ids, 100);
1171
			foreach ($chunks as $chunk) {
1172
				$qb->delete('share')
1173
					->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
1174
					->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
1175
				$qb->execute();
1176
			}
1177
		}
1178
1179
		/*
1180
		 * Now delete all the group shares
1181
		 */
1182
		$qb = $this->dbConn->getQueryBuilder();
1183
		$qb->delete('share')
1184
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
1185
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1186
		$qb->execute();
1187
	}
1188
1189
	/**
1190
	 * Delete custom group shares to this group for this user
1191
	 *
1192
	 * @param string $uid
1193
	 * @param string $gid
1194
	 */
1195
	public function userDeletedFromGroup($uid, $gid) {
1196
		/*
1197
		 * Get all group shares
1198
		 */
1199
		$qb = $this->dbConn->getQueryBuilder();
1200
		$qb->select('id')
1201
			->from('share')
1202
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
1203
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1204
1205
		$cursor = $qb->execute();
1206
		$ids = [];
1207
		while ($row = $cursor->fetch()) {
1208
			$ids[] = (int)$row['id'];
1209
		}
1210
		$cursor->closeCursor();
1211
1212 View Code Duplication
		if (!empty($ids)) {
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...
1213
			$chunks = \array_chunk($ids, 100);
1214
			foreach ($chunks as $chunk) {
1215
				/*
1216
				 * Delete all special shares wit this users for the found group shares
1217
				 */
1218
				$qb->delete('share')
1219
					->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
1220
					->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid)))
1221
					->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
1222
				$qb->execute();
1223
			}
1224
		}
1225
	}
1226
1227
	/**
1228
	 * Check whether the share object fits the expectations of this provider
1229
	 *
1230
	 * @param IShare $share share
1231
	 *
1232
	 * @throws InvalidArgumentException if the share validation failed
1233
	 */
1234
	private function validate($share) {
1235
		if ($share->getName() !== null && \strlen($share->getName()) > 64) {
1236
			throw new \InvalidArgumentException('Share name cannot be more than 64 characters');
1237
		}
1238
1239
		// TODO: add more early validation for fields instead of relying on the DB
1240
	}
1241
}
1242