Completed
Push — master ( e4992c...6d0a35 )
by
unknown
10:42
created

DefaultShareProvider::deleteFromSelf()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
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
				->execute();
216
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
217
			$qb = $this->dbConn->getQueryBuilder();
218
			$qb->update('share')
219
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
220
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
221
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
222
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
223
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
224
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
225
				->set('accepted', $qb->createNamedParameter($share->getState()))
226
				->execute();
227
228
			/*
229
			 * Update all user defined group shares
230
			 */
231
			$qb = $this->dbConn->getQueryBuilder();
232
			$qb->update('share')
233
				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
234
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
235
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
236
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
237
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
238
				->execute();
239
240
			/*
241
			 * Now update the permissions for all children
242
			 */
243
			$qb = $this->dbConn->getQueryBuilder();
244
			$qb->update('share')
245
				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
246
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
247
				->execute();
248
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
249
			$qb = $this->dbConn->getQueryBuilder();
250
			$qb->update('share')
251
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
252
				->set('share_with', $qb->createNamedParameter($share->getPassword()))
253
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
254
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
255
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
256
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
257
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
258
				->set('token', $qb->createNamedParameter($share->getToken()))
259
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
260
				->set('share_name', $qb->createNamedParameter($share->getName()))
261
				->execute();
262
		}
263
264
		return $share;
265
	}
266
267
	/**
268
	 * Get all children of this share
269
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
270
	 *
271
	 * @param \OCP\Share\IShare $parent
272
	 * @return \OCP\Share\IShare[]
273
	 */
274
	public function getChildren(\OCP\Share\IShare $parent) {
275
		$children = [];
276
277
		$qb = $this->dbConn->getQueryBuilder();
278
		$qb->select('*')
279
			->from('share')
280
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
281
			->andWhere(
282
				$qb->expr()->in(
283
					'share_type',
284
					$qb->createNamedParameter([
285
						\OCP\Share::SHARE_TYPE_USER,
286
						\OCP\Share::SHARE_TYPE_GROUP,
287
						\OCP\Share::SHARE_TYPE_LINK,
288
					], IQueryBuilder::PARAM_INT_ARRAY)
289
				)
290
			)
291
			->andWhere($qb->expr()->orX(
292
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
293
				$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...
294
			))
295
			->orderBy('id');
296
297
		$cursor = $qb->execute();
298
		while ($data = $cursor->fetch()) {
299
			$children[] = $this->createShare($data);
300
		}
301
		$cursor->closeCursor();
302
303
		return $children;
304
	}
305
306
	/**
307
	 * Delete a share
308
	 *
309
	 * @param \OCP\Share\IShare $share
310
	 */
311
	public function delete(\OCP\Share\IShare $share) {
312
		$qb = $this->dbConn->getQueryBuilder();
313
		$qb->delete('share')
314
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
315
316
		/*
317
		 * If the share is a group share delete all possible
318
		 * user defined groups shares.
319
		 */
320
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
321
			$qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
322
		}
323
324
		$qb->execute();
325
	}
326
327
	/**
328
	 * Unshare a share from the recipient. If this is a group share
329
	 * this means we need a special entry in the share db.
330
	 *
331
	 * @param \OCP\Share\IShare $share
332
	 * @param string $recipient UserId of recipient
333
	 * @throws BackendError
334
	 * @throws ProviderException
335
	 */
336
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipient) {
337
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP || $share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
338
			$share->setState(\OCP\Share::STATE_REJECTED);
339
			$this->updateForRecipient($share, $recipient);
340
		} else {
341
			throw new ProviderException('Invalid share type ' . $share->getShareType());
342
		}
343
	}
344
345
	/**
346
	 * @inheritdoc
347
	 */
348
	public function move(\OCP\Share\IShare $share, $recipient) {
349
		return $this->updateForRecipient($share, $recipient);
350
	}
351
352
	/**
353
	 * @inheritdoc
354
	 */
355
	public function updateForRecipient(\OCP\Share\IShare $share, $recipient) {
356
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
357
			if ($share->getSharedWith() !== $recipient) {
358
				throw new ProviderException('Recipient does not match');
359
			}
360
361
			// Just update the target
362
			$qb = $this->dbConn->getQueryBuilder();
363
			$qb->update('share')
364
				->set('accepted', $qb->createNamedParameter($share->getState()))
365
				->set('file_target', $qb->createNamedParameter($share->getTarget()))
366
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
367
				->execute();
368
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
369
			$group = $this->groupManager->get($share->getSharedWith());
370
			$user = $this->userManager->get($recipient);
371
372
			if ($group === null) {
373
				throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
374
			}
375
376
			if (!$group->inGroup($user)) {
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($recipient) on line 370 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...
377
				throw new ProviderException('Recipient not in receiving group');
378
			}
379
380
			// Check if there is a usergroup share
381
			$qb = $this->dbConn->getQueryBuilder();
382
			$stmt = $qb->select('id')
383
				->from('share')
384
				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
385
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
386
				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
387
				->andWhere($qb->expr()->orX(
388
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
389
					$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...
390
				))
391
				->setMaxResults(1)
392
				->execute();
393
394
			$data = $stmt->fetch();
395
			$stmt->closeCursor();
396
397
			if ($data === false) {
398
				// No usergroup share yet. Create one.
399
				$qb = $this->dbConn->getQueryBuilder();
400
				$qb->insert('share')
401
					->values([
402
						'share_type' => $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP),
403
						'share_with' => $qb->createNamedParameter($recipient),
404
						'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
405
						'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
406
						'parent' => $qb->createNamedParameter($share->getId()),
407
						'item_type' => $qb->createNamedParameter($share->getNode() instanceof File ? 'file' : 'folder'),
408
						'item_source' => $qb->createNamedParameter($share->getNode()->getId()),
409
						'file_source' => $qb->createNamedParameter($share->getNode()->getId()),
410
						'file_target' => $qb->createNamedParameter($share->getTarget()),
411
						'permissions' => $qb->createNamedParameter($share->getPermissions()),
412
						'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
413
						'accepted' => $qb->createNamedParameter($share->getState()),
414
					])->execute();
415
			} else {
416
				// Already a usergroup share. Update it.
417
				$qb = $this->dbConn->getQueryBuilder();
418
				$qb->update('share')
419
					->set('accepted', $qb->createNamedParameter($share->getState()))
420
					->set('file_target', $qb->createNamedParameter($share->getTarget()))
421
					// make sure to reset the permissions to the one of the parent share,
422
					// as legacy entries with zero permissions might still exist
423
					->set('permissions', $qb->createNamedParameter($share->getPermissions()))
424
					->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
425
					->execute();
426
			}
427
		} else {
428
			throw new ProviderException('Can\'t update share of recipient for share type ' . $share->getShareType());
429
		}
430
431
		return $share;
432
	}
433
434
	/**
435
	 * @inheritdoc
436
	 */
437
	public function getAllSharesBy($userId, $shareTypes, $nodeIDs, $reshares) {
438
		$shares = [];
439
		$qb = $this->dbConn->getQueryBuilder();
440
441
		$nodeIdsChunks = \array_chunk($nodeIDs, 100);
442
		foreach ($nodeIdsChunks as $nodeIdsChunk) {
443
			$qb->select('*')
444
				->from('share')
445
				->andWhere($qb->expr()->orX(
446
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
447
					$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...
448
				));
449
450
			$orX = $qb->expr()->orX();
451
452
			foreach ($shareTypes as $shareType) {
453
				$orX->add($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
454
			}
455
456
			$qb->andWhere($orX);
457
458
			/**
459
			 * Reshares for this user are shares where they are the owner.
460
			 */
461
			if ($reshares === false) {
462
				$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
463
			} else {
464
				$qb->andWhere(
465
					$qb->expr()->orX(
466
						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
467
						$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...
468
					)
469
				);
470
			}
471
472
			$qb->andWhere($qb->expr()->in('file_source', $qb->createParameter('file_source_ids')));
473
			$qb->setParameter('file_source_ids', $nodeIdsChunk, IQueryBuilder::PARAM_INT_ARRAY);
474
475
			$qb->orderBy('id');
476
477
			$cursor = $qb->execute();
478
			while ($data = $cursor->fetch()) {
479
				$shares[] = $this->createShare($data);
480
			}
481
			$cursor->closeCursor();
482
		}
483
		
484
		return $shares;
485
	}
486
487
	/**
488
	 * @inheritdoc
489
	 */
490
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
491
		$qb = $this->dbConn->getQueryBuilder();
492
		$qb->select('*')
493
			->from('share')
494
			->andWhere($qb->expr()->orX(
495
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
496
				$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...
497
			));
498
499
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
500
501
		/**
502
		 * Reshares for this user are shares where they are the owner.
503
		 */
504
		if ($reshares === false) {
505
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
506
		} else {
507
			$qb->andWhere(
508
				$qb->expr()->orX(
509
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
510
					$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...
511
				)
512
			);
513
		}
514
515
		if ($node !== null) {
516
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
517
		}
518
519
		if ($limit !== -1) {
520
			$qb->setMaxResults($limit);
521
		}
522
523
		$qb->setFirstResult($offset);
524
		$qb->orderBy('id');
525
526
		$cursor = $qb->execute();
527
		$shares = [];
528
		while ($data = $cursor->fetch()) {
529
			$shares[] = $this->createShare($data);
530
		}
531
		$cursor->closeCursor();
532
533
		return $shares;
534
	}
535
536
	/**
537
	 * @inheritdoc
538
	 */
539
	public function getShareById($id, $recipientId = null) {
540
		$qb = $this->dbConn->getQueryBuilder();
541
542
		$qb->select('*')
543
			->from('share')
544
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
545
			->andWhere(
546
				$qb->expr()->in(
547
					'share_type',
548
					$qb->createNamedParameter([
549
						\OCP\Share::SHARE_TYPE_USER,
550
						\OCP\Share::SHARE_TYPE_GROUP,
551
						\OCP\Share::SHARE_TYPE_LINK,
552
					], IQueryBuilder::PARAM_INT_ARRAY)
553
				)
554
			)
555
			->andWhere($qb->expr()->orX(
556
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
557
				$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...
558
			));
559
		
560
		$cursor = $qb->execute();
561
		$data = $cursor->fetch();
562
		$cursor->closeCursor();
563
564
		if ($data === false) {
565
			throw new ShareNotFound();
566
		}
567
568
		try {
569
			$share = $this->createShare($data);
570
		} catch (InvalidShare $e) {
571
			throw new ShareNotFound();
572
		}
573
574
		// If the recipient is set for a group share resolve to that user
575
		if ($recipientId !== null && $share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
576
			$resolvedShares = $this->resolveGroupShares([$share], $recipientId);
577
			if (\count($resolvedShares) === 1) {
578
				// If we pass to resolveGroupShares() an with one element,
579
				// we expect to receive exactly one element, otherwise it is error
580
				$share = $resolvedShares[0];
581
			} else {
582
				throw new ProviderException("ResolveGroupShares() returned wrong result");
583
			}
584
		}
585
586
		return $share;
587
	}
588
589
	/**
590
	 * Get shares for a given path
591
	 *
592
	 * @param \OCP\Files\Node $path
593
	 * @return \OCP\Share\IShare[]
594
	 */
595
	public function getSharesByPath(Node $path) {
596
		$qb = $this->dbConn->getQueryBuilder();
597
598
		$cursor = $qb->select('*')
599
			->from('share')
600
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
601
			->andWhere(
602
				$qb->expr()->orX(
603
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)),
604
					$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...
605
				)
606
			)
607
			->andWhere($qb->expr()->orX(
608
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
609
				$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...
610
			))
611
			->execute();
612
613
		$shares = [];
614
		while ($data = $cursor->fetch()) {
615
			$shares[] = $this->createShare($data);
616
		}
617
		$cursor->closeCursor();
618
619
		return $shares;
620
	}
621
622
	/**
623
	 * Returns whether the given database result can be interpreted as
624
	 * a share with accessible file (not trashed, not deleted)
625
	 */
626
	private function isAccessibleResult($data) {
627
		// exclude shares leading to deleted file entries
628
		if ($data['fileid'] === null) {
629
			return false;
630
		}
631
632
		// exclude shares leading to trashbin on home storages
633
		$pathSections = \explode('/', $data['path'], 2);
634
		// FIXME: would not detect rare md5'd home storage case properly
635
		$storagePrefix = \explode(':', $data['storage_string_id'], 2)[0];
636
		if ($pathSections[0] !== 'files' && \in_array($storagePrefix, ['home', 'object'], true)) {
637
			return false;
638
		}
639
		return true;
640
	}
641
642
	/*
643
	 * Get shared with user shares for the given userId and node
644
	 *
645
	 * @param string $userId
646
	 * @param Node|null $node
647
	 * @return DB\QueryBuilder\IQueryBuilder $qb
648
	 */
649
	public function getSharedWithUserQuery($userId, $node) {
650
		$qb = $this->dbConn->getQueryBuilder();
651
		$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...
652
			->selectAlias('st.id', 'storage_string_id')
653
			->from('share', 's')
654
			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
655
			->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'));
656
657
		// Order by id
658
		$qb->orderBy('s.id');
659
660
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)))
661
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
662
			->andWhere($qb->expr()->orX(
663
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
664
				$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...
665
			));
666
667
		// Filter by node if provided
668
		if ($node !== null) {
669
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
670
		}
671
672
		return $qb;
673
	}
674
675
	/*
676
	 * Get shared with group shares for the given groups and node
677
	 *
678
	 * @param IGroup[] $groups
679
	 * @param Node|null $node
680
	 * @return DB\QueryBuilder\IQueryBuilder $qb
681
	 */
682
	private function getSharedWithGroupQuery($groups, $node) {
683
		$qb = $this->dbConn->getQueryBuilder();
684
		$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...
685
			->selectAlias('st.id', 'storage_string_id')
686
			->from('share', 's')
687
			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
688
			->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
689
			->orderBy('s.id');
690
691
		// Filter by node if provided
692
		if ($node !== null) {
693
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
694
		}
695
696
		$groups = \array_map(function (IGroup $group) {
697
			return $group->getGID();
698
		}, $groups);
699
700
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
701
			->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter(
702
				$groups,
703
				IQueryBuilder::PARAM_STR_ARRAY
704
			)))
705
			->andWhere($qb->expr()->orX(
706
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
707
				$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...
708
			));
709
710
		return $qb;
711
	}
712
713
	/*
714
	 * Get shared with group and shared with user shares for the given groups, userId and node
715
	 *
716
	 * @param IGroup[] $groups
717
	 * @param string $userId
718
	 * @param Node|null $node
719
	 * @return DB\QueryBuilder\IQueryBuilder $qb
720
	 */
721
	private function getSharedWithUserGroupQuery($groups, $userId, $node) {
722
		$qb = $this->dbConn->getQueryBuilder();
723
		$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...
724
			->selectAlias('st.id', 'storage_string_id')
725
			->from('share', 's')
726
			->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
727
			->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
728
			->orderBy('s.id');
729
730
		// Filter by node if provided
731
		if ($node !== null) {
732
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
733
		}
734
735
		$groups = \array_map(function (IGroup $group) {
736
			return $group->getGID();
737
		}, $groups);
738
739
		$qb->andWhere($qb->expr()->orX(
740
			$qb->expr()->andX(
741
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)),
742
				$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...
743
					$groups,
744
					IQueryBuilder::PARAM_STR_ARRAY
745
				))
746
			),
747
			$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...
748
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)),
749
				$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...
750
			)
751
		));
752
753
		return $qb;
754
	}
755
756
	/**
757
	 * @inheritdoc
758
	 */
759
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
760
		/** @var Share[] $shares */
761
		$shares = [];
762
763
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
764
			// Create SharedWithUser query
765
			$qb = $this->getSharedWithUserQuery($userId, $node);
766
767
			// Set limit and offset
768
			if ($limit !== -1) {
769
				$qb->setMaxResults($limit);
770
			}
771
			$qb->setFirstResult($offset);
772
773
			$cursor = $qb->execute();
774
775
			while ($data = $cursor->fetch()) {
776
				if ($this->isAccessibleResult($data)) {
777
					$shares[] = $this->createShare($data);
778
				}
779
			}
780
			$cursor->closeCursor();
781
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
782
			$user = $this->userManager->get($userId);
783
			$allGroups = $this->groupManager->getUserGroups($user, 'sharing');
784
785
			/** @var Share[] $shares2 */
786
			$shares2 = [];
787
788
			$start = 0;
789
			while (true) {
790
				$groups = \array_slice($allGroups, $start, 100);
791
				$start += 100;
792
793
				if ($groups === []) {
794
					break;
795
				}
796
797
				// Create SharedWithGroups query
798
				$qb = $this->getSharedWithGroupQuery($groups, $node);
799
				$qb->setFirstResult(0);
800
801
				if ($limit !== -1) {
802
					$qb->setMaxResults($limit - \count($shares));
803
				}
804
805
				$cursor = $qb->execute();
806
				while ($data = $cursor->fetch()) {
807
					if ($offset > 0) {
808
						$offset--;
809
						continue;
810
					}
811
812
					if ($this->isAccessibleResult($data)) {
813
						$shares2[] = $this->createShare($data);
814
					}
815
				}
816
				$cursor->closeCursor();
817
			}
818
819
			//Resolve all group shares to user specific shares
820
			if (!empty($shares2)) {
821
				$resolvedGroupShares = $this->resolveGroupShares($shares2, $userId);
822
				$shares = \array_merge($shares, $resolvedGroupShares);
823
			}
824
		} else {
825
			throw new BackendError('Invalid backend');
826
		}
827
828
		return $shares;
829
	}
830
831
	/**
832
	 * @inheritdoc
833
	 */
834
	public function getAllSharedWith($userId, $node) {
835
		// Create array of sharedWith objects (target user -> $userId or group of which user is a member
836
		$user = $this->userManager->get($userId);
837
838
		// Check if user is member of some groups and chunk them
839
		$allGroups = $this->groupManager->getUserGroups($user, 'sharing');
840
841
		// Make chunks
842
		$sharedWithGroupChunks = \array_chunk($allGroups, 100);
843
844
		// Check how many group chunks do we need
845
		$sharedWithGroupChunksNo = \count($sharedWithGroupChunks);
846
847
		// If there are not groups, query only user, if there are groups, query both
848
		$chunkedResults = [];
849
		if ($sharedWithGroupChunksNo === 0) {
850
			// There are no groups, query only for user
851
			$qb = $this->getSharedWithUserQuery($userId, $node);
852
			$cursor = $qb->execute();
853
			$chunkedResults[] = $cursor->fetchAll();
854
			$cursor->closeCursor();
855
		} else {
856
			// There are groups, query both for user and for groups
857
			$userSharesRetrieved = false;
858
			for ($chunkNo = 0; $chunkNo < $sharedWithGroupChunksNo; $chunkNo++) {
859
				// Get respective group chunk
860
				$groups = $sharedWithGroupChunks[$chunkNo];
861
862
				// Check if user shares were already retrieved
863
				// One cannot retrieve user shares multiple times, since it will result in duplicated
864
				// user shares with each query
865
				if ($userSharesRetrieved === false) {
866
					$qb = $this->getSharedWithUserGroupQuery($groups, $userId, $node);
867
					$userSharesRetrieved = true;
868
				} else {
869
					$qb = $this->getSharedWithGroupQuery($groups, $node);
870
				}
871
				$cursor = $qb->execute();
872
				$chunkedResults[] = $cursor->fetchAll();
873
				$cursor->closeCursor();
874
			}
875
		}
876
877
		$resolvedShares = [];
878
		$groupShares = [];
879
		foreach ($chunkedResults as $resultBatch) {
880
			foreach ($resultBatch as $data) {
881
				if ($this->isAccessibleResult($data)) {
882
					$share = $this->createShare($data);
883
					if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
884
						$groupShares[] = $share;
885
					} else {
886
						$resolvedShares[] = $share;
887
					}
888
				}
889
			}
890
		}
891
892
		//Resolve all group shares to user specific shares
893
		if (!empty($groupShares)) {
894
			$resolvedGroupShares = $this->resolveGroupShares($groupShares, $userId);
895
			$resolvedShares = \array_merge($resolvedShares, $resolvedGroupShares);
896
		}
897
898
		return $resolvedShares;
899
	}
900
901
	/**
902
	 * Get a share by token
903
	 *
904
	 * @param string $token
905
	 * @return \OCP\Share\IShare
906
	 * @throws ShareNotFound
907
	 */
908
	public function getShareByToken($token) {
909
		$qb = $this->dbConn->getQueryBuilder();
910
911
		$cursor = $qb->select('*')
912
			->from('share')
913
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)))
914
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
915
			->andWhere($qb->expr()->orX(
916
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
917
				$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...
918
			))
919
			->execute();
920
921
		$data = $cursor->fetch();
922
923
		if ($data === false) {
924
			throw new ShareNotFound();
925
		}
926
927
		try {
928
			$share = $this->createShare($data);
929
		} catch (InvalidShare $e) {
930
			throw new ShareNotFound();
931
		}
932
933
		return $share;
934
	}
935
	
936
	/**
937
	 * Create a share object from an database row
938
	 *
939
	 * @param mixed[] $data
940
	 * @return \OCP\Share\IShare
941
	 * @throws InvalidShare
942
	 */
943
	private function createShare($data) {
944
		$share = new Share($this->rootFolder, $this->userManager);
945
		$share->setId((int)$data['id'])
946
			->setShareType((int)$data['share_type'])
947
			->setPermissions((int)$data['permissions'])
948
			->setTarget($data['file_target'])
949
			->setMailSend((bool)$data['mail_send']);
950
951
		$shareTime = new \DateTime();
952
		$shareTime->setTimestamp((int)$data['stime']);
953
		$share->setShareTime($shareTime);
954
955
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
956
			$share->setSharedWith($data['share_with']);
957
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
958
			$share->setSharedWith($data['share_with']);
959
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
960
			$share->setPassword($data['share_with']);
961
			$share->setToken($data['token']);
962
		}
963
964
		$share->setSharedBy($data['uid_initiator']);
965
		$share->setShareOwner($data['uid_owner']);
966
967
		$share->setNodeId((int)$data['file_source']);
968
		$share->setNodeType($data['item_type']);
969
		$share->setName($data['share_name']);
970
		$share->setState((int)$data['accepted']);
971
972
		if ($data['expiration'] !== null) {
973
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
974
			$share->setExpirationDate($expiration);
0 ignored issues
show
Security Bug introduced by
It seems like $expiration defined by \DateTime::createFromFor...', $data['expiration']) on line 973 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...
975
		}
976
977
		$share->setProviderId($this->identifier());
978
979
		return $share;
980
	}
981
982
	/**
983
	 * Will return two maps:
984
	 * - $chunkedShareIds responsible to split shareIds into chunks containing 100 elements
985
	 * 	 e.g. $chunkedShareIds { { "4", "52", "54",... }[100], { .. }[2] }[2]
986
	 *
987
	 * - $shareIdToShareMap responsible to split shareIds into chunks containing 100 elements
988
	 * 	 e.g. $shareIdToShareMap { "4" => IShare, "52" => IShare, "54" => IShare, ... }[102]
989
	 *
990
	 * @param \OCP\Share\IShare[] $shares
991
	 * @return array $chunkedSharesToMaps e.g { $chunkedShareIds, $shareIdToShareMap }[2]
992
	 */
993
	private function chunkSharesToMaps($shares) {
994
		$chunkedShareIds = [];
995
		$shareIdToShareMap = [];
996
		$chunkId = 0;
997
		$shareNo = 0;
998
		foreach ($shares as $share) {
999
			// Map unique shareIds to IShare
1000
			$shareId = $share->getId();
1001
			$shareIdToShareMap[$shareId] = $share;
1002
1003
			// Chunk shareId array
1004
			if ($shareNo >= 100) {
1005
				// If we have over 100 shares in the array, start next chunk
1006
				$shareNo = 0;
1007
				$chunkId++;
1008
			} else {
1009
				// Increase number of shares in current array
1010
				$shareNo++;
1011
			}
1012
			$chunkedShareIds[$chunkId][] = $shareId;
1013
		}
1014
1015
		$chunkedSharesToMaps = [$chunkedShareIds, $shareIdToShareMap];
1016
		return $chunkedSharesToMaps;
1017
	}
1018
1019
	/**
1020
	 * Resolve a group shares to a user specific share.
1021
	 * Thus if the user moved their group share make sure this is properly reflected here,
1022
	 * If $shares array contains exactly 2 elements, where
1023
	 * only 1 will be changed(resolved), it returns exactly 2 elements, containing the resolved one.
1024
	 *
1025
	 * @param \OCP\Share\IShare[] $shares e.g. { IShare, IShare }[2]
1026
	 * @param string $userId
1027
	 * @return \OCP\Share\IShare[] $resolvedShares
1028
	 * @throws ProviderException
1029
	 */
1030
	private function resolveGroupShares($shares, $userId) {
1031
		$qb = $this->dbConn->getQueryBuilder();
1032
1033
		list($chunkedShareIds, $shareIdToShareMap) = $this->chunkSharesToMaps($shares);
1034
		foreach ($chunkedShareIds as $shareIdsChunk) {
1035
			$qb->select('*')
1036
				->from('share')
1037
				->where($qb->expr()->in('parent', $qb->createNamedParameter(
1038
					$shareIdsChunk,
1039
					IQueryBuilder::PARAM_STR_ARRAY
1040
				)))
1041
				->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
1042
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
1043
				->andWhere($qb->expr()->orX(
1044
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1045
					$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...
1046
				));
1047
1048
			$stmt = $qb->execute();
1049
1050
			// Resolve $shareIdToShareMap array containing group shares
1051
			$shareParents = [];
1052
			while ($data = $stmt->fetch()) {
1053
				// Get share parent
1054
				$shareParent = $data['parent'];
1055
1056
				// Ensure uniquenes of parents
1057
				if (!isset($shareParents[$shareParent])) {
1058
					$shareParents[$shareParent] = true;
1059
				} else {
1060
					throw new ProviderException('Parent of share should be unique');
1061
				}
1062
1063
				// Resolve only shares contained in the map.
1064
				// This will ensure that we return the same amount of shares in the input as in the output
1065
				// If $shareParent is contained in $shareIdToShareMap, it means that needs resolving
1066
				if (isset($shareIdToShareMap[$shareParent])) {
1067
					$share = $shareIdToShareMap[$shareParent];
1068
					$share->setState(\intval($data['accepted']));
1069
					$share->setTarget($data['file_target']);
1070
				}
1071
			}
1072
			$stmt->closeCursor();
1073
		}
1074
		
1075
		$resolvedShares = \array_values($shareIdToShareMap);
1076
		return $resolvedShares;
1077
	}
1078
1079
	/**
1080
	 * A user is deleted from the system
1081
	 * So clean up the relevant shares.
1082
	 *
1083
	 * @param string $uid
1084
	 * @param int $shareType
1085
	 */
1086
	public function userDeleted($uid, $shareType) {
1087
		$qb = $this->dbConn->getQueryBuilder();
1088
1089
		$qb->delete('share');
1090
1091
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
1092
			/*
1093
			 * Delete all user shares that are owned by this user
1094
			 * or that are received by this user
1095
			 */
1096
1097
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)));
1098
1099
			$qb->andWhere(
1100
				$qb->expr()->orX(
1101
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1102
					$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...
1103
				)
1104
			);
1105
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
1106
			/*
1107
			 * Delete all group shares that are owned by this user
1108
			 * Or special user group shares that are received by this user
1109
			 */
1110
			$qb->where(
1111
				$qb->expr()->andX(
1112
					$qb->expr()->orX(
1113
						$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)),
1114
						$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...
1115
					),
1116
					$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...
1117
				)
1118
			);
1119
1120
			$qb->orWhere(
1121
				$qb->expr()->andX(
1122
					$qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)),
1123
					$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...
1124
				)
1125
			);
1126
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
1127
			/*
1128
			 * Delete all link shares owned by this user.
1129
			 * And all link shares initiated by this user (until #22327 is in)
1130
			 */
1131
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)));
1132
1133
			$qb->andWhere(
1134
				$qb->expr()->orX(
1135
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1136
					$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...
1137
				)
1138
			);
1139
		}
1140
1141
		$qb->execute();
1142
	}
1143
1144
	/**
1145
	 * Delete all shares received by this group. As well as any custom group
1146
	 * shares for group members.
1147
	 *
1148
	 * @param string $gid
1149
	 */
1150
	public function groupDeleted($gid) {
1151
		/*
1152
		 * First delete all custom group shares for group members
1153
		 */
1154
		$qb = $this->dbConn->getQueryBuilder();
1155
		$qb->select('id')
1156
			->from('share')
1157
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
1158
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1159
1160
		$cursor = $qb->execute();
1161
		$ids = [];
1162
		while ($row = $cursor->fetch()) {
1163
			$ids[] = (int)$row['id'];
1164
		}
1165
		$cursor->closeCursor();
1166
1167
		if (!empty($ids)) {
1168
			$chunks = \array_chunk($ids, 100);
1169
			foreach ($chunks as $chunk) {
1170
				$qb->delete('share')
1171
					->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
1172
					->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
1173
				$qb->execute();
1174
			}
1175
		}
1176
1177
		/*
1178
		 * Now delete all the group shares
1179
		 */
1180
		$qb = $this->dbConn->getQueryBuilder();
1181
		$qb->delete('share')
1182
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
1183
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1184
		$qb->execute();
1185
	}
1186
1187
	/**
1188
	 * Delete custom group shares to this group for this user
1189
	 *
1190
	 * @param string $uid
1191
	 * @param string $gid
1192
	 */
1193
	public function userDeletedFromGroup($uid, $gid) {
1194
		/*
1195
		 * Get all group shares
1196
		 */
1197
		$qb = $this->dbConn->getQueryBuilder();
1198
		$qb->select('id')
1199
			->from('share')
1200
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)))
1201
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1202
1203
		$cursor = $qb->execute();
1204
		$ids = [];
1205
		while ($row = $cursor->fetch()) {
1206
			$ids[] = (int)$row['id'];
1207
		}
1208
		$cursor->closeCursor();
1209
1210 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...
1211
			$chunks = \array_chunk($ids, 100);
1212
			foreach ($chunks as $chunk) {
1213
				/*
1214
				 * Delete all special shares wit this users for the found group shares
1215
				 */
1216
				$qb->delete('share')
1217
					->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)))
1218
					->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid)))
1219
					->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
1220
				$qb->execute();
1221
			}
1222
		}
1223
	}
1224
1225
	/**
1226
	 * Check whether the share object fits the expectations of this provider
1227
	 *
1228
	 * @param IShare $share share
1229
	 *
1230
	 * @throws InvalidArgumentException if the share validation failed
1231
	 */
1232
	private function validate($share) {
1233
		if ($share->getName() !== null && \strlen($share->getName()) > 64) {
1234
			throw new \InvalidArgumentException('Share name cannot be more than 64 characters');
1235
		}
1236
1237
		// TODO: add more early validation for fields instead of relying on the DB
1238
	}
1239
}
1240