DefaultShareProvider::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 9
dl 0
loc 19
rs 9.9666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Calviño Sánchez <[email protected]>
10
 * @author Jan-Philipp Litza <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Julius Härtl <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author Maxence Lange <[email protected]>
15
 * @author phisch <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Vincent Petry <[email protected]>
19
 *
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program. If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
namespace OC\Share20;
36
37
use OC\Files\Cache\Cache;
38
use OC\Share20\Exception\BackendError;
39
use OC\Share20\Exception\InvalidShare;
40
use OC\Share20\Exception\ProviderException;
41
use OCP\DB\QueryBuilder\IQueryBuilder;
42
use OCP\Defaults;
43
use OCP\Files\Folder;
44
use OCP\Files\IRootFolder;
45
use OCP\Files\Node;
46
use OCP\IConfig;
47
use OCP\IDBConnection;
48
use OCP\IGroupManager;
49
use OCP\IURLGenerator;
50
use OCP\IUser;
51
use OCP\IUserManager;
52
use OCP\L10N\IFactory;
53
use OCP\Mail\IMailer;
54
use OCP\Share\Exceptions\ShareNotFound;
55
use OCP\Share\IAttributes;
56
use OCP\Share\IShare;
57
use OCP\Share\IShareProvider;
58
use function str_starts_with;
59
60
/**
61
 * Class DefaultShareProvider
62
 *
63
 * @package OC\Share20
64
 */
65
class DefaultShareProvider implements IShareProvider {
66
	// Special share type for user modified group shares
67
	public const SHARE_TYPE_USERGROUP = 2;
68
69
	/** @var IDBConnection */
70
	private $dbConn;
71
72
	/** @var IUserManager */
73
	private $userManager;
74
75
	/** @var IGroupManager */
76
	private $groupManager;
77
78
	/** @var IRootFolder */
79
	private $rootFolder;
80
81
	/** @var IMailer */
82
	private $mailer;
83
84
	/** @var Defaults */
85
	private $defaults;
86
87
	/** @var IFactory */
88
	private $l10nFactory;
89
90
	/** @var IURLGenerator */
91
	private $urlGenerator;
92
93
	/** @var IConfig */
94
	private $config;
95
96
	public function __construct(
97
			IDBConnection $connection,
98
			IUserManager $userManager,
99
			IGroupManager $groupManager,
100
			IRootFolder $rootFolder,
101
			IMailer $mailer,
102
			Defaults $defaults,
103
			IFactory $l10nFactory,
104
			IURLGenerator $urlGenerator,
105
			IConfig $config) {
106
		$this->dbConn = $connection;
107
		$this->userManager = $userManager;
108
		$this->groupManager = $groupManager;
109
		$this->rootFolder = $rootFolder;
110
		$this->mailer = $mailer;
111
		$this->defaults = $defaults;
112
		$this->l10nFactory = $l10nFactory;
113
		$this->urlGenerator = $urlGenerator;
114
		$this->config = $config;
115
	}
116
117
	/**
118
	 * Return the identifier of this provider.
119
	 *
120
	 * @return string Containing only [a-zA-Z0-9]
121
	 */
122
	public function identifier() {
123
		return 'ocinternal';
124
	}
125
126
	/**
127
	 * Share a path
128
	 *
129
	 * @param \OCP\Share\IShare $share
130
	 * @return \OCP\Share\IShare The share object
131
	 * @throws ShareNotFound
132
	 * @throws \Exception
133
	 */
134
	public function create(\OCP\Share\IShare $share) {
135
		$qb = $this->dbConn->getQueryBuilder();
136
137
		$qb->insert('share');
138
		$qb->setValue('share_type', $qb->createNamedParameter($share->getShareType()));
139
140
		if ($share->getShareType() === IShare::TYPE_USER) {
141
			//Set the UID of the user we share with
142
			$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
143
			$qb->setValue('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING));
144
145
			//If an expiration date is set store it
146
			if ($share->getExpirationDate() !== null) {
147
				$qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime'));
148
			}
149
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
150
			//Set the GID of the group we share with
151
			$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
152
153
			//If an expiration date is set store it
154
			if ($share->getExpirationDate() !== null) {
155
				$qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime'));
156
			}
157
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
158
			//set label for public link
159
			$qb->setValue('label', $qb->createNamedParameter($share->getLabel()));
160
			//Set the token of the share
161
			$qb->setValue('token', $qb->createNamedParameter($share->getToken()));
162
163
			//If a password is set store it
164
			if ($share->getPassword() !== null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() !== null is always true.
Loading history...
165
				$qb->setValue('password', $qb->createNamedParameter($share->getPassword()));
166
			}
167
168
			$qb->setValue('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL));
169
170
			//If an expiration date is set store it
171
			if ($share->getExpirationDate() !== null) {
172
				$qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime'));
173
			}
174
175
			if (method_exists($share, 'getParent')) {
176
				$qb->setValue('parent', $qb->createNamedParameter($share->getParent()));
177
			}
178
179
			$qb->setValue('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0, IQueryBuilder::PARAM_INT));
180
		} else {
181
			throw new \Exception('invalid share type!');
182
		}
183
184
		// Set what is shares
185
		$qb->setValue('item_type', $qb->createParameter('itemType'));
186
		if ($share->getNode() instanceof \OCP\Files\File) {
187
			$qb->setParameter('itemType', 'file');
188
		} else {
189
			$qb->setParameter('itemType', 'folder');
190
		}
191
192
		// Set the file id
193
		$qb->setValue('item_source', $qb->createNamedParameter($share->getNode()->getId()));
194
		$qb->setValue('file_source', $qb->createNamedParameter($share->getNode()->getId()));
195
196
		// set the permissions
197
		$qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions()));
198
199
		// set share attributes
200
		$shareAttributes = $this->formatShareAttributes(
201
			$share->getAttributes()
202
		);
203
		$qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
204
205
		// Set who created this share
206
		$qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
207
208
		// Set who is the owner of this file/folder (and this the owner of the share)
209
		$qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()));
210
211
		// Set the file target
212
		$qb->setValue('file_target', $qb->createNamedParameter($share->getTarget()));
213
214
		if ($share->getNote() !== '') {
215
			$qb->setValue('note', $qb->createNamedParameter($share->getNote()));
216
		}
217
218
		// Set the time this share was created
219
		$qb->setValue('stime', $qb->createNamedParameter(time()));
220
221
		// insert the data and fetch the id of the share
222
		$this->dbConn->beginTransaction();
223
		$qb->execute();
224
		$id = $this->dbConn->lastInsertId('*PREFIX*share');
225
226
		// Now fetch the inserted share and create a complete share object
227
		$qb = $this->dbConn->getQueryBuilder();
228
		$qb->select('*')
229
			->from('share')
230
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
231
232
		$cursor = $qb->execute();
233
		$data = $cursor->fetch();
234
		$this->dbConn->commit();
235
		$cursor->closeCursor();
236
237
		if ($data === false) {
238
			throw new ShareNotFound('Newly created share could not be found');
239
		}
240
241
		$mailSendValue = $share->getMailSend();
242
		$data['mail_send'] = ($mailSendValue === null) ? true : $mailSendValue;
0 ignored issues
show
introduced by
The condition $mailSendValue === null is always false.
Loading history...
243
244
		$share = $this->createShare($data);
245
		return $share;
246
	}
247
248
	/**
249
	 * Update a share
250
	 *
251
	 * @param \OCP\Share\IShare $share
252
	 * @return \OCP\Share\IShare The share object
253
	 * @throws ShareNotFound
254
	 * @throws \OCP\Files\InvalidPathException
255
	 * @throws \OCP\Files\NotFoundException
256
	 */
257
	public function update(\OCP\Share\IShare $share) {
258
		$originalShare = $this->getShareById($share->getId());
259
260
		$shareAttributes = $this->formatShareAttributes($share->getAttributes());
261
262
		if ($share->getShareType() === IShare::TYPE_USER) {
263
			/*
264
			 * We allow updating the recipient on user shares.
265
			 */
266
			$qb = $this->dbConn->getQueryBuilder();
267
			$qb->update('share')
268
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
269
				->set('share_with', $qb->createNamedParameter($share->getSharedWith()))
270
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
271
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
272
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
273
				->set('attributes', $qb->createNamedParameter($shareAttributes))
274
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
275
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
276
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
277
				->set('note', $qb->createNamedParameter($share->getNote()))
278
				->set('accepted', $qb->createNamedParameter($share->getStatus()))
279
				->execute();
280
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
281
			$qb = $this->dbConn->getQueryBuilder();
282
			$qb->update('share')
283
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
284
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
285
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
286
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
287
				->set('attributes', $qb->createNamedParameter($shareAttributes))
288
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
289
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
290
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
291
				->set('note', $qb->createNamedParameter($share->getNote()))
292
				->execute();
293
294
			/*
295
			 * Update all user defined group shares
296
			 */
297
			$qb = $this->dbConn->getQueryBuilder();
298
			$qb->update('share')
299
				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
300
				->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
301
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
302
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
303
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
304
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
305
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
306
				->set('note', $qb->createNamedParameter($share->getNote()))
307
				->execute();
308
309
			/*
310
			 * Now update the permissions for all children that have not set it to 0
311
			 */
312
			$qb = $this->dbConn->getQueryBuilder();
313
			$qb->update('share')
314
				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
315
				->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0)))
316
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
317
				->set('attributes', $qb->createNamedParameter($shareAttributes))
318
				->execute();
319
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
320
			$qb = $this->dbConn->getQueryBuilder();
321
			$qb->update('share')
322
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
323
				->set('password', $qb->createNamedParameter($share->getPassword()))
324
				->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
325
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
326
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
327
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
328
				->set('attributes', $qb->createNamedParameter($shareAttributes))
329
				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
330
				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
331
				->set('token', $qb->createNamedParameter($share->getToken()))
332
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
333
				->set('note', $qb->createNamedParameter($share->getNote()))
334
				->set('label', $qb->createNamedParameter($share->getLabel()))
335
				->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT)
0 ignored issues
show
Unused Code introduced by
The call to OCP\DB\QueryBuilder\IQueryBuilder::set() has too many arguments starting with OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

335
				->/** @scrutinizer ignore-call */ set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT)

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. Please note the @ignore annotation hint above.

Loading history...
336
				->execute();
337
		}
338
339
		if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
340
			$this->propagateNote($share);
341
		}
342
343
344
		return $share;
345
	}
346
347
	/**
348
	 * Accept a share.
349
	 *
350
	 * @param IShare $share
351
	 * @param string $recipient
352
	 * @return IShare The share object
353
	 * @since 9.0.0
354
	 */
355
	public function acceptShare(IShare $share, string $recipient): IShare {
356
		if ($share->getShareType() === IShare::TYPE_GROUP) {
357
			$group = $this->groupManager->get($share->getSharedWith());
358
			$user = $this->userManager->get($recipient);
359
360
			if (is_null($group)) {
361
				throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
362
			}
363
364
			if (!$group->inGroup($user)) {
365
				throw new ProviderException('Recipient not in receiving group');
366
			}
367
368
			// Try to fetch user specific share
369
			$qb = $this->dbConn->getQueryBuilder();
370
			$stmt = $qb->select('*')
371
				->from('share')
372
				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
373
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
374
				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
375
				->andWhere($qb->expr()->orX(
376
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
377
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
378
				))
379
				->execute();
380
381
			$data = $stmt->fetch();
382
			$stmt->closeCursor();
383
384
			/*
385
			 * Check if there already is a user specific group share.
386
			 * If there is update it (if required).
387
			 */
388
			if ($data === false) {
389
				$id = $this->createUserSpecificGroupShare($share, $recipient);
390
			} else {
391
				$id = $data['id'];
392
			}
393
		} elseif ($share->getShareType() === IShare::TYPE_USER) {
394
			if ($share->getSharedWith() !== $recipient) {
395
				throw new ProviderException('Recipient does not match');
396
			}
397
398
			$id = $share->getId();
399
		} else {
400
			throw new ProviderException('Invalid shareType');
401
		}
402
403
		$qb = $this->dbConn->getQueryBuilder();
404
		$qb->update('share')
405
			->set('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED))
406
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
407
			->execute();
408
409
		return $share;
410
	}
411
412
	/**
413
	 * Get all children of this share
414
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
415
	 *
416
	 * @param \OCP\Share\IShare $parent
417
	 * @return \OCP\Share\IShare[]
418
	 */
419
	public function getChildren(\OCP\Share\IShare $parent) {
420
		$children = [];
421
422
		$qb = $this->dbConn->getQueryBuilder();
423
		$qb->select('*')
424
			->from('share')
425
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
426
			->andWhere(
427
				$qb->expr()->in(
428
					'share_type',
429
					$qb->createNamedParameter([
430
						IShare::TYPE_USER,
431
						IShare::TYPE_GROUP,
432
						IShare::TYPE_LINK,
433
					], IQueryBuilder::PARAM_INT_ARRAY)
434
				)
435
			)
436
			->andWhere($qb->expr()->orX(
437
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
438
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
439
			))
440
			->orderBy('id');
441
442
		$cursor = $qb->execute();
443
		while ($data = $cursor->fetch()) {
444
			$children[] = $this->createShare($data);
445
		}
446
		$cursor->closeCursor();
447
448
		return $children;
449
	}
450
451
	/**
452
	 * Delete a share
453
	 *
454
	 * @param \OCP\Share\IShare $share
455
	 */
456
	public function delete(\OCP\Share\IShare $share) {
457
		$qb = $this->dbConn->getQueryBuilder();
458
		$qb->delete('share')
459
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
460
461
		/*
462
		 * If the share is a group share delete all possible
463
		 * user defined groups shares.
464
		 */
465
		if ($share->getShareType() === IShare::TYPE_GROUP) {
466
			$qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
467
		}
468
469
		$qb->execute();
470
	}
471
472
	/**
473
	 * Unshare a share from the recipient. If this is a group share
474
	 * this means we need a special entry in the share db.
475
	 *
476
	 * @param IShare $share
477
	 * @param string $recipient UserId of recipient
478
	 * @throws BackendError
479
	 * @throws ProviderException
480
	 */
481
	public function deleteFromSelf(IShare $share, $recipient) {
482
		if ($share->getShareType() === IShare::TYPE_GROUP) {
483
			$group = $this->groupManager->get($share->getSharedWith());
484
			$user = $this->userManager->get($recipient);
485
486
			if (is_null($group)) {
487
				throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
488
			}
489
490
			if (!$group->inGroup($user)) {
491
				// nothing left to do
492
				return;
493
			}
494
495
			// Try to fetch user specific share
496
			$qb = $this->dbConn->getQueryBuilder();
497
			$stmt = $qb->select('*')
498
				->from('share')
499
				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
500
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
501
				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
502
				->andWhere($qb->expr()->orX(
503
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
504
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
505
				))
506
				->execute();
507
508
			$data = $stmt->fetch();
509
510
			/*
511
			 * Check if there already is a user specific group share.
512
			 * If there is update it (if required).
513
			 */
514
			if ($data === false) {
515
				$id = $this->createUserSpecificGroupShare($share, $recipient);
516
				$permissions = $share->getPermissions();
517
			} else {
518
				$permissions = $data['permissions'];
519
				$id = $data['id'];
520
			}
521
522
			if ($permissions !== 0) {
523
				// Update existing usergroup share
524
				$qb = $this->dbConn->getQueryBuilder();
525
				$qb->update('share')
526
					->set('permissions', $qb->createNamedParameter(0))
527
					->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
528
					->execute();
529
			}
530
		} elseif ($share->getShareType() === IShare::TYPE_USER) {
531
			if ($share->getSharedWith() !== $recipient) {
532
				throw new ProviderException('Recipient does not match');
533
			}
534
535
			// We can just delete user and link shares
536
			$this->delete($share);
537
		} else {
538
			throw new ProviderException('Invalid shareType');
539
		}
540
	}
541
542
	protected function createUserSpecificGroupShare(IShare $share, string $recipient): int {
543
		$type = $share->getNodeType();
544
545
		$qb = $this->dbConn->getQueryBuilder();
546
		$qb->insert('share')
547
			->values([
548
				'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP),
549
				'share_with' => $qb->createNamedParameter($recipient),
550
				'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
551
				'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
552
				'parent' => $qb->createNamedParameter($share->getId()),
553
				'item_type' => $qb->createNamedParameter($type),
554
				'item_source' => $qb->createNamedParameter($share->getNodeId()),
555
				'file_source' => $qb->createNamedParameter($share->getNodeId()),
556
				'file_target' => $qb->createNamedParameter($share->getTarget()),
557
				'permissions' => $qb->createNamedParameter($share->getPermissions()),
558
				'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
559
			])->execute();
560
561
		return $qb->getLastInsertId();
562
	}
563
564
	/**
565
	 * @inheritdoc
566
	 *
567
	 * For now this only works for group shares
568
	 * If this gets implemented for normal shares we have to extend it
569
	 */
570
	public function restore(IShare $share, string $recipient): IShare {
571
		$qb = $this->dbConn->getQueryBuilder();
572
		$qb->select('permissions')
573
			->from('share')
574
			->where(
575
				$qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))
576
			);
577
		$cursor = $qb->execute();
578
		$data = $cursor->fetch();
579
		$cursor->closeCursor();
580
581
		$originalPermission = $data['permissions'];
582
583
		$qb = $this->dbConn->getQueryBuilder();
584
		$qb->update('share')
585
			->set('permissions', $qb->createNamedParameter($originalPermission))
586
			->where(
587
				$qb->expr()->eq('parent', $qb->createNamedParameter($share->getParent()))
0 ignored issues
show
Bug introduced by
The method getParent() does not exist on OCP\Share\IShare. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\Share\IShare. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

587
				$qb->expr()->eq('parent', $qb->createNamedParameter($share->/** @scrutinizer ignore-call */ getParent()))
Loading history...
588
			)->andWhere(
589
				$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))
590
			)->andWhere(
591
				$qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))
592
			);
593
594
		$qb->execute();
595
596
		return $this->getShareById($share->getId(), $recipient);
597
	}
598
599
	/**
600
	 * @inheritdoc
601
	 */
602
	public function move(\OCP\Share\IShare $share, $recipient) {
603
		if ($share->getShareType() === IShare::TYPE_USER) {
604
			// Just update the target
605
			$qb = $this->dbConn->getQueryBuilder();
606
			$qb->update('share')
607
				->set('file_target', $qb->createNamedParameter($share->getTarget()))
608
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
609
				->execute();
610
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
611
			// Check if there is a usergroup share
612
			$qb = $this->dbConn->getQueryBuilder();
613
			$stmt = $qb->select('id')
614
				->from('share')
615
				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
616
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
617
				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
618
				->andWhere($qb->expr()->orX(
619
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
620
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
621
				))
622
				->setMaxResults(1)
623
				->execute();
624
625
			$data = $stmt->fetch();
626
			$stmt->closeCursor();
627
628
			$shareAttributes = $this->formatShareAttributes(
629
				$share->getAttributes()
630
			);
631
632
			if ($data === false) {
633
				// No usergroup share yet. Create one.
634
				$qb = $this->dbConn->getQueryBuilder();
635
				$qb->insert('share')
636
					->values([
637
						'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP),
638
						'share_with' => $qb->createNamedParameter($recipient),
639
						'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
640
						'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
641
						'parent' => $qb->createNamedParameter($share->getId()),
642
						'item_type' => $qb->createNamedParameter($share->getNodeType()),
643
						'item_source' => $qb->createNamedParameter($share->getNodeId()),
644
						'file_source' => $qb->createNamedParameter($share->getNodeId()),
645
						'file_target' => $qb->createNamedParameter($share->getTarget()),
646
						'permissions' => $qb->createNamedParameter($share->getPermissions()),
647
						'attributes' => $qb->createNamedParameter($shareAttributes),
648
						'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
649
					])->execute();
650
			} else {
651
				// Already a usergroup share. Update it.
652
				$qb = $this->dbConn->getQueryBuilder();
653
				$qb->update('share')
654
					->set('file_target', $qb->createNamedParameter($share->getTarget()))
655
					->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
656
					->execute();
657
			}
658
		}
659
660
		return $share;
661
	}
662
663
	public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true) {
664
		$qb = $this->dbConn->getQueryBuilder();
665
		$qb->select('s.*',
666
			'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
667
			'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
668
			'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum')
669
			->from('share', 's')
670
			->andWhere($qb->expr()->orX(
671
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
672
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
673
			));
674
675
		$qb->andWhere($qb->expr()->orX(
676
			$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)),
677
			$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)),
678
			$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK))
679
		));
680
681
		/**
682
		 * Reshares for this user are shares where they are the owner.
683
		 */
684
		if ($reshares === false) {
685
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
686
		} else {
687
			$qb->andWhere(
688
				$qb->expr()->orX(
689
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
690
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
691
				)
692
			);
693
		}
694
695
		// todo? maybe get these from the oc_mounts table
696
		$childMountNodes = array_filter($node->getDirectoryListing(), function (Node $node): bool {
697
			return $node->getInternalPath() === '';
698
		});
699
		$childMountRootIds = array_map(function (Node $node): int {
700
			return $node->getId();
701
		}, $childMountNodes);
702
703
		$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
704
		if ($shallow) {
705
			$qb->andWhere(
706
				$qb->expr()->orX(
707
					$qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())),
708
					$qb->expr()->in('f.fileid', $qb->createParameter('chunk'))
709
				)
710
			);
711
		} else {
712
			$qb->andWhere(
713
				$qb->expr()->orX(
714
					$qb->expr()->like('f.path', $qb->createNamedParameter($this->dbConn->escapeLikeParameter($node->getInternalPath()) . '/%')),
715
					$qb->expr()->in('f.fileid', $qb->createParameter('chunk'))
716
				)
717
			);
718
		}
719
720
		$qb->orderBy('id');
721
722
		$shares = [];
723
724
		$chunks = array_chunk($childMountRootIds, 1000);
725
726
		// Force the request to be run when there is 0 mount.
727
		if (count($chunks) === 0) {
728
			$chunks = [[]];
729
		}
730
731
		foreach ($chunks as $chunk) {
732
			$qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
733
			$cursor = $qb->executeQuery();
734
			while ($data = $cursor->fetch()) {
735
				$shares[$data['fileid']][] = $this->createShare($data);
736
			}
737
			$cursor->closeCursor();
738
		}
739
740
		return $shares;
741
	}
742
743
	/**
744
	 * @inheritdoc
745
	 */
746
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
747
		$qb = $this->dbConn->getQueryBuilder();
748
		$qb->select('*')
749
			->from('share')
750
			->andWhere($qb->expr()->orX(
751
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
752
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
753
			));
754
755
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
756
757
		/**
758
		 * Reshares for this user are shares where they are the owner.
759
		 */
760
		if ($reshares === false) {
761
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
762
		} else {
763
			if ($node === null) {
764
				$qb->andWhere(
765
					$qb->expr()->orX(
766
						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
767
						$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
768
					)
769
				);
770
			}
771
		}
772
773
		if ($node !== null) {
774
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
775
		}
776
777
		if ($limit !== -1) {
778
			$qb->setMaxResults($limit);
779
		}
780
781
		$qb->setFirstResult($offset);
782
		$qb->orderBy('id');
783
784
		$cursor = $qb->execute();
785
		$shares = [];
786
		while ($data = $cursor->fetch()) {
787
			$shares[] = $this->createShare($data);
788
		}
789
		$cursor->closeCursor();
790
791
		return $shares;
792
	}
793
794
	/**
795
	 * @inheritdoc
796
	 */
797
	public function getShareById($id, $recipientId = null) {
798
		$qb = $this->dbConn->getQueryBuilder();
799
800
		$qb->select('*')
801
			->from('share')
802
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
803
			->andWhere(
804
				$qb->expr()->in(
805
					'share_type',
806
					$qb->createNamedParameter([
807
						IShare::TYPE_USER,
808
						IShare::TYPE_GROUP,
809
						IShare::TYPE_LINK,
810
					], IQueryBuilder::PARAM_INT_ARRAY)
811
				)
812
			)
813
			->andWhere($qb->expr()->orX(
814
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
815
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
816
			));
817
818
		$cursor = $qb->execute();
819
		$data = $cursor->fetch();
820
		$cursor->closeCursor();
821
822
		if ($data === false) {
823
			throw new ShareNotFound();
824
		}
825
826
		try {
827
			$share = $this->createShare($data);
828
		} catch (InvalidShare $e) {
829
			throw new ShareNotFound();
830
		}
831
832
		// If the recipient is set for a group share resolve to that user
833
		if ($recipientId !== null && $share->getShareType() === IShare::TYPE_GROUP) {
834
			$share = $this->resolveGroupShares([$share], $recipientId)[0];
835
		}
836
837
		return $share;
838
	}
839
840
	/**
841
	 * Get shares for a given path
842
	 *
843
	 * @param \OCP\Files\Node $path
844
	 * @return \OCP\Share\IShare[]
845
	 */
846
	public function getSharesByPath(Node $path) {
847
		$qb = $this->dbConn->getQueryBuilder();
848
849
		$cursor = $qb->select('*')
850
			->from('share')
851
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
852
			->andWhere(
853
				$qb->expr()->orX(
854
					$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)),
855
					$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP))
856
				)
857
			)
858
			->andWhere($qb->expr()->orX(
859
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
860
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
861
			))
862
			->execute();
863
864
		$shares = [];
865
		while ($data = $cursor->fetch()) {
866
			$shares[] = $this->createShare($data);
867
		}
868
		$cursor->closeCursor();
869
870
		return $shares;
871
	}
872
873
	/**
874
	 * Returns whether the given database result can be interpreted as
875
	 * a share with accessible file (not trashed, not deleted)
876
	 */
877
	private function isAccessibleResult($data) {
878
		// exclude shares leading to deleted file entries
879
		if ($data['fileid'] === null || $data['path'] === null) {
880
			return false;
881
		}
882
883
		// exclude shares leading to trashbin on home storages
884
		$pathSections = explode('/', $data['path'], 2);
885
		// FIXME: would not detect rare md5'd home storage case properly
886
		if ($pathSections[0] !== 'files'
887
			&& (str_starts_with($data['storage_string_id'], 'home::') || str_starts_with($data['storage_string_id'], 'object::user'))) {
888
			return false;
889
		} elseif ($pathSections[0] === '__groupfolders'
890
			&& str_starts_with($pathSections[1], 'trash/')
891
		) {
892
			// exclude shares leading to trashbin on group folders storages
893
			return false;
894
		}
895
		return true;
896
	}
897
898
	/**
899
	 * @inheritdoc
900
	 */
901
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
902
		/** @var Share[] $shares */
903
		$shares = [];
904
905
		if ($shareType === IShare::TYPE_USER) {
906
			//Get shares directly with this user
907
			$qb = $this->dbConn->getQueryBuilder();
908
			$qb->select('s.*',
909
				'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
910
				'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
911
				'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum'
912
			)
913
				->selectAlias('st.id', 'storage_string_id')
914
				->from('share', 's')
915
				->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
916
				->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'));
917
918
			// Order by id
919
			$qb->orderBy('s.id');
920
921
			// Set limit and offset
922
			if ($limit !== -1) {
923
				$qb->setMaxResults($limit);
924
			}
925
			$qb->setFirstResult($offset);
926
927
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)))
928
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
929
				->andWhere($qb->expr()->orX(
930
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
931
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
932
				));
933
934
			// Filter by node if provided
935
			if ($node !== null) {
936
				$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
937
			}
938
939
			$cursor = $qb->execute();
940
941
			while ($data = $cursor->fetch()) {
942
				if ($data['fileid'] && $data['path'] === null) {
943
					$data['path'] = (string) $data['path'];
944
					$data['name'] = (string) $data['name'];
945
					$data['checksum'] = (string) $data['checksum'];
946
				}
947
				if ($this->isAccessibleResult($data)) {
948
					$shares[] = $this->createShare($data);
949
				}
950
			}
951
			$cursor->closeCursor();
952
		} elseif ($shareType === IShare::TYPE_GROUP) {
953
			$user = $this->userManager->get($userId);
954
			$allGroups = ($user instanceof IUser) ? $this->groupManager->getUserGroupIds($user) : [];
955
956
			/** @var Share[] $shares2 */
957
			$shares2 = [];
958
959
			$start = 0;
960
			while (true) {
961
				$groups = array_slice($allGroups, $start, 1000);
962
				$start += 1000;
963
964
				if ($groups === []) {
965
					break;
966
				}
967
968
				$qb = $this->dbConn->getQueryBuilder();
969
				$qb->select('s.*',
970
					'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
971
					'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
972
					'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum'
973
				)
974
					->selectAlias('st.id', 'storage_string_id')
975
					->from('share', 's')
976
					->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
977
					->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
978
					->orderBy('s.id')
979
					->setFirstResult(0);
980
981
				if ($limit !== -1) {
982
					$qb->setMaxResults($limit - count($shares));
983
				}
984
985
				// Filter by node if provided
986
				if ($node !== null) {
987
					$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
988
				}
989
990
991
				$groups = array_filter($groups);
992
993
				$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
994
					->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter(
995
						$groups,
996
						IQueryBuilder::PARAM_STR_ARRAY
997
					)))
998
					->andWhere($qb->expr()->orX(
999
						$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1000
						$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1001
					));
1002
1003
				$cursor = $qb->execute();
1004
				while ($data = $cursor->fetch()) {
1005
					if ($offset > 0) {
1006
						$offset--;
1007
						continue;
1008
					}
1009
1010
					if ($this->isAccessibleResult($data)) {
1011
						$shares2[] = $this->createShare($data);
1012
					}
1013
				}
1014
				$cursor->closeCursor();
1015
			}
1016
1017
			/*
1018
			 * Resolve all group shares to user specific shares
1019
			 */
1020
			$shares = $this->resolveGroupShares($shares2, $userId);
1021
		} else {
1022
			throw new BackendError('Invalid backend');
1023
		}
1024
1025
1026
		return $shares;
1027
	}
1028
1029
	/**
1030
	 * Get a share by token
1031
	 *
1032
	 * @param string $token
1033
	 * @return \OCP\Share\IShare
1034
	 * @throws ShareNotFound
1035
	 */
1036
	public function getShareByToken($token) {
1037
		$qb = $this->dbConn->getQueryBuilder();
1038
1039
		$cursor = $qb->select('*')
1040
			->from('share')
1041
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)))
1042
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
1043
			->andWhere($qb->expr()->orX(
1044
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1045
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1046
			))
1047
			->execute();
1048
1049
		$data = $cursor->fetch();
1050
1051
		if ($data === false) {
1052
			throw new ShareNotFound();
1053
		}
1054
1055
		try {
1056
			$share = $this->createShare($data);
1057
		} catch (InvalidShare $e) {
1058
			throw new ShareNotFound();
1059
		}
1060
1061
		return $share;
1062
	}
1063
1064
	/**
1065
	 * Create a share object from an database row
1066
	 *
1067
	 * @param mixed[] $data
1068
	 * @return \OCP\Share\IShare
1069
	 * @throws InvalidShare
1070
	 */
1071
	private function createShare($data) {
1072
		$share = new Share($this->rootFolder, $this->userManager);
1073
		$share->setId((int)$data['id'])
1074
			->setShareType((int)$data['share_type'])
1075
			->setPermissions((int)$data['permissions'])
1076
			->setTarget($data['file_target'])
1077
			->setNote((string)$data['note'])
1078
			->setMailSend((bool)$data['mail_send'])
1079
			->setStatus((int)$data['accepted'])
1080
			->setLabel($data['label']);
1081
1082
		$shareTime = new \DateTime();
1083
		$shareTime->setTimestamp((int)$data['stime']);
1084
		$share->setShareTime($shareTime);
1085
1086
		if ($share->getShareType() === IShare::TYPE_USER) {
1087
			$share->setSharedWith($data['share_with']);
1088
			$user = $this->userManager->get($data['share_with']);
1089
			if ($user !== null) {
1090
				$share->setSharedWithDisplayName($user->getDisplayName());
1091
			}
1092
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1093
			$share->setSharedWith($data['share_with']);
1094
			$group = $this->groupManager->get($data['share_with']);
1095
			if ($group !== null) {
1096
				$share->setSharedWithDisplayName($group->getDisplayName());
1097
			}
1098
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
1099
			$share->setPassword($data['password']);
1100
			$share->setSendPasswordByTalk((bool)$data['password_by_talk']);
1101
			$share->setToken($data['token']);
1102
		}
1103
1104
		$share = $this->updateShareAttributes($share, $data['attributes']);
1105
1106
		$share->setSharedBy($data['uid_initiator']);
1107
		$share->setShareOwner($data['uid_owner']);
1108
1109
		$share->setNodeId((int)$data['file_source']);
1110
		$share->setNodeType($data['item_type']);
1111
1112
		if ($data['expiration'] !== null) {
1113
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
1114
			$share->setExpirationDate($expiration);
1115
		}
1116
1117
		if (isset($data['f_permissions'])) {
1118
			$entryData = $data;
1119
			$entryData['permissions'] = $entryData['f_permissions'];
1120
			$entryData['parent'] = $entryData['f_parent'];
1121
			$share->setNodeCacheEntry(Cache::cacheEntryFromData($entryData,
1122
				\OC::$server->getMimeTypeLoader()));
1123
		}
1124
1125
		$share->setProviderId($this->identifier());
1126
		$share->setHideDownload((int)$data['hide_download'] === 1);
1127
1128
		return $share;
1129
	}
1130
1131
	/**
1132
	 * @param Share[] $shares
1133
	 * @param $userId
1134
	 * @return Share[] The updates shares if no update is found for a share return the original
1135
	 */
1136
	private function resolveGroupShares($shares, $userId) {
1137
		$result = [];
1138
1139
		$start = 0;
1140
		while (true) {
1141
			/** @var Share[] $shareSlice */
1142
			$shareSlice = array_slice($shares, $start, 100);
1143
			$start += 100;
1144
1145
			if ($shareSlice === []) {
1146
				break;
1147
			}
1148
1149
			/** @var int[] $ids */
1150
			$ids = [];
1151
			/** @var Share[] $shareMap */
1152
			$shareMap = [];
1153
1154
			foreach ($shareSlice as $share) {
1155
				$ids[] = (int)$share->getId();
1156
				$shareMap[$share->getId()] = $share;
1157
			}
1158
1159
			$qb = $this->dbConn->getQueryBuilder();
1160
1161
			$query = $qb->select('*')
1162
				->from('share')
1163
				->where($qb->expr()->in('parent', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1164
				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
1165
				->andWhere($qb->expr()->orX(
1166
					$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1167
					$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1168
				));
1169
1170
			$stmt = $query->execute();
1171
1172
			while ($data = $stmt->fetch()) {
1173
				$shareMap[$data['parent']]->setPermissions((int)$data['permissions']);
1174
				$shareMap[$data['parent']]->setStatus((int)$data['accepted']);
1175
				$shareMap[$data['parent']]->setTarget($data['file_target']);
1176
				$shareMap[$data['parent']]->setParent($data['parent']);
1177
			}
1178
1179
			$stmt->closeCursor();
1180
1181
			foreach ($shareMap as $share) {
1182
				$result[] = $share;
1183
			}
1184
		}
1185
1186
		return $result;
1187
	}
1188
1189
	/**
1190
	 * A user is deleted from the system
1191
	 * So clean up the relevant shares.
1192
	 *
1193
	 * @param string $uid
1194
	 * @param int $shareType
1195
	 */
1196
	public function userDeleted($uid, $shareType) {
1197
		$qb = $this->dbConn->getQueryBuilder();
1198
1199
		$qb->delete('share');
1200
1201
		if ($shareType === IShare::TYPE_USER) {
1202
			/*
1203
			 * Delete all user shares that are owned by this user
1204
			 * or that are received by this user
1205
			 */
1206
1207
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)));
1208
1209
			$qb->andWhere(
1210
				$qb->expr()->orX(
1211
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1212
					$qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
1213
				)
1214
			);
1215
		} elseif ($shareType === IShare::TYPE_GROUP) {
1216
			/*
1217
			 * Delete all group shares that are owned by this user
1218
			 * Or special user group shares that are received by this user
1219
			 */
1220
			$qb->where(
1221
				$qb->expr()->andX(
1222
					$qb->expr()->orX(
1223
						$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)),
1224
						$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))
1225
					),
1226
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid))
1227
				)
1228
			);
1229
1230
			$qb->orWhere(
1231
				$qb->expr()->andX(
1232
					$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)),
1233
					$qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
1234
				)
1235
			);
1236
		} elseif ($shareType === IShare::TYPE_LINK) {
1237
			/*
1238
			 * Delete all link shares owned by this user.
1239
			 * And all link shares initiated by this user (until #22327 is in)
1240
			 */
1241
			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)));
1242
1243
			$qb->andWhere(
1244
				$qb->expr()->orX(
1245
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1246
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($uid))
1247
				)
1248
			);
1249
		} else {
1250
			\OC::$server->getLogger()->logException(new \InvalidArgumentException('Default share provider tried to delete all shares for type: ' . $shareType));
1251
			return;
1252
		}
1253
1254
		$qb->execute();
1255
	}
1256
1257
	/**
1258
	 * Delete all shares received by this group. As well as any custom group
1259
	 * shares for group members.
1260
	 *
1261
	 * @param string $gid
1262
	 */
1263
	public function groupDeleted($gid) {
1264
		/*
1265
		 * First delete all custom group shares for group members
1266
		 */
1267
		$qb = $this->dbConn->getQueryBuilder();
1268
		$qb->select('id')
1269
			->from('share')
1270
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1271
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1272
1273
		$cursor = $qb->execute();
1274
		$ids = [];
1275
		while ($row = $cursor->fetch()) {
1276
			$ids[] = (int)$row['id'];
1277
		}
1278
		$cursor->closeCursor();
1279
1280
		if (!empty($ids)) {
1281
			$chunks = array_chunk($ids, 100);
1282
			foreach ($chunks as $chunk) {
1283
				$qb->delete('share')
1284
					->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1285
					->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
1286
				$qb->execute();
1287
			}
1288
		}
1289
1290
		/*
1291
		 * Now delete all the group shares
1292
		 */
1293
		$qb = $this->dbConn->getQueryBuilder();
1294
		$qb->delete('share')
1295
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1296
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1297
		$qb->execute();
1298
	}
1299
1300
	/**
1301
	 * Delete custom group shares to this group for this user
1302
	 *
1303
	 * @param string $uid
1304
	 * @param string $gid
1305
	 */
1306
	public function userDeletedFromGroup($uid, $gid) {
1307
		/*
1308
		 * Get all group shares
1309
		 */
1310
		$qb = $this->dbConn->getQueryBuilder();
1311
		$qb->select('id')
1312
			->from('share')
1313
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1314
			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1315
1316
		$cursor = $qb->execute();
1317
		$ids = [];
1318
		while ($row = $cursor->fetch()) {
1319
			$ids[] = (int)$row['id'];
1320
		}
1321
		$cursor->closeCursor();
1322
1323
		if (!empty($ids)) {
1324
			$chunks = array_chunk($ids, 100);
1325
			foreach ($chunks as $chunk) {
1326
				/*
1327
				 * Delete all special shares with this users for the found group shares
1328
				 */
1329
				$qb->delete('share')
1330
					->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1331
					->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid)))
1332
					->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
1333
				$qb->execute();
1334
			}
1335
		}
1336
	}
1337
1338
	/**
1339
	 * @inheritdoc
1340
	 */
1341
	public function getAccessList($nodes, $currentAccess) {
1342
		$ids = [];
1343
		foreach ($nodes as $node) {
1344
			$ids[] = $node->getId();
1345
		}
1346
1347
		$qb = $this->dbConn->getQueryBuilder();
1348
1349
		$or = $qb->expr()->orX(
1350
			$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)),
1351
			$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)),
1352
			$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK))
1353
		);
1354
1355
		if ($currentAccess) {
1356
			$or->add($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)));
1357
		}
1358
1359
		$qb->select('id', 'parent', 'share_type', 'share_with', 'file_source', 'file_target', 'permissions')
1360
			->from('share')
1361
			->where(
1362
				$or
1363
			)
1364
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1365
			->andWhere($qb->expr()->orX(
1366
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1367
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1368
			));
1369
		$cursor = $qb->execute();
1370
1371
		$users = [];
1372
		$link = false;
1373
		while ($row = $cursor->fetch()) {
1374
			$type = (int)$row['share_type'];
1375
			if ($type === IShare::TYPE_USER) {
1376
				$uid = $row['share_with'];
1377
				$users[$uid] = isset($users[$uid]) ? $users[$uid] : [];
1378
				$users[$uid][$row['id']] = $row;
1379
			} elseif ($type === IShare::TYPE_GROUP) {
1380
				$gid = $row['share_with'];
1381
				$group = $this->groupManager->get($gid);
1382
1383
				if ($group === null) {
1384
					continue;
1385
				}
1386
1387
				$userList = $group->getUsers();
1388
				foreach ($userList as $user) {
1389
					$uid = $user->getUID();
1390
					$users[$uid] = isset($users[$uid]) ? $users[$uid] : [];
1391
					$users[$uid][$row['id']] = $row;
1392
				}
1393
			} elseif ($type === IShare::TYPE_LINK) {
1394
				$link = true;
1395
			} elseif ($type === IShare::TYPE_USERGROUP && $currentAccess === true) {
1396
				$uid = $row['share_with'];
1397
				$users[$uid] = isset($users[$uid]) ? $users[$uid] : [];
1398
				$users[$uid][$row['id']] = $row;
1399
			}
1400
		}
1401
		$cursor->closeCursor();
1402
1403
		if ($currentAccess === true) {
1404
			$users = array_map([$this, 'filterSharesOfUser'], $users);
1405
			$users = array_filter($users);
1406
		} else {
1407
			$users = array_keys($users);
1408
		}
1409
1410
		return ['users' => $users, 'public' => $link];
1411
	}
1412
1413
	/**
1414
	 * For each user the path with the fewest slashes is returned
1415
	 * @param array $shares
1416
	 * @return array
1417
	 */
1418
	protected function filterSharesOfUser(array $shares) {
1419
		// Group shares when the user has a share exception
1420
		foreach ($shares as $id => $share) {
1421
			$type = (int) $share['share_type'];
1422
			$permissions = (int) $share['permissions'];
1423
1424
			if ($type === IShare::TYPE_USERGROUP) {
1425
				unset($shares[$share['parent']]);
1426
1427
				if ($permissions === 0) {
1428
					unset($shares[$id]);
1429
				}
1430
			}
1431
		}
1432
1433
		$best = [];
1434
		$bestDepth = 0;
1435
		foreach ($shares as $id => $share) {
1436
			$depth = substr_count(($share['file_target'] ?? ''), '/');
1437
			if (empty($best) || $depth < $bestDepth) {
1438
				$bestDepth = $depth;
1439
				$best = [
1440
					'node_id' => $share['file_source'],
1441
					'node_path' => $share['file_target'],
1442
				];
1443
			}
1444
		}
1445
1446
		return $best;
1447
	}
1448
1449
	/**
1450
	 * propagate notes to the recipients
1451
	 *
1452
	 * @param IShare $share
1453
	 * @throws \OCP\Files\NotFoundException
1454
	 */
1455
	private function propagateNote(IShare $share) {
1456
		if ($share->getShareType() === IShare::TYPE_USER) {
1457
			$user = $this->userManager->get($share->getSharedWith());
1458
			$this->sendNote([$user], $share);
1459
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1460
			$group = $this->groupManager->get($share->getSharedWith());
1461
			$groupMembers = $group->getUsers();
1462
			$this->sendNote($groupMembers, $share);
1463
		}
1464
	}
1465
1466
	/**
1467
	 * send note by mail
1468
	 *
1469
	 * @param array $recipients
1470
	 * @param IShare $share
1471
	 * @throws \OCP\Files\NotFoundException
1472
	 */
1473
	private function sendNote(array $recipients, IShare $share) {
1474
		$toListByLanguage = [];
1475
1476
		foreach ($recipients as $recipient) {
1477
			/** @var IUser $recipient */
1478
			$email = $recipient->getEMailAddress();
1479
			if ($email) {
1480
				$language = $this->l10nFactory->getUserLanguage($recipient);
1481
				if (!isset($toListByLanguage[$language])) {
1482
					$toListByLanguage[$language] = [];
1483
				}
1484
				$toListByLanguage[$language][$email] = $recipient->getDisplayName();
1485
			}
1486
		}
1487
1488
		if (empty($toListByLanguage)) {
1489
			return;
1490
		}
1491
1492
		foreach ($toListByLanguage as $l10n => $toList) {
1493
			$filename = $share->getNode()->getName();
1494
			$initiator = $share->getSharedBy();
1495
			$note = $share->getNote();
1496
1497
			$l = $this->l10nFactory->get('lib', $l10n);
1498
1499
			$initiatorUser = $this->userManager->get($initiator);
1500
			$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
1501
			$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
1502
			$plainHeading = $l->t('%1$s shared »%2$s« with you and wants to add:', [$initiatorDisplayName, $filename]);
1503
			$htmlHeading = $l->t('%1$s shared »%2$s« with you and wants to add', [$initiatorDisplayName, $filename]);
1504
			$message = $this->mailer->createMessage();
1505
1506
			$emailTemplate = $this->mailer->createEMailTemplate('defaultShareProvider.sendNote');
1507
1508
			$emailTemplate->setSubject($l->t('»%s« added a note to a file shared with you', [$initiatorDisplayName]));
1509
			$emailTemplate->addHeader();
1510
			$emailTemplate->addHeading($htmlHeading, $plainHeading);
1511
			$emailTemplate->addBodyText(htmlspecialchars($note), $note);
1512
1513
			$link = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]);
1514
			$emailTemplate->addBodyButton(
1515
				$l->t('Open »%s«', [$filename]),
1516
				$link
1517
			);
1518
1519
1520
			// The "From" contains the sharers name
1521
			$instanceName = $this->defaults->getName();
1522
			$senderName = $l->t(
1523
				'%1$s via %2$s',
1524
				[
1525
					$initiatorDisplayName,
1526
					$instanceName
1527
				]
1528
			);
1529
			$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
1530
			if ($initiatorEmailAddress !== null) {
1531
				$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
1532
				$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
1533
			} else {
1534
				$emailTemplate->addFooter();
1535
			}
1536
1537
			if (count($toList) === 1) {
1538
				$message->setTo($toList);
1539
			} else {
1540
				$message->setTo([]);
1541
				$message->setBcc($toList);
1542
			}
1543
			$message->useTemplate($emailTemplate);
1544
			$this->mailer->send($message);
1545
		}
1546
	}
1547
1548
	public function getAllShares(): iterable {
1549
		$qb = $this->dbConn->getQueryBuilder();
1550
1551
		$qb->select('*')
1552
			->from('share')
1553
			->where(
1554
				$qb->expr()->orX(
1555
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_USER)),
1556
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_GROUP)),
1557
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_LINK))
1558
				)
1559
			);
1560
1561
		$cursor = $qb->execute();
1562
		while ($data = $cursor->fetch()) {
1563
			try {
1564
				$share = $this->createShare($data);
1565
			} catch (InvalidShare $e) {
1566
				continue;
1567
			}
1568
1569
			yield $share;
1570
		}
1571
		$cursor->closeCursor();
1572
	}
1573
1574
	/**
1575
	 * Load from database format (JSON string) to IAttributes
1576
	 *
1577
	 * @return IShare the modified share
1578
	 */
1579
	private function updateShareAttributes(IShare $share, ?string $data): IShare {
1580
		if ($data !== null && $data !== '') {
1581
			$attributes = new ShareAttributes();
1582
			$compressedAttributes = \json_decode($data, true);
1583
			if ($compressedAttributes === false || $compressedAttributes === null) {
1584
				return $share;
1585
			}
1586
			foreach ($compressedAttributes as $compressedAttribute) {
1587
				$attributes->setAttribute(
1588
					$compressedAttribute[0],
1589
					$compressedAttribute[1],
1590
					$compressedAttribute[2]
1591
				);
1592
			}
1593
			$share->setAttributes($attributes);
1594
		}
1595
1596
		return $share;
1597
	}
1598
1599
	/**
1600
	 * Format IAttributes to database format (JSON string)
1601
	 */
1602
	private function formatShareAttributes(?IAttributes $attributes): ?string {
1603
		if ($attributes === null || empty($attributes->toArray())) {
1604
			return null;
1605
		}
1606
1607
		$compressedAttributes = [];
1608
		foreach ($attributes->toArray() as $attribute) {
1609
			$compressedAttributes[] = [
1610
				0 => $attribute['scope'],
1611
				1 => $attribute['key'],
1612
				2 => $attribute['enabled']
1613
			];
1614
		}
1615
		return \json_encode($compressedAttributes);
1616
	}
1617
}
1618